Compare commits

..

40 Commits

Author SHA1 Message Date
Valentin Maerten
24a3ccdf42 chore: changelog for #2871 2026-06-07 16:39:02 +02:00
Christian Provenzano
b455286b63 fix: deterministic ordering of required var prompts (#2871) 2026-06-07 16:36:58 +02:00
Valentin Maerten
81d621d8aa chore: add mise config for dev environment setup (#2863)
Co-authored-by: Andrey Nering <andreynering@users.noreply.github.com>
2026-06-07 14:46:01 +02:00
dependabot[bot]
f50327f2a2 chore(deps): bump mdast-util-to-hast from 13.2.0 to 13.2.1 in /website (#2846)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-31 11:13:28 +02:00
Valentin Maerten
dd810d42e1 chore: changelog for #2852 2026-05-31 11:08:42 +02:00
Koji Ishii
de1717b05f feat(completion): ps completion supports aliases (#2852)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-05-31 09:06:39 +00:00
Valentin Maerten
7766e8add5 chore: changelog for #2859 2026-05-31 11:03:32 +02:00
Zhaowei Li
e61d0f0f7c fix: install fish completions to vendor directory (#2859) 2026-05-31 11:01:13 +02:00
renovate[bot]
9c8799480d chore(deps): update goreleaser/goreleaser-action digest to 5daf1e9 (#2857)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-05-31 08:47:42 +00:00
renovate[bot]
7f04f7057c chore(deps): update all non-major dependencies (#2858)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-05-31 10:38:31 +02:00
renovate[bot]
48dd62cdc1 chore(deps): update go-task/setup-task digest to 01a4adf (#2848) 2026-05-18 15:37:33 +02:00
renovate[bot]
b27a6653f6 chore(deps): update all non-major dependencies (#2816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-05-17 12:40:51 +02:00
renovate[bot]
76269b03c4 chore(deps): update pnpm to v11 (#2844)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-05-17 12:20:33 +02:00
renovate[bot]
ed492ac862 chore(deps): update pnpm/action-setup digest to 0e279bb (#2843)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-17 11:59:56 +02:00
dependabot[bot]
1d385ad5b4 chore(deps): bump vite from 5.4.19 to 5.4.21 in /website (#2841)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-17 11:59:42 +02:00
Andrey Nering
2eff3545d9 ci(goreleaser): add missing env to workflow 2026-05-16 18:09:51 -03:00
Andrey Nering
bd79c29a39 v3.51.1 2026-05-16 17:51:05 -03:00
Andrey Nering
136e0dae89 ci: fix goreleaser 2026-05-16 17:50:34 -03:00
Andrey Nering
41a2137044 v3.51.0 2026-05-16 17:27:25 -03:00
imgbot[bot]
aea7f7a713 docs: optimize images (#2690) 2026-05-16 17:23:46 -03:00
renovate[bot]
c3e16b0a68 chore(deps): update mvdan.cc/sh/v3 digest to f5c6e27 (#2834) 2026-05-16 17:20:20 -03:00
renovate[bot]
19699a1f0e chore(deps): update pnpm/action-setup digest to 739bfe4 (#2835)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-11 16:27:48 +02:00
Timothy Rule
9e3ff27ba1 fix: avoid panic after calling WordsSeq (#2810) 2026-05-09 21:42:00 +00:00
renovate[bot]
de05ebc503 chore(deps): update goreleaser/goreleaser-action digest to 1a80836 (#2815) 2026-05-09 21:34:39 +00:00
Andrey Nering
6e154f2b71 docs(changelog): add entry for #2832 2026-05-09 18:28:46 -03:00
Andrey Nering
9ba1e566a9 fix: update mvdan.cc/sh to fix regression
* Closes #2812
* Closes #2818
* Ref https://github.com/mvdan/sh/pull/1330
2026-05-09 18:28:46 -03:00
Andrey Nering
1b53d425ee ci(govulncheck): update golang.org/x/net 2026-05-09 11:45:53 -03:00
Andrey Nering
7b3559ce4c ci: fix build and lint builds 2026-05-09 11:45:53 -03:00
Andrey Nering
43dbeb4260 docs(changelog): add entry for #2406 #2408 2026-05-09 11:25:49 -03:00
Jerry Wiltse
d9e0e04725 feat: add joinEnv and joinUrl string functions and 2 new system vars (#2408) 2026-05-09 14:19:33 +00:00
Andrey Nering
629a10813f chore: update minimal go version to go 1.25.10 2026-05-09 11:19:02 -03:00
Andrey Nering
dc64864643 docs: add security > threat model page (#2830) 2026-05-09 13:59:36 +00:00
renovate[bot]
5f78da2d0a chore(deps): update dependency netlify-cli to v26 (#2823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-05 03:03:32 +02:00
renovate[bot]
7915b78ac2 chore(deps): update pnpm/action-setup action to v6 (#2824)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-05 03:03:13 +02:00
Andrey Nering
3441bf522e docs(changelog): add entry for #2820 2026-05-01 10:03:53 -03:00
Roman Dahm
46f5db22c8 perf(templater): skip template engine for strings without {{ (#2820) 2026-05-01 09:57:16 -03:00
Ludovic Fernandez
ecffcc720f chore(golangci-lint): improve and clean configuration (#2808) 2026-04-23 21:13:15 -03:00
Valentin Maerten
be35b3af75 feat(website): highlight big orgs / projects that use Task (#2803) 2026-04-22 10:02:06 +02:00
renovate[bot]
3aaedc790d chore(deps): update goreleaser/goreleaser-action digest to e24998b (#2804)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-20 22:49:24 +02:00
renovate[bot]
0b19d973ac chore(deps): update all non-major dependencies (#2805)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-20 22:08:05 +02:00
65 changed files with 2529 additions and 2048 deletions

View File

@@ -16,7 +16,7 @@ jobs:
name: Lint
strategy:
matrix:
go-version: [1.25.x, 1.26.x]
go-version: [1.25.10, 1.26.x]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
@@ -26,9 +26,9 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1
with:
version: v2.11.4
version: v2.12.2
lint-jsonschema:
runs-on: ubuntu-latest

View File

@@ -21,7 +21,7 @@ jobs:
with:
go-version: "1.26.x"
cache: true
- uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
- uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7
with:
version: "~> v2"
args: release --snapshot --clean --config .goreleaser-pr.yml

View File

@@ -23,7 +23,7 @@ jobs:
go-version: 1.26.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7
with:
distribution: goreleaser-pro
version: latest

View File

@@ -23,7 +23,7 @@ jobs:
with:
go-version: 1.26.x
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
@@ -32,22 +32,23 @@ jobs:
run: npm install -g npm@latest
- name: Install Task
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6
with:
package_json_file: "website/package.json"
run_install: "true"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7
with:
distribution: goreleaser-pro
version: latest
args: release --clean --draft
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
GH_GORELEASER_TOKEN: ${{secrets.GH_GORELEASER_TOKEN}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}

View File

@@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
go-version: [1.25.x, 1.26.x]
go-version: [1.25.10, 1.26.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}}
steps:

View File

@@ -2,33 +2,16 @@ version: "2"
formatters:
enable:
- gofmt
- gofumpt
- goimports
- gci
settings:
gofmt:
simplify: true
rewrite-rules:
- pattern: interface{}
replacement: any
gofumpt:
module-path: github.com/go-task/task/v3
goimports:
local-prefixes:
- github.com/go-task
gci:
sections:
- standard
- default
- prefix(github.com/go-task)
- localmodule
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
linters:
enable:
@@ -36,6 +19,7 @@ linters:
- gosec
- mirror
- misspell
- modernize
- noctx
- paralleltest
- thelper
@@ -56,13 +40,8 @@ linters:
- pkg: errors
desc: Use github.com/go-task/task/v3/errors instead
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$

View File

@@ -76,7 +76,7 @@ nfpms:
- src: completion/bash/task.bash
dst: /etc/bash_completion.d/task
- src: completion/fish/task.fish
dst: /usr/share/fish/completions/task.fish
dst: /usr/share/fish/vendor_completions.d/task.fish
- src: completion/zsh/_task
dst: /usr/local/share/zsh/site-functions/_task
@@ -89,7 +89,7 @@ brews:
repository:
owner: go-task
name: homebrew-tap
token: "{{secrets.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
token: "{{.Env.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
test: system "#{bin}/task", "--help"
install: |-
bin.install "task"
@@ -131,7 +131,7 @@ winget:
owner: go-task
name: winget-pkgs
branch: 'task-{{.Version}}'
token: "{{secrets.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
token: "{{.Env.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
pull_request:
enabled: true
draft: false

View File

@@ -2,8 +2,29 @@
## Unreleased
- Fixed --interactive prompts for required vars sometimes appearing in a random
order. Prompts now follow the order the vars are declared in the Taskfile.
(#2871 by @caproven)
- Fixed Fish completions not being picked up correctly by installing them to
Fish's `vendor_completions.d` directory instead of `completions` (#2850, #2859
by @Legimity).
- PowerShell completions now work with aliases of the `task` command, not just
the `task` binary itself (#2852 by @kojiishi).
## v3.51.1 - 2026-05-16
- A significant performance boost was achieved for large Taskfiles (monorepos)
by skipping templating altogether when the string is static (#2820 by @romnn).
- Added `absPath` template function that resolves a path to its absolute form,
cleaning `..` and `.` components (#2681, #2788 by @mateenanjum).
- Added `joinEnv` function to join paths based on your oprating system: `;` for
Windows and `:` elsewhere, and `joinUrl` to join URL paths. Also, added two
new special variables: `FILE_PATH_SEPARATOR` which returns `\` on Windows and
`/` elsewhere, and `PATH_LIST_SEPARATOR` which returns `;` on Windows and `:`
elsewhere (#2406, #2408 by @solvingj).
- Update the shell interpreter with a regression fix (#2812, #2832 by
@andreynering).
- Fix potential panic with the shell interpreter (#2810 by @trulede).
## v3.50.0 - 2026-04-13
@@ -16,13 +37,13 @@
- Fixed watch mode ignoring SIGHUP signal, causing the watcher to exit instead
of restarting (#2764, #2642).
- Fixed a long time bug where the task wouldn't re-run as it should when using
`method: timestamp` and the files listed on `generates:` were deleted.
This makes `method: timestamp` behaves the same as `method: checksum`
(#1230, #2716 by @drichardson).
`method: timestamp` and the files listed on `generates:` were deleted. This
makes `method: timestamp` behaves the same as `method: checksum` (#1230, #2716
by @drichardson).
## v3.49.1 - 2026-03-08
* Reverted #2632 for now, which caused some regressions. That change will be
- Reverted #2632 for now, which caused some regressions. That change will be
reworked (#2720, #2722, #2723).
## v3.49.0 - 2026-03-07

View File

@@ -202,11 +202,13 @@ func (c *Compiler) getSpecialVars(t *ast.Task, call *Call) (map[string]string, e
// across platforms. This prevents issues with backslashes being interpreted
// as escape sequences when paths are used in shell commands on Windows.
allVars := map[string]string{
"TASK_EXE": filepath.ToSlash(os.Args[0]),
"ROOT_TASKFILE": filepath.ToSlash(filepathext.SmartJoin(c.Dir, c.Entrypoint)),
"ROOT_DIR": filepath.ToSlash(c.Dir),
"USER_WORKING_DIR": filepath.ToSlash(c.UserWorkingDir),
"TASK_VERSION": version.GetVersion(),
"TASK_EXE": filepath.ToSlash(os.Args[0]),
"ROOT_TASKFILE": filepath.ToSlash(filepathext.SmartJoin(c.Dir, c.Entrypoint)),
"ROOT_DIR": filepath.ToSlash(c.Dir),
"USER_WORKING_DIR": filepath.ToSlash(c.UserWorkingDir),
"TASK_VERSION": version.GetVersion(),
"PATH_LIST_SEPARATOR": string(os.PathListSeparator),
"FILE_PATH_SEPARATOR": string(os.PathSeparator),
}
if t != nil {
allVars["TASK"] = t.Task

View File

@@ -1,6 +1,8 @@
using namespace System.Management.Automation
Register-ArgumentCompleter -CommandName task -ScriptBlock {
$cmdNames = @('task') + (Get-Alias -Definition task,task.exe,*\task,*\task.exe -ErrorAction SilentlyContinue).Name | Select-Object -Unique
Register-ArgumentCompleter -CommandName $cmdNames -ScriptBlock {
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
if ($commandName.StartsWith('-')) {

View File

@@ -67,7 +67,6 @@ type (
Compiler *Compiler
Output output.Output
OutputStyle ast.Output
OutputCIAuto bool
TaskSorter sort.Sorter
UserWorkingDir string
EnableVersionCheck bool
@@ -523,21 +522,6 @@ func (o *outputStyleOption) ApplyToExecutor(e *Executor) {
e.OutputStyle = o.outputStyle
}
// WithOutputCIAuto enables automatic selection of a CI-aware output style
// (e.g. "gitlab") when a supported CI environment is detected and no explicit
// output style is configured in the Taskfile or via CLI.
func WithOutputCIAuto(enabled bool) ExecutorOption {
return &outputCIAutoOption{enabled}
}
type outputCIAutoOption struct {
enabled bool
}
func (o *outputCIAutoOption) ApplyToExecutor(e *Executor) {
e.OutputCIAuto = o.enabled
}
// WithTaskSorter sets the sorter that the [Executor] will use to sort tasks. By
// default, the sorter is set to sort tasks alphabetically, but with tasks with
// no namespace (in the root Taskfile) first.

38
go.mod
View File

@@ -1,27 +1,27 @@
module github.com/go-task/task/v3
go 1.25.8
go 1.25.10
require (
charm.land/bubbles/v2 v2.1.0
charm.land/bubbletea/v2 v2.0.2
charm.land/lipgloss/v2 v2.0.2
charm.land/bubbletea/v2 v2.0.6
charm.land/lipgloss/v2 v2.0.3
github.com/Ladicle/tabwriter v1.0.0
github.com/Masterminds/semver/v3 v3.4.0
github.com/alecthomas/chroma/v2 v2.23.1
github.com/Masterminds/semver/v3 v3.5.0
github.com/alecthomas/chroma/v2 v2.26.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
github.com/elliotchance/orderedmap/v3 v3.1.0
github.com/fatih/color v1.19.0
github.com/fsnotify/fsnotify v1.9.0
github.com/fsnotify/fsnotify v1.10.1
github.com/go-task/slim-sprig/v3 v3.0.0
github.com/go-task/template v0.2.0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-getter v1.8.6
github.com/joho/godotenv v1.5.1
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/puzpuzpuz/xsync/v4 v4.4.0
github.com/puzpuzpuz/xsync/v4 v4.5.0
github.com/sajari/fuzzy v1.0.0
github.com/sebdah/goldie/v2 v2.8.0
github.com/spf13/pflag v1.0.10
@@ -29,9 +29,9 @@ require (
github.com/zeebo/xxh3 v1.1.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/sync v0.20.0
golang.org/x/term v0.42.0
golang.org/x/term v0.43.0
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997
mvdan.cc/sh/v3 v3.13.1
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117
)
require (
@@ -68,16 +68,16 @@ require (
github.com/aws/smithy-go v1.24.2 // 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.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/colorprofile v0.4.3 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 // indirect
github.com/charmbracelet/x/ansi v0.11.7 // 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.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dlclark/regexp2/v2 v2.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
@@ -94,10 +94,10 @@ require (
github.com/klauspost/compress v1.18.5 // 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/lucasb-eyer/go-colorful v1.4.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.21 // indirect
github.com/mattn/go-runewidth v0.0.23 // 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
@@ -120,11 +120,11 @@ require (
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/crypto v0.51.0 // indirect
golang.org/x/net v0.55.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/api v0.271.0 // indirect
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect

76
go.sum
View File

@@ -2,10 +2,10 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs=
charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
charm.land/bubbletea/v2 v2.0.6 h1:UHN/91OyuhaOFGSrBXQ/hMZD8IO1Uc4BvHlgHXL2WJo=
charm.land/bubbletea/v2 v2.0.6/go.mod h1:MH/D8ZLlN3op37vQvijKuU29g3rqTp+aQapURFonF9g=
charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU=
charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA=
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.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
@@ -36,12 +36,12 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.5.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.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/chroma/v2 v2.26.1 h1:2X21EdxGZNv5GF9mG5u+uzc02GCFyGxbcBm3Grd9A78=
github.com/alecthomas/chroma/v2 v2.26.1/go.mod h1:lxhRRa9H4hPmRLOOdYga4zkQIQjq3dtrrdwQeCfu78Y=
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=
@@ -92,12 +92,12 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ=
github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
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-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.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 h1:Q9fO0y1Zo5KB/5Vu8JZoLGm1N3RzF9bNj3Ao3xoR+Ac=
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468/go.mod h1:bAAz7dh/FTYfC+oiHavL4mX1tOIBZ0ZwYjSi3qE6ivM=
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
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=
@@ -118,8 +118,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2/v2 v2.1.1 h1:LCUGyd9Wf+r+VVOl8Ny38JTpWJcAsdVnCIuhhtthmKw=
github.com/dlclark/regexp2/v2 v2.1.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -138,8 +138,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -192,14 +192,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
github.com/mattn/go-runewidth v0.0.23/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=
@@ -214,8 +214,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.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo=
github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg=
github.com/puzpuzpuz/xsync/v4 v4.5.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=
@@ -272,27 +272,23 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
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.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
@@ -319,5 +315,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997 h1:3bbJwtPFh98dJ6lxRdR3eLHTH1CmR3BcU6TriIMiXjE=
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997/go.mod h1:Qy/zdaMDxq9sT72Gi43K3gsV+TtTohyDO3f1cyBVwuo=
mvdan.cc/sh/v3 v3.13.1 h1:DP3TfgZhDkT7lerUdnp6PTGKyxxzz6T+cOlY/xEvfWk=
mvdan.cc/sh/v3 v3.13.1/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117 h1:BfzdGSjcnJBb8sPNLudpzTml8zFUxS1N0N/v9IIS6tQ=
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=

View File

@@ -81,7 +81,7 @@ func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, erro
traverseFunc = func(copy, v reflect.Value) error {
switch v.Kind() {
case reflect.Ptr:
case reflect.Pointer:
// Unwrap the pointer
originalValue := v.Elem()
// If the pointer is nil, do nothing

View File

@@ -127,7 +127,10 @@ func ExpandFields(s string) ([]string, error) {
s = escape(s)
p := syntax.NewParser()
var words []*syntax.Word
for w := range p.WordsSeq(strings.NewReader(s)) {
for w, err := range p.WordsSeq(strings.NewReader(s)) {
if err != nil {
return nil, err
}
words = append(words, w)
}
cfg := &expand.Config{

View File

@@ -71,7 +71,6 @@ var (
Dir string
Entrypoint string
Output ast.Output
OutputCIAuto bool
Color bool
Interval time.Duration
Failfast bool
@@ -144,11 +143,10 @@ func init() {
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.")
pflag.StringVarP(&Entrypoint, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml".`)
pflag.StringVarP(&Output.Name, "output", "o", "", "Sets output style: [interleaved|group|prefixed|gitlab].")
pflag.StringVarP(&Output.Name, "output", "o", "", "Sets output style: [interleaved|group|prefixed].")
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.")
OutputCIAuto = getConfig(config, "OUTPUT_CI_AUTO", func() *bool { return config.OutputCIAuto }, false)
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.")
@@ -307,7 +305,6 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithConcurrency(Concurrency),
task.WithInterval(Interval),
task.WithOutputStyle(Output),
task.WithOutputCIAuto(OutputCIAuto),
task.WithTaskSorter(sorter),
task.WithVersionCheck(true),
task.WithFailfast(Failfast),

View File

@@ -1,116 +0,0 @@
package output
import (
"bytes"
"fmt"
"io"
"regexp"
"sync"
"time"
"github.com/google/uuid"
"github.com/go-task/task/v3/internal/templater"
)
// GitLab renders a task's output wrapped in [GitLab CI collapsible
// section markers]. Section IDs are generated automatically so that
// start and end markers always match and stay unique per invocation.
//
// GitLab wraps output at the task level via the [TaskWrapper] interface,
// so each task (including its command announcements and all its cmds)
// appears inside a single collapsible section. Nested task invocations
// produce nested sections.
//
// [GitLab CI collapsible section markers]: https://docs.gitlab.com/ci/jobs/job_logs/#create-custom-collapsible-sections
type GitLab struct {
Collapsed bool
ErrorOnly bool
}
// WrapWriter is a passthrough for GitLab: wrapping happens at the task
// level via WrapTask, not per command.
func (g GitLab) WrapWriter(stdOut, stdErr io.Writer, _ string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
return stdOut, stdErr, func(error) error { return nil }
}
// WrapTask wraps an entire task's output in a single collapsible section.
func (g GitLab) WrapTask(stdOut, _ io.Writer, cache *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
header := ""
if cache != nil {
header = templater.Replace("{{.TASK}}", cache)
}
if header == "" {
header = "task"
}
id := fmt.Sprintf("%s_%s", gitlabSectionSlug(header), uuid.New().String()[:8])
gw := &gitlabWriter{
writer: stdOut,
id: id,
header: header,
collapsed: g.Collapsed,
startTS: time.Now().Unix(),
}
return gw, gw, func(err error) error {
if g.ErrorOnly && err == nil {
return nil
}
return gw.close()
}
}
type gitlabWriter struct {
mu sync.Mutex
writer io.Writer
buff bytes.Buffer
id string
header string
collapsed bool
startTS int64
}
func (gw *gitlabWriter) Write(p []byte) (int, error) {
gw.mu.Lock()
defer gw.mu.Unlock()
return gw.buff.Write(p)
}
func (gw *gitlabWriter) close() error {
gw.mu.Lock()
defer gw.mu.Unlock()
if gw.buff.Len() == 0 {
return nil
}
var b bytes.Buffer
b.WriteString(gitlabSectionStart(gw.startTS, gw.id, gw.header, gw.collapsed))
if _, err := io.Copy(&b, &gw.buff); err != nil {
return err
}
b.WriteString(gitlabSectionEnd(time.Now().Unix(), gw.id))
_, err := io.Copy(gw.writer, &b)
return err
}
func gitlabSectionStart(ts int64, id, header string, collapsed bool) string {
options := ""
if collapsed {
options = "[collapsed=true]"
}
return fmt.Sprintf("\x1b[0Ksection_start:%d:%s%s\r\x1b[0K%s\n", ts, id, options, header)
}
func gitlabSectionEnd(ts int64, id string) string {
return fmt.Sprintf("\x1b[0Ksection_end:%d:%s\r\x1b[0K\n", ts, id)
}
var gitlabSlugDisallowed = regexp.MustCompile(`[^a-zA-Z0-9_.-]`)
func gitlabSectionSlug(s string) string {
return gitlabSlugDisallowed.ReplaceAllString(s, "_")
}

View File

@@ -13,14 +13,6 @@ type Output interface {
WrapWriter(stdOut, stdErr io.Writer, prefix string, cache *templater.Cache) (io.Writer, io.Writer, CloseFunc)
}
// TaskWrapper is an optional interface that Output implementations can satisfy
// to wrap an entire task's execution in a single enclosing block — including
// the task's command announcements and all its commands' output — instead of
// wrapping each command individually via WrapWriter.
type TaskWrapper interface {
WrapTask(stdOut, stdErr io.Writer, cache *templater.Cache) (io.Writer, io.Writer, CloseFunc)
}
type CloseFunc func(err error) error
// Build the Output for the requested ast.Output.
@@ -42,14 +34,6 @@ func BuildFor(o *ast.Output, logger *logger.Logger) (Output, error) {
return nil, err
}
return NewPrefixed(logger), nil
case "gitlab":
if err := checkOutputGroupUnset(o); err != nil {
return nil, err
}
return GitLab{
Collapsed: o.GitLab.Collapsed,
ErrorOnly: o.GitLab.ErrorOnly,
}, nil
default:
return nil, fmt.Errorf(`task: output style %q not recognized`, o.Name)
}

View File

@@ -5,12 +5,7 @@ import (
"errors"
"fmt"
"io"
"regexp"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/fatih/color"
"github.com/stretchr/testify/assert"
@@ -126,238 +121,6 @@ func TestGroupErrorOnlyShowsOutputOnError(t *testing.T) {
assert.Equal(t, "std-out\nstd-err\n", b.String())
}
func gitlabTaskCache(taskName string) *templater.Cache {
return &templater.Cache{
Vars: ast.NewVars(
&ast.VarElement{
Key: "TASK",
Value: ast.Var{Value: taskName},
},
),
}
}
var gitlabMarkerPattern = regexp.MustCompile(
`\x1b\[0Ksection_start:(\d+):(\S+?)(\[[^\]]+\])?\r\x1b\[0K(.*)\n` +
`(?s)(.*)` +
`\x1b\[0Ksection_end:(\d+):(\S+)\r\x1b\[0K\n$`,
)
func TestGitLab(t *testing.T) {
t.Parallel()
var b bytes.Buffer
o := output.GitLab{}
w, _, cleanup := o.WrapTask(&b, io.Discard, gitlabTaskCache("build"))
fmt.Fprintln(w, "hello")
assert.Equal(t, "", b.String(), "output must be buffered until close")
require.NoError(t, cleanup(nil))
m := gitlabMarkerPattern.FindStringSubmatch(b.String())
require.NotNil(t, m, "output should match GitLab section markers, got: %q", b.String())
assert.Equal(t, m[2], m[7], "start and end section IDs must match")
assert.Empty(t, m[3], "collapsed option should not be present by default")
assert.Equal(t, "build", m[4], "section header should be the task name")
assert.Equal(t, "hello\n", m[5], "wrapped content must be preserved")
assert.Contains(t, m[2], "build_", "section ID should be prefixed with slugged task name")
}
func TestGitLabUniqueSectionIDs(t *testing.T) {
t.Parallel()
o := output.GitLab{}
ids := make([]string, 3)
for i := range ids {
var b bytes.Buffer
w, _, cleanup := o.WrapTask(&b, io.Discard, gitlabTaskCache("build"))
fmt.Fprintln(w, "x")
require.NoError(t, cleanup(nil))
m := gitlabMarkerPattern.FindStringSubmatch(b.String())
require.NotNil(t, m)
ids[i] = m[2]
}
assert.NotEqual(t, ids[0], ids[1])
assert.NotEqual(t, ids[1], ids[2])
assert.NotEqual(t, ids[0], ids[2])
}
func TestGitLabCollapsed(t *testing.T) {
t.Parallel()
var b bytes.Buffer
o := output.GitLab{Collapsed: true}
w, _, cleanup := o.WrapTask(&b, io.Discard, gitlabTaskCache("build"))
fmt.Fprintln(w, "x")
require.NoError(t, cleanup(nil))
m := gitlabMarkerPattern.FindStringSubmatch(b.String())
require.NotNil(t, m)
assert.Equal(t, "[collapsed=true]", m[3])
}
func TestGitLabErrorOnlySwallowsOutputOnNoError(t *testing.T) {
t.Parallel()
var b bytes.Buffer
o := output.GitLab{ErrorOnly: true}
w, _, cleanup := o.WrapTask(&b, io.Discard, gitlabTaskCache("build"))
fmt.Fprintln(w, "hello")
require.NoError(t, cleanup(nil))
assert.Empty(t, b.String())
}
func TestGitLabErrorOnlyShowsOutputOnError(t *testing.T) {
t.Parallel()
var b bytes.Buffer
o := output.GitLab{ErrorOnly: true}
w, _, cleanup := o.WrapTask(&b, io.Discard, gitlabTaskCache("build"))
fmt.Fprintln(w, "hello")
require.NoError(t, cleanup(errors.New("boom")))
m := gitlabMarkerPattern.FindStringSubmatch(b.String())
require.NotNil(t, m)
assert.Equal(t, "hello\n", m[5])
}
func TestGitLabSlugSanitizesTaskName(t *testing.T) {
t.Parallel()
var b bytes.Buffer
o := output.GitLab{}
w, _, cleanup := o.WrapTask(&b, io.Discard, gitlabTaskCache("my task:with spaces"))
fmt.Fprintln(w, "x")
require.NoError(t, cleanup(nil))
m := gitlabMarkerPattern.FindStringSubmatch(b.String())
require.NotNil(t, m)
assert.Regexp(t, `^[a-zA-Z0-9_.-]+$`, m[2], "section ID must only contain GitLab-allowed chars")
}
func TestGitLabWrapWriterIsPassthrough(t *testing.T) {
t.Parallel()
var b bytes.Buffer
o := output.GitLab{}
w, _, cleanup := o.WrapWriter(&b, io.Discard, "", nil)
fmt.Fprintln(w, "hello")
assert.Equal(t, "hello\n", b.String(), "WrapWriter must be a passthrough for GitLab")
assert.NoError(t, cleanup(nil))
assert.Equal(t, "hello\n", b.String(), "closer must be a no-op")
}
func TestGitLabWrapTaskSingleSection(t *testing.T) {
t.Parallel()
var b bytes.Buffer
o := output.GitLab{}
w, _, cleanup := o.WrapTask(&b, io.Discard, gitlabTaskCache("build"))
// Simulate multiple cmd outputs being written during a task's execution.
fmt.Fprintln(w, "cmd 1 output")
fmt.Fprintln(w, "cmd 2 output")
fmt.Fprintln(w, "cmd 3 output")
require.NoError(t, cleanup(nil))
// There must be exactly one section_start and one section_end.
assert.Equal(t, 1, strings.Count(b.String(), "section_start:"))
assert.Equal(t, 1, strings.Count(b.String(), "section_end:"))
m := gitlabMarkerPattern.FindStringSubmatch(b.String())
require.NotNil(t, m)
assert.Equal(t, "cmd 1 output\ncmd 2 output\ncmd 3 output\n", m[5])
}
func TestGitLabWrapTaskDurationElapsed(t *testing.T) {
t.Parallel()
var b bytes.Buffer
o := output.GitLab{}
w, _, cleanup := o.WrapTask(&b, io.Discard, gitlabTaskCache("slow"))
fmt.Fprintln(w, "started")
time.Sleep(1100 * time.Millisecond)
fmt.Fprintln(w, "done")
require.NoError(t, cleanup(nil))
m := gitlabMarkerPattern.FindStringSubmatch(b.String())
require.NotNil(t, m)
startTS, err := strconv.ParseInt(m[1], 10, 64)
require.NoError(t, err)
endTS, err := strconv.ParseInt(m[6], 10, 64)
require.NoError(t, err)
assert.GreaterOrEqual(t, endTS-startTS, int64(1),
"end TS must be at least 1 second after start TS when task takes >1s")
}
func TestGitLabWrapTaskNested(t *testing.T) {
t.Parallel()
var root bytes.Buffer
parent := output.GitLab{}
parentW, _, parentClose := parent.WrapTask(&root, io.Discard, gitlabTaskCache("parent"))
fmt.Fprintln(parentW, "before child")
child := output.GitLab{}
childW, _, childClose := child.WrapTask(parentW, io.Discard, gitlabTaskCache("child"))
fmt.Fprintln(childW, "inside child")
require.NoError(t, childClose(nil))
fmt.Fprintln(parentW, "after child")
require.NoError(t, parentClose(nil))
out := root.String()
// Two section_start and two section_end
assert.Equal(t, 2, strings.Count(out, "section_start:"))
assert.Equal(t, 2, strings.Count(out, "section_end:"))
// Order: parent start → child start → child end → parent end
parentStart := strings.Index(out, "section_start:") // first
childStart := strings.Index(out[parentStart+1:], "section_start:") + parentStart + 1
childEnd := strings.Index(out, "section_end:")
parentEnd := strings.LastIndex(out, "section_end:")
assert.Less(t, parentStart, childStart, "child_start must come after parent_start")
assert.Less(t, childStart, childEnd, "child_end must come after child_start")
assert.Less(t, childEnd, parentEnd, "parent_end must come after child_end")
}
func TestGitLabWrapTaskConcurrentWrites(t *testing.T) {
t.Parallel()
var root bytes.Buffer
parent := output.GitLab{}
parentW, _, parentClose := parent.WrapTask(&root, io.Discard, gitlabTaskCache("parent"))
const numChildren = 10
var wg sync.WaitGroup
for i := 0; i < numChildren; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
child := output.GitLab{}
childW, _, childClose := child.WrapTask(parentW, io.Discard, gitlabTaskCache(fmt.Sprintf("child%d", i)))
fmt.Fprintf(childW, "child %d output\n", i)
_ = childClose(nil)
}(i)
}
wg.Wait()
require.NoError(t, parentClose(nil))
out := root.String()
// 1 parent + 10 children = 11 section_start and 11 section_end
assert.Equal(t, 11, strings.Count(out, "section_start:"))
assert.Equal(t, 11, strings.Count(out, "section_end:"))
// All 10 child outputs present
for i := 0; i < numChildren; i++ {
assert.Contains(t, out, fmt.Sprintf("child %d output", i))
}
}
func TestPrefixed(t *testing.T) { //nolint:paralleltest // cannot run in parallel
var b bytes.Buffer
l := &logger.Logger{

View File

@@ -285,7 +285,9 @@ func isEnvVar(key string, envVars map[string]bool) bool {
key == "TASKFILE_DIR" ||
key == "USER_WORKING_DIR" ||
key == "ALIAS" ||
key == "MATCH" {
key == "MATCH" ||
key == "PATH_LIST_SEPARATOR" ||
key == "FILE_PATH_SEPARATOR" {
return true
}
return envVars[key]

View File

@@ -3,6 +3,8 @@ package templater
import (
"maps"
"math/rand/v2"
"os"
"path"
"path/filepath"
"runtime"
"strings"
@@ -21,8 +23,8 @@ var templateFuncs template.FuncMap
func init() {
taskFuncs := template.FuncMap{
"OS": os,
"ARCH": arch,
"OS": goos,
"ARCH": goarch,
"numCPU": runtime.NumCPU,
"catLines": catLines,
"splitLines": splitLines,
@@ -33,6 +35,8 @@ func init() {
"splitArgs": splitArgs,
"IsSH": IsSH, // Deprecated
"joinPath": filepath.Join,
"joinEnv": joinEnv,
"joinUrl": joinUrl,
"relPath": filepath.Rel,
"absPath": filepath.Abs,
"merge": merge,
@@ -57,11 +61,11 @@ func init() {
maps.Copy(templateFuncs, taskFuncs)
}
func os() string {
func goos() string {
return runtime.GOOS
}
func arch() string {
func goarch() string {
return runtime.GOARCH
}
@@ -95,6 +99,14 @@ func IsSH() bool {
return true
}
func joinEnv(elem ...string) string {
return strings.Join(elem, string(os.PathListSeparator))
}
func joinUrl(elem ...string) string {
return path.Join(elem...)
}
func merge(base map[string]any, v ...map[string]any) map[string]any {
cap := len(v)
for _, m := range v {

View File

@@ -68,6 +68,13 @@ func ReplaceWithExtra[T any](v T, cache *Cache, extra map[string]any) T {
return v
}
// Optimization: skip if string is not a template
if s, ok := any(v).(string); ok {
if !strings.Contains(s, "{{") {
return v
}
}
// Initialize the cache map if it's not already initialized
if cache.cacheMap == nil {
cache.cacheMap = cache.Vars.ToCacheMap()
@@ -82,6 +89,10 @@ func ReplaceWithExtra[T any](v T, cache *Cache, extra map[string]any) T {
// Traverse the value and parse any template variables
copy, err := deepcopy.TraverseStringsFunc(v, func(v string) (string, error) {
// Optimization: skip if string is not a template
if !strings.Contains(v, "{{") {
return v, nil
}
tpl, err := template.New("").Funcs(templateFuncs).Parse(v)
if err != nil {
return v, err

View File

@@ -1 +1 @@
3.50.0
3.51.1

12
mise.toml Normal file
View File

@@ -0,0 +1,12 @@
[tools]
# Runtimes
go = "1.25.10"
node = "24"
pnpm = "11.5.0"
# Dev tools
golangci-lint = "2.12.2"
mockery = "3.2.2"
gotestsum = "latest"
goreleaser = "2"
"go:golang.org/x/exp/cmd/gorelease" = "latest"

View File

@@ -3,6 +3,8 @@ package task
import (
"slices"
"github.com/elliotchance/orderedmap/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/input"
"github.com/go-task/task/v3/internal/term"
@@ -32,7 +34,7 @@ func (e *Executor) promptDepsVars(calls []*Call) error {
// Collect all missing vars from the dependency tree
visited := make(map[string]bool)
varsMap := make(map[string]*ast.VarsWithValidation)
varsMap := orderedmap.NewOrderedMap[string, *ast.VarsWithValidation]()
var collect func(call *Call) error
collect = func(call *Call) error {
@@ -42,8 +44,8 @@ func (e *Executor) promptDepsVars(calls []*Call) error {
}
for _, v := range getMissingRequiredVars(compiledTask) {
if _, exists := varsMap[v.Name]; !exists {
varsMap[v.Name] = v
if !varsMap.Has(v.Name) {
varsMap.Set(v.Name, v)
}
}
@@ -73,14 +75,14 @@ func (e *Executor) promptDepsVars(calls []*Call) error {
}
}
if len(varsMap) == 0 {
if varsMap.Len() == 0 {
return nil
}
prompter := e.newPrompter()
e.promptedVars = ast.NewVars()
for _, v := range varsMap {
for v := range varsMap.Values() {
value, err := prompter.Prompt(v.Name, getEnumValues(v.Enum))
if err != nil {
if errors.Is(err, input.ErrCancelled) {

View File

@@ -6,7 +6,6 @@ import (
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"sync"
@@ -203,27 +202,12 @@ func (e *Executor) setupOutput() error {
if !e.OutputStyle.IsSet() {
e.OutputStyle = e.Taskfile.Output
}
if !e.OutputStyle.IsSet() && e.OutputCIAuto {
if name := detectCIOutput(); name != "" {
e.OutputStyle.Name = name
}
}
var err error
e.Output, err = output.BuildFor(&e.OutputStyle, e.Logger)
return err
}
// detectCIOutput returns the name of a CI-aware output style to use based
// on environment variables set by common CI runners. Returns an empty string
// when no supported CI environment is detected.
func detectCIOutput() string {
if isGitLab, _ := strconv.ParseBool(os.Getenv("GITLAB_CI")); isGitLab {
return "gitlab"
}
return ""
}
func (e *Executor) setupCompiler() error {
if e.UserWorkingDir == "" {
var err error

View File

@@ -1,97 +0,0 @@
package task
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/taskfile/ast"
)
func TestDetectCIOutput(t *testing.T) {
cases := []struct {
name string
env map[string]string
want string
}{
{name: "no CI detected", env: nil, want: ""},
{name: "GITLAB_CI=true", env: map[string]string{"GITLAB_CI": "true"}, want: "gitlab"},
{name: "GITLAB_CI=1", env: map[string]string{"GITLAB_CI": "1"}, want: "gitlab"},
{name: "GITLAB_CI=false", env: map[string]string{"GITLAB_CI": "false"}, want: ""},
{name: "GITLAB_CI empty", env: map[string]string{"GITLAB_CI": ""}, want: ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("GITLAB_CI", "") // reset
for k, v := range tc.env {
t.Setenv(k, v)
}
assert.Equal(t, tc.want, detectCIOutput())
})
}
}
func TestSetupOutputPriority(t *testing.T) {
cases := []struct {
name string
cliStyle ast.Output
taskfileStyle ast.Output
ciAuto bool
gitlabEnv string
wantName string
}{
{
name: "CLI wins over everything",
cliStyle: ast.Output{Name: "prefixed"},
taskfileStyle: ast.Output{Name: "group", Group: ast.OutputGroup{
Begin: "b", End: "e",
}},
ciAuto: true,
gitlabEnv: "true",
wantName: "prefixed",
},
{
name: "Taskfile wins over auto-detect",
taskfileStyle: ast.Output{Name: "prefixed"},
ciAuto: true,
gitlabEnv: "true",
wantName: "prefixed",
},
{
name: "auto-detect activates when nothing explicit",
ciAuto: true,
gitlabEnv: "true",
wantName: "gitlab",
},
{
name: "auto-detect disabled does nothing",
ciAuto: false,
gitlabEnv: "true",
wantName: "",
},
{
name: "auto-detect without CI env does nothing",
ciAuto: true,
gitlabEnv: "",
wantName: "",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("GITLAB_CI", tc.gitlabEnv)
e := &Executor{
OutputStyle: tc.cliStyle,
OutputCIAuto: tc.ciAuto,
Taskfile: &ast.Taskfile{Output: tc.taskfileStyle},
Logger: &logger.Logger{},
}
require.NoError(t, e.setupOutput())
assert.Equal(t, tc.wantName, e.OutputStyle.Name)
})
}
}

12
task.go
View File

@@ -204,9 +204,9 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
release := e.acquireConcurrencyLimit()
defer release()
if err = e.startExecution(ctx, t, func(ctx context.Context) (err error) {
if err = e.startExecution(ctx, t, func(ctx context.Context) error {
e.Logger.VerboseErrf(logger.Magenta, "task: %q started\n", call.Task)
if err = e.runDeps(ctx, t); err != nil {
if err := e.runDeps(ctx, t); err != nil {
return err
}
@@ -266,9 +266,6 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
var deferredExitCode uint8
ctx, taskOutCloser := e.wrapTaskOutput(ctx, t, call)
defer func() { taskOutCloser(err) }()
for i := range t.Cmds {
if t.Cmds[i].Defer {
defer e.runDeferred(t, call, i, t.Vars, &deferredExitCode)
@@ -396,7 +393,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
}
if e.Verbose || (!call.Silent && !cmd.Silent && !t.IsSilent() && !e.Taskfile.Silent && !e.Silent) {
e.printCmdAnnouncement(ctx, t, cmd.Cmd)
e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmd.Cmd)
}
if e.Dry {
@@ -412,8 +409,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
if err != nil {
return fmt.Errorf("task: failed to get variables: %w", err)
}
taskStdOut, taskStdErr := e.writersFromCtx(ctx)
stdOut, stdErr, closer := outputWrapper.WrapWriter(taskStdOut, taskStdErr, t.Prefix, outputTemplater)
stdOut, stdErr, closer := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater)
err = execext.RunCommand(ctx, &execext.RunCommandOptions{
Command: cmd.Cmd,

View File

@@ -1,70 +0,0 @@
package task
import (
"context"
"io"
"github.com/fatih/color"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile/ast"
)
type taskWritersKey struct{}
type taskWriters struct {
stdout, stderr io.Writer
}
// writersFromCtx returns the task-scoped writers if set, otherwise the
// Executor's own stdout/stderr.
func (e *Executor) writersFromCtx(ctx context.Context) (io.Writer, io.Writer) {
if tw, ok := ctx.Value(taskWritersKey{}).(*taskWriters); ok && tw != nil {
return tw.stdout, tw.stderr
}
return e.Stdout, e.Stderr
}
// wrapTaskOutput wraps a task's output in a task-scoped block if e.Output
// implements [output.TaskWrapper] and the task is not interactive. Returns
// the (possibly updated) ctx and a closer that flushes the block. The closer
// is always safe to call — it is a no-op when no wrapping took place.
func (e *Executor) wrapTaskOutput(ctx context.Context, t *ast.Task, call *Call) (context.Context, func(error)) {
noop := func(error) {}
if t.Interactive {
return ctx, noop
}
tw, ok := e.Output.(output.TaskWrapper)
if !ok {
return ctx, noop
}
stdOut, stdErr := e.writersFromCtx(ctx)
vars, err := e.Compiler.FastGetVariables(t, call)
if err != nil {
e.Logger.VerboseErrf(logger.Yellow, "task: output setup: %v\n", err)
return ctx, noop
}
wOut, wErr, closer := tw.WrapTask(stdOut, stdErr, &templater.Cache{Vars: vars})
ctx = context.WithValue(ctx, taskWritersKey{}, &taskWriters{stdout: wOut, stderr: wErr})
return ctx, func(loopErr error) {
if err := closer(loopErr); err != nil {
e.Logger.Errf(logger.Red, "task: output close: %v\n", err)
}
}
}
// printCmdAnnouncement prints the "task: [NAME] CMD" line using the
// task-scoped stderr if available, so the announcement ends up inside the
// task's output block.
func (e *Executor) printCmdAnnouncement(ctx context.Context, t *ast.Task, cmdStr string) {
_, stdErr := e.writersFromCtx(ctx)
if stdErr == e.Stderr {
// No task-scoped writer — fall back to the Logger to preserve existing
// behavior (respects Logger's color config, etc.).
e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmdStr)
return
}
_, _ = color.New(color.FgGreen).Fprintf(stdErr, "task: [%s] %s\n", t.Name(), cmdStr)
}

View File

@@ -12,8 +12,6 @@ type Output struct {
Name string `yaml:"-"`
// Group specific style
Group OutputGroup
// GitLab specific style
GitLab OutputGitLab
}
// IsSet returns true if and only if a custom output style is set.
@@ -34,30 +32,19 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
case yaml.MappingNode:
var tmp struct {
Group *OutputGroup
GitLab *OutputGitLab `yaml:"gitlab"`
Group *OutputGroup
}
if err := node.Decode(&tmp); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}
switch {
case tmp.Group != nil && tmp.GitLab != nil:
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`output style cannot set both "group" and "gitlab"`)
case tmp.Group != nil:
*s = Output{
Name: "group",
Group: *tmp.Group,
}
return nil
case tmp.GitLab != nil:
*s = Output{
Name: "gitlab",
GitLab: *tmp.GitLab,
}
return nil
default:
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`output style must have the "group" or "gitlab" key when in mapping form`)
if tmp.Group == nil {
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`output style must have the "group" key when in mapping form`)
}
*s = Output{
Name: "group",
Group: *tmp.Group,
}
return nil
}
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("output")
@@ -76,9 +63,3 @@ func (g *OutputGroup) IsSet() bool {
}
return g.Begin != "" || g.End != ""
}
// OutputGitLab is the style options specific to the GitLab style.
type OutputGitLab struct {
Collapsed bool
ErrorOnly bool `yaml:"error_only"`
}

View File

@@ -93,8 +93,8 @@ func getScheme(uri string) (string, error) {
return "git", nil
}
if i := strings.Index(uri, "://"); i != -1 {
return uri[:i], nil
if before, _, ok := strings.Cut(uri, "://"); ok {
return before, nil
}
return "", nil

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"os"
"slices"
"sync"
"time"
@@ -285,12 +286,7 @@ func (r *Reader) isTrusted(uri string) bool {
host := parsedURL.Host
// Check against each trusted pattern (exact match including port if provided)
for _, pattern := range r.trustedHosts {
if host == pattern {
return true
}
}
return false
return slices.Contains(r.trustedHosts, host)
}
func (r *Reader) include(ctx context.Context, node Node) error {

View File

@@ -17,7 +17,6 @@ type TaskRC struct {
DisableFuzzy *bool `yaml:"disable-fuzzy"`
Concurrency *int `yaml:"concurrency"`
Interactive *bool `yaml:"interactive"`
OutputCIAuto *bool `yaml:"output-ci-auto"`
Remote Remote `yaml:"remote"`
Failfast bool `yaml:"failfast"`
Experiments map[string]int `yaml:"experiments"`
@@ -70,6 +69,5 @@ func (t *TaskRC) Merge(other *TaskRC) {
t.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy)
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
t.Interactive = cmp.Or(other.Interactive, t.Interactive)
t.OutputCIAuto = cmp.Or(other.OutputCIAuto, t.OutputCIAuto)
t.Failfast = cmp.Or(other.Failfast, t.Failfast)
}

View File

@@ -306,27 +306,4 @@ remote:
assert.Equal(t, &cacheExpiry, base.Remote.CacheExpiry)
assert.Equal(t, []string{"github.com", "gitlab.com"}, base.Remote.TrustedHosts)
})
t.Run("output-ci-auto merge", func(t *testing.T) { //nolint:paralleltest // parent test cannot run in parallel
trueVal := true
falseVal := false
t.Run("other overrides nil base", func(t *testing.T) { //nolint:paralleltest
base := &ast.TaskRC{}
base.Merge(&ast.TaskRC{OutputCIAuto: &trueVal})
assert.Equal(t, &trueVal, base.OutputCIAuto)
})
t.Run("other overrides base", func(t *testing.T) { //nolint:paralleltest
base := &ast.TaskRC{OutputCIAuto: &falseVal}
base.Merge(&ast.TaskRC{OutputCIAuto: &trueVal})
assert.Equal(t, &trueVal, base.OutputCIAuto)
})
t.Run("nil other does not override base", func(t *testing.T) { //nolint:paralleltest
base := &ast.TaskRC{OutputCIAuto: &trueVal}
base.Merge(&ast.TaskRC{})
assert.Equal(t, &trueVal, base.OutputCIAuto)
})
})
}

View File

@@ -0,0 +1,138 @@
export interface Adopter {
name: string;
url: string;
img: string;
description: string;
}
export const adopters: Adopter[] = [
// Big brand names (first three double as the "featured" showcase)
{
name: 'Docker',
url: 'https://github.com/docker/mcp-registry',
img: 'https://github.com/docker.png',
description:
'The industry-standard container platform uses Task in mcp-registry, the official registry for Docker Model Context Protocol servers.'
},
{
name: 'Vercel',
url: 'https://github.com/vercel/terraform-provider-vercel',
img: 'https://github.com/vercel.png',
description:
'The team behind Next.js and the leading frontend cloud platform uses Task to run and release the official Vercel Terraform provider.'
},
{
name: 'HashiCorp',
url: 'https://github.com/hashicorp/terraform-aws-terraform-enterprise-hvd',
img: 'https://github.com/hashicorp.png',
description:
'HashiCorp ships Task across its Validated Design modules for Terraform, Vault, Consul, Nomad, and Boundary on AWS, Azure, and GCP.'
},
// Other big brands
{
name: 'Microsoft',
url: 'https://github.com/Azure/Azure-Sentinel',
img: 'https://github.com/microsoft.png',
description:
'Azure Sentinel, Microsofts cloud-native SIEM used by enterprises worldwide, relies on Task to orchestrate its repository automation.'
},
{
name: 'Google Cloud',
url: 'https://github.com/GoogleCloudPlatform/deploystack',
img: 'https://github.com/GoogleCloudPlatform.png',
description:
'DeployStack, Google Clouds one-click Terraform deployment tool, automates its workflows with Task.'
},
{
name: 'AWS',
url: 'https://github.com/aws-samples/appmod-blueprints',
img: 'https://github.com/aws-samples.png',
description:
'The AWS Samples AppMod Blueprints reference platform uses Task to orchestrate its demo environments.'
},
{
name: 'Anthropic',
url: 'https://github.com/anthropics/buffa',
img: 'https://github.com/anthropics.png',
description:
'Anthropics Rust protobuf implementation, buffa, uses Task for its build and release tooling.'
},
{
name: 'MongoDB',
url: 'https://github.com/mongodb/mongo-go-driver',
img: 'https://github.com/mongodb.png',
description:
'The official Go driver for MongoDB uses Task to orchestrate its build, lint, formatting, and full test suite across every commit.'
},
{
name: 'Redpanda',
url: 'https://github.com/redpanda-data/connect',
img: 'https://github.com/redpanda-data.png',
description:
'Redpanda Connect, the stream processor formerly known as Benthos, uses Task to orchestrate builds, Docker images, test suites, and its GitHub release pipeline.'
},
// Notable open source projects
{
name: 'Flet',
url: 'https://github.com/flet-dev/flet',
img: 'https://github.com/flet-dev.png',
description:
'Build realtime web, mobile and desktop apps in Python, with no frontend experience required.'
},
{
name: 'GoReleaser',
url: 'https://github.com/goreleaser/goreleaser',
img: 'https://github.com/goreleaser.png',
description:
'Release engineering, simplified. GoReleaser is the de-facto release automation tool for Go projects.'
},
{
name: 'Arduino CLI',
url: 'https://github.com/arduino/arduino-cli',
img: 'https://github.com/arduino.png',
description:
'The official Arduino command-line tool. Task powers the entire Arduino developer tooling stack across 70+ repositories.'
},
{
name: 'FerretDB',
url: 'https://github.com/FerretDB/FerretDB',
img: 'https://github.com/FerretDB.png',
description:
'A truly open-source MongoDB alternative built on PostgreSQL, with Task driving every build and release step.'
},
{
name: 'Tyk',
url: 'https://github.com/TykTechnologies/tyk',
img: 'https://github.com/TykTechnologies.png',
description:
'Open source API gateway supporting REST, GraphQL, TCP and gRPC, automated end-to-end with Task.'
},
{
name: 'Charmbracelet',
url: 'https://github.com/charmbracelet/glamour',
img: 'https://github.com/charmbracelet.png',
description:
'The team behind Bubble Tea uses Task to build Glamour, the stylesheet-based markdown renderer for CLI apps.'
},
{
name: 'Outline',
url: 'https://github.com/OutlineFoundation/outline-server',
img: 'https://github.com/OutlineFoundation.png',
description:
'Outline, the open-source proxy server originally built by Jigsaw (Google), uses Task for its build pipeline.'
},
{
name: 'werf',
url: 'https://github.com/werf/werf',
img: 'https://github.com/werf.png',
description:
'werf, the CNCF-hosted CI/CD tool for shipping software to Kubernetes, uses Task as its build and development entry point.'
},
{
name: 'Gobuster',
url: 'https://github.com/OJ/gobuster',
img: 'https://github.com/OJ.png',
description:
'The ubiquitous directory, DNS and virtual-host brute-forcing tool trusted by pen testers worldwide runs its entire build through Task.'
}
];

View File

@@ -0,0 +1,629 @@
<script setup lang="ts">
import { adopters } from '../adopters';
const featured = adopters.slice(0, 3);
const rest = adopters.slice(3);
const pad = (n: number) => String(n).padStart(2, '0');
const githubPath = (url: string) =>
url.replace(/^https?:\/\/github\.com\//, '').replace(/\/$/, '');
</script>
<template>
<article class="adopters-page">
<header class="hero">
<p class="kicker">
<span class="slashes">//</span>
Who builds with Task
</p>
<h1 class="title">
Trusted by teams shipping<br />
production software.
</h1>
<p class="lead">
Thousands of open source projects use Task as their build and release
orchestrator, from hyperscaler platforms and enterprise security tools
to CLI utilities downloaded millions of times. Below are a few
organizations whose public repositories ship a
<code>Taskfile.yml</code>. Every entry links to real, production code
you can inspect yourself.
</p>
</header>
<section class="featured" aria-labelledby="featured-heading">
<h2 id="featured-heading" class="section-title">
<span class="slashes">//</span>
Featured adopters
</h2>
<div class="featured-grid">
<a
v-for="item in featured"
:key="item.name"
:href="item.url"
target="_blank"
rel="noopener"
class="featured-card"
:aria-label="`${item.name} on GitHub`"
>
<span class="corner tl" aria-hidden="true"></span>
<span class="corner tr" aria-hidden="true"></span>
<span class="corner bl" aria-hidden="true"></span>
<span class="corner br" aria-hidden="true"></span>
<img
:src="item.img"
:alt="`${item.name} logo`"
class="featured-logo"
loading="lazy"
decoding="async"
width="64"
height="64"
/>
<h3 class="featured-name">{{ item.name }}</h3>
<p class="featured-desc">{{ item.description }}</p>
<span class="featured-cta">
<span class="cta-label">View Taskfile on GitHub</span>
<span class="cta-arrow" aria-hidden="true">&rarr;</span>
</span>
</a>
</div>
</section>
<section class="grid-section" aria-labelledby="grid-heading">
<h2 id="grid-heading" class="section-title">
<span class="slashes">//</span>
More projects using Task
</h2>
<div class="grid">
<a
v-for="(item, i) in rest"
:key="item.name"
:href="item.url"
target="_blank"
rel="noopener"
class="card"
:aria-label="`${item.name} on GitHub`"
>
<div class="card-head">
<img
:src="item.img"
:alt="`${item.name} logo`"
class="card-logo"
loading="lazy"
decoding="async"
width="44"
height="44"
/>
<span class="card-index"
>N&deg; {{ pad(i + featured.length + 1) }}</span
>
</div>
<h3 class="card-name">{{ item.name }}</h3>
<p class="card-desc">{{ item.description }}</p>
<div class="card-foot">
<span class="card-path">{{ githubPath(item.url) }}</span>
<span class="card-arrow" aria-hidden="true">&rarr;</span>
</div>
</a>
</div>
</section>
<section class="faq" aria-labelledby="why-heading">
<h2 id="why-heading" class="section-title">
<span class="slashes">//</span>
Why Task?
</h2>
<dl class="faq-list">
<div class="faq-item">
<dt>Is Task production-ready?</dt>
<dd>
Yes. Task ships as a single static binary, has been in wide
production use since 2018, and powers the release workflows of
projects with millions of downloads including Arduino CLI,
GoReleaser, FerretDB, and Gogs.
</dd>
</div>
<div class="faq-item">
<dt>Who uses Task in enterprise?</dt>
<dd>
Docker, Vercel, HashiCorp, Microsoft (Azure Sentinel), Google Cloud,
AWS, and Anthropic are among the organizations that ship code with a
<code>Taskfile.yml</code>. Task is also embedded end-to-end in
Arduinos developer tooling stack across more than 70 repositories.
</dd>
</div>
<div class="faq-item">
<dt>How is Task different from Make?</dt>
<dd>
Task uses plain YAML instead of Makes tab-sensitive syntax, runs
identically on Linux, macOS, and Windows, and provides built-in
caching based on file fingerprints. It also comes with an
<a href="/docs/integrations"
>ecosystem of editor and CI integrations</a
>
that Make lacks by default.
</dd>
</div>
<div class="faq-item">
<dt>Where can I find real-world Taskfile examples?</dt>
<dd>
Every project above links directly to a public repository containing
a production <code>Taskfile.yml</code>. Browsing those is the
fastest way to see Task used in real codebases at different scales.
</dd>
</div>
</dl>
</section>
<aside class="submit-cta">
<div class="submit-body">
<p class="submit-kicker">
<span class="slashes">//</span>
Using Task in your project?
</p>
<p class="submit-text">
Open a pull request on
<a
href="https://github.com/go-task/task/blob/main/website/.vitepress/adopters.ts"
target="_blank"
rel="noopener"
><code>.vitepress/adopters.ts</code></a
>
to get featured here.
</p>
</div>
</aside>
</article>
</template>
<style scoped>
.adopters-page {
max-width: 1152px;
margin: 0 auto;
padding: 0 24px 6rem;
}
.slashes {
color: var(--vp-c-brand-1);
margin-right: 0.4em;
}
.section-title {
font-family: var(--vp-font-family-mono);
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--vp-c-text-2);
text-transform: uppercase;
margin: 0 0 1.5rem;
}
/* ---------- Hero ---------- */
.hero {
padding: 3.5rem 0 4rem;
max-width: 48rem;
}
.kicker {
font-family: var(--vp-font-family-mono);
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--vp-c-text-2);
text-transform: uppercase;
margin: 0 0 1.25rem;
}
.title {
font-size: clamp(2.25rem, 5vw, 3.5rem);
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1.05;
margin: 0 0 1.75rem;
color: var(--vp-c-text-1);
}
.lead {
font-size: 1.1rem;
line-height: 1.65;
color: var(--vp-c-text-2);
margin: 0;
}
.lead code {
font-family: var(--vp-font-family-mono);
font-size: 0.9em;
padding: 0.1rem 0.4rem;
border-radius: 4px;
background: var(--vp-c-bg-alt);
color: var(--vp-c-brand-1);
}
/* ---------- Featured ---------- */
.featured {
margin-bottom: 4rem;
}
.featured-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.25rem;
}
.featured-card {
position: relative;
display: flex;
flex-direction: column;
gap: 1rem;
padding: 2rem 1.75rem;
border: 1px solid var(--vp-c-divider);
border-radius: 16px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
text-decoration: none !important;
transition:
border-color 0.3s ease,
transform 0.3s ease,
box-shadow 0.3s ease;
isolation: isolate;
overflow: hidden;
}
.featured-card::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(
600px circle at 50% 0%,
color-mix(in srgb, var(--vp-c-brand-1) 14%, transparent),
transparent 50%
);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
z-index: -1;
}
.featured-card:hover {
border-color: color-mix(
in srgb,
var(--vp-c-brand-1) 50%,
var(--vp-c-divider)
);
transform: translateY(-3px);
box-shadow: 0 18px 48px -28px
color-mix(in srgb, var(--vp-c-brand-1) 50%, transparent);
}
.featured-card:hover::before {
opacity: 1;
}
.featured-logo {
width: 56px;
height: 56px;
border-radius: 12px;
object-fit: cover;
background: #fff;
}
.featured-name {
font-size: 1.4rem;
font-weight: 700;
letter-spacing: -0.02em;
margin: 0;
line-height: 1.2;
}
.featured-desc {
font-size: 0.95rem;
line-height: 1.55;
color: var(--vp-c-text-2);
margin: 0;
flex: 1;
}
.featured-cta {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-family: var(--vp-font-family-mono);
font-size: 0.78rem;
color: var(--vp-c-text-2);
transition: color 0.3s ease;
margin-top: 0.5rem;
}
.featured-card:hover .featured-cta {
color: var(--vp-c-brand-1);
}
.cta-arrow {
display: inline-block;
transition: transform 0.3s ease;
}
.featured-card:hover .cta-arrow {
transform: translateX(4px);
}
/* Crosshair corner marks (shared with grid cards) */
.corner {
position: absolute;
width: 10px;
height: 10px;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.corner::before,
.corner::after {
content: '';
position: absolute;
background: var(--vp-c-brand-1);
}
.corner::before {
width: 10px;
height: 1px;
top: 50%;
left: 0;
}
.corner::after {
width: 1px;
height: 10px;
top: 0;
left: 50%;
}
.corner.tl {
top: 8px;
left: 8px;
}
.corner.tr {
top: 8px;
right: 8px;
}
.corner.bl {
bottom: 8px;
left: 8px;
}
.corner.br {
bottom: 8px;
right: 8px;
}
.featured-card:hover .corner {
opacity: 0.8;
}
/* ---------- Grid ---------- */
.grid-section {
margin-bottom: 4rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 1rem;
}
.card {
position: relative;
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.25rem 1.25rem 1rem;
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
text-decoration: none !important;
transition:
border-color 0.25s ease,
transform 0.25s ease,
box-shadow 0.25s ease;
}
.card:hover {
border-color: color-mix(
in srgb,
var(--vp-c-brand-1) 45%,
var(--vp-c-divider)
);
transform: translateY(-2px);
box-shadow: 0 10px 28px -22px
color-mix(in srgb, var(--vp-c-brand-1) 40%, transparent);
}
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.card-logo {
width: 40px;
height: 40px;
border-radius: 8px;
object-fit: cover;
background: #fff;
flex-shrink: 0;
}
.card-index {
font-family: var(--vp-font-family-mono);
font-size: 0.7rem;
letter-spacing: 0.04em;
color: var(--vp-c-text-3);
font-variant-numeric: tabular-nums;
transition: color 0.25s ease;
}
.card:hover .card-index {
color: var(--vp-c-brand-1);
}
.card-name {
font-size: 1.05rem;
font-weight: 600;
letter-spacing: -0.01em;
line-height: 1.25;
margin: 0;
}
.card-desc {
font-size: 0.85rem;
line-height: 1.5;
color: var(--vp-c-text-2);
margin: 0;
flex: 1;
}
.card-foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
padding-top: 0.75rem;
border-top: 1px dashed var(--vp-c-divider);
}
.card-path {
font-family: var(--vp-font-family-mono);
font-size: 0.72rem;
color: var(--vp-c-text-3);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
flex: 1;
}
.card-arrow {
font-family: var(--vp-font-family-mono);
font-size: 0.8rem;
color: var(--vp-c-text-2);
transition:
transform 0.25s ease,
color 0.25s ease;
}
.card:hover .card-arrow {
transform: translateX(3px);
color: var(--vp-c-brand-1);
}
/* ---------- FAQ ---------- */
.faq {
margin-bottom: 3.5rem;
max-width: 48rem;
}
.faq-list {
margin: 0;
}
.faq-item {
padding: 1.25rem 0;
border-bottom: 1px solid var(--vp-c-divider);
}
.faq-item:first-of-type {
border-top: 1px solid var(--vp-c-divider);
}
.faq-item dt {
font-size: 1.05rem;
font-weight: 600;
color: var(--vp-c-text-1);
margin: 0 0 0.5rem;
letter-spacing: -0.01em;
}
.faq-item dd {
font-size: 0.95rem;
line-height: 1.65;
color: var(--vp-c-text-2);
margin: 0;
}
.faq-item dd a {
color: var(--vp-c-brand-1);
font-weight: 500;
}
.faq-item dd code {
font-family: var(--vp-font-family-mono);
font-size: 0.88em;
padding: 0.08rem 0.35rem;
border-radius: 4px;
background: var(--vp-c-bg-alt);
color: var(--vp-c-brand-1);
}
/* ---------- Submit CTA ---------- */
.submit-cta {
padding: 1.75rem 2rem;
border: 1px solid var(--vp-c-divider);
border-radius: 14px;
background:
linear-gradient(
135deg,
color-mix(in srgb, var(--vp-c-brand-1) 6%, transparent) 0%,
transparent 60%
),
var(--vp-c-bg-soft);
position: relative;
overflow: hidden;
}
.submit-cta::before {
content: '';
position: absolute;
top: -1px;
left: 2rem;
right: 2rem;
height: 1px;
background: linear-gradient(
90deg,
transparent,
var(--vp-c-brand-1),
transparent
);
opacity: 0.5;
}
.submit-kicker {
font-family: var(--vp-font-family-mono);
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--vp-c-text-2);
text-transform: uppercase;
margin: 0 0 0.5rem;
}
.submit-text {
font-size: 0.95rem;
line-height: 1.55;
color: var(--vp-c-text-2);
margin: 0;
}
.submit-text a {
color: var(--vp-c-brand-1);
font-weight: 500;
}
.submit-text code {
font-family: var(--vp-font-family-mono);
font-size: 0.88em;
padding: 0.08rem 0.4rem;
border-radius: 4px;
background: var(--vp-c-bg-alt);
}
</style>

View File

@@ -0,0 +1,227 @@
<script setup lang="ts">
import { adopters } from '../adopters';
const loop = [...adopters, ...adopters];
</script>
<template>
<section class="adopters-carousel" aria-labelledby="adopters-heading">
<h2 id="adopters-heading" class="label">
<span class="slashes">//</span>
Trusted by open source projects
</h2>
<p class="subline">
Adopted by <strong>Docker</strong>, <strong>Vercel</strong>,
<strong>HashiCorp</strong>, <strong>Microsoft</strong>,
<strong>Google Cloud</strong>, <strong>AWS</strong>,
<strong>Anthropic</strong> and more.
<a class="see-all" href="/adopters">
See all adopters
<span class="see-all-arrow" aria-hidden="true">&rarr;</span>
</a>
</p>
<div class="viewport">
<div class="track">
<a
v-for="(item, i) in loop"
:key="`${item.name}-${i}`"
:href="item.url"
target="_blank"
rel="noopener"
class="chip"
:aria-label="`${item.name} on GitHub`"
>
<img
:src="item.img"
:alt="`${item.name} logo`"
class="logo"
loading="lazy"
decoding="async"
width="28"
height="28"
/>
<span class="name">{{ item.name }}</span>
<span class="chevron" aria-hidden="true">&rarr;</span>
</a>
</div>
</div>
</section>
</template>
<style scoped>
.adopters-carousel {
max-width: 1248px;
margin: 5rem auto 2rem;
padding: 0 24px;
}
.label {
font-family: var(--vp-font-family-mono);
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--vp-c-text-2);
text-transform: uppercase;
text-align: center;
margin: 0 0 0.75rem;
}
.slashes {
color: var(--vp-c-brand-1);
margin-right: 0.4em;
}
.subline {
text-align: center;
font-size: 0.95rem;
color: var(--vp-c-text-2);
max-width: 640px;
margin: 0 auto 2rem;
line-height: 1.5;
}
.subline strong {
color: var(--vp-c-text-1);
font-weight: 600;
}
.see-all {
display: inline-block;
margin-left: 0.4em;
color: var(--vp-c-brand-1);
font-weight: 500;
white-space: nowrap;
text-decoration: none !important;
}
.see-all:hover {
text-decoration: underline !important;
}
.see-all-arrow {
display: inline-block;
transition: transform 0.25s ease;
}
.see-all:hover .see-all-arrow {
transform: translateX(3px);
}
.viewport {
overflow: hidden;
-webkit-mask-image: linear-gradient(
90deg,
transparent 0,
#000 6%,
#000 94%,
transparent 100%
);
mask-image: linear-gradient(
90deg,
transparent 0,
#000 6%,
#000 94%,
transparent 100%
);
}
.track {
display: flex;
gap: 0.875rem;
width: max-content;
animation: scroll 55s linear infinite;
padding: 6px 0;
}
.track:hover {
animation-play-state: paused;
}
.chip {
display: inline-flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 1.125rem 0.625rem 0.625rem;
border: 1px solid var(--vp-c-divider);
border-radius: 999px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
text-decoration: none !important;
white-space: nowrap;
transition:
border-color 0.25s ease,
background 0.25s ease,
transform 0.25s ease,
box-shadow 0.25s ease;
}
.chip:hover {
border-color: var(--vp-c-brand-1);
background: var(--vp-c-bg);
transform: translateY(-2px);
box-shadow: 0 6px 20px -10px
color-mix(in srgb, var(--vp-c-brand-1) 60%, transparent);
}
.logo {
width: 28px;
height: 28px;
border-radius: 6px;
object-fit: cover;
flex-shrink: 0;
background: #fff;
}
.name {
font-size: 0.9rem;
font-weight: 500;
letter-spacing: -0.005em;
}
.chevron {
font-family: var(--vp-font-family-mono);
font-size: 0.85rem;
color: var(--vp-c-text-3);
opacity: 0;
transform: translateX(-4px);
transition:
opacity 0.25s ease,
transform 0.25s ease,
color 0.25s ease;
margin-left: -0.25rem;
}
.chip:hover .chevron {
opacity: 1;
transform: translateX(0);
color: var(--vp-c-brand-1);
}
@keyframes scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(calc(-50% - 0.4375rem));
}
}
@media (max-width: 640px) {
.adopters-carousel {
margin-top: 3.5rem;
}
}
@media (prefers-reduced-motion: reduce) {
.track {
animation: none;
flex-wrap: wrap;
justify-content: center;
width: 100%;
}
.chip:hover {
transform: none;
}
}
</style>

View File

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

View File

@@ -9,6 +9,7 @@ import {
localIconLoader
} from 'vitepress-plugin-group-icons';
import { team } from './team.ts';
import { adopters } from './adopters.ts';
import { taskDescription, taskName, ogUrl, ogImage } from './meta.ts';
import { fileURLToPath, URL } from 'node:url';
import llmstxt from 'vitepress-plugin-llms';
@@ -107,6 +108,112 @@ export default defineConfig({
head.push(['meta', { name: 'robots', content: 'noindex, nofollow' }])
}
// Structured data for the adopters carousel on the homepage: an ItemList
// of Organization entities so search engines can surface Task's adopters
// directly in rich results.
if (isHome) {
head.push([
'script',
{ type: 'application/ld+json' },
JSON.stringify({
'@context': 'https://schema.org',
'@type': 'ItemList',
name: 'Organizations and projects using Task',
itemListOrder: 'https://schema.org/ItemListUnordered',
numberOfItems: adopters.length,
itemListElement: adopters.map((a, i) => ({
'@type': 'ListItem',
position: i + 1,
item: {
'@type': 'Organization',
name: a.name,
url: a.url,
logo: a.img,
sameAs: [a.url]
}
}))
})
])
}
// On the /adopters page, emit CollectionPage + ItemList (richer than the
// homepage snippet because it targets this specific URL) and FAQPage for
// the question block at the bottom of the page. Kept in sync by hand with
// components/Adopters.vue.
if (pageData.relativePath === 'adopters.md') {
head.push([
'script',
{ type: 'application/ld+json' },
JSON.stringify({
'@context': 'https://schema.org',
'@type': 'CollectionPage',
name: 'Who uses Task',
url: 'https://taskfile.dev/adopters',
description:
'Organizations and open source projects that use Task as their build and release runner.',
mainEntity: {
'@type': 'ItemList',
numberOfItems: adopters.length,
itemListElement: adopters.map((a, i) => ({
'@type': 'ListItem',
position: i + 1,
item: {
'@type': 'Organization',
name: a.name,
url: a.url,
logo: a.img,
description: a.description,
sameAs: [a.url]
}
}))
}
})
])
head.push([
'script',
{ type: 'application/ld+json' },
JSON.stringify({
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: [
{
'@type': 'Question',
name: 'Is Task production-ready?',
acceptedAnswer: {
'@type': 'Answer',
text: 'Yes. Task ships as a single static binary, has been in wide production use since 2018, and powers the release workflows of projects with millions of downloads including Arduino CLI, GoReleaser, FerretDB, and Gogs.'
}
},
{
'@type': 'Question',
name: 'Who uses Task in enterprise?',
acceptedAnswer: {
'@type': 'Answer',
text: 'Docker, Vercel, HashiCorp, Microsoft (Azure Sentinel), Google Cloud, AWS, and Anthropic are among the organizations that ship code with a Taskfile.yml. Task is also embedded end-to-end in Arduinos developer tooling stack across more than 70 repositories.'
}
},
{
'@type': 'Question',
name: 'How is Task different from Make?',
acceptedAnswer: {
'@type': 'Answer',
text: 'Task uses plain YAML instead of Makes tab-sensitive syntax, runs identically on Linux, macOS, and Windows, and provides built-in caching based on file fingerprints. It also comes with an ecosystem of editor and CI integrations that Make lacks by default.'
}
},
{
'@type': 'Question',
name: 'Where can I find real-world Taskfile examples?',
acceptedAnswer: {
'@type': 'Answer',
text: 'Every adopter listed above links directly to a public repository containing a production Taskfile.yml. Browsing those is the fastest way to see Task used in real codebases at different scales.'
}
}
]
})
])
}
return head
},
srcDir: 'src',
@@ -363,6 +470,10 @@ export default defineConfig({
{
text: 'Incident Response Plan',
link: '/docs/security/incident-response-plan'
},
{
text: 'Threat Model',
link: '/docs/security/threat-model'
}
]
},
@@ -377,7 +488,8 @@ export default defineConfig({
],
// Hacky to disable sidebar for these pages
'/donate': [],
'/team': []
'/team': [],
'/adopters': []
},
socialLinks: [

View File

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

Before

Width:  |  Height:  |  Size: 681 B

After

Width:  |  Height:  |  Size: 676 B

View File

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

Before

Width:  |  Height:  |  Size: 350 B

After

Width:  |  Height:  |  Size: 345 B

View File

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

Before

Width:  |  Height:  |  Size: 796 B

After

Width:  |  Height:  |  Size: 791 B

View File

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

Before

Width:  |  Height:  |  Size: 405 B

After

Width:  |  Height:  |  Size: 400 B

View File

@@ -5,6 +5,7 @@ import HomePage from '../components/HomePage.vue';
import AuthorCard from '../components/AuthorCard.vue';
import BlogPost from '../components/BlogPost.vue';
import Version from '../components/Version.vue';
import Adopters from '../components/Adopters.vue';
import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client';
import { h } from 'vue';
import 'virtual:group-icons.css';
@@ -21,6 +22,7 @@ export default {
app.component('AuthorCard', AuthorCard);
app.component('BlogPost', BlogPost);
app.component('Version', Version);
app.component('Adopters', Adopters);
app.component('CopyOrDownloadAsMarkdownButtons', CopyOrDownloadAsMarkdownButtons);
enhanceAppWithTabs(app);
}

View File

@@ -15,13 +15,13 @@
"devDependencies": {
"@types/markdown-it": "^14.1.2",
"@types/node": "^24.1.0",
"netlify-cli": "^24.0.0",
"netlify-cli": "^26.0.0",
"prettier": "^3.6.2",
"vitepress": "^1.6.3",
"vitepress-plugin-group-icons": "^1.6.1",
"vitepress-plugin-tabs": "^0.8.0",
"vitepress-plugin-tabs": "^0.9.0",
"vitepress-plugin-llms": "^1.9.1",
"vue": "^3.5.18"
},
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
"packageManager": "pnpm@11.5.0+sha512.dbfcc4f81cf48597afd4bc391ffdf12c11f1a9fb83a395bfa6b0a2d9cc2fd8ffebafdb1ccbd529632153f793904c2615b7f09fe1a345473fd1c35845172a8eb1"
}

2041
website/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
allowBuilds:
'@parcel/watcher': true
esbuild: true
netlify-cli: true
sharp: true
unix-dgram: true

11
website/src/adopters.md Normal file
View File

@@ -0,0 +1,11 @@
---
title: Who uses Task
description:
Organizations and open source projects that use Task as their build and
release runner, including Docker, Microsoft, HashiCorp, Vercel, Google Cloud,
AWS, Anthropic, Arduino, GoReleaser, and more.
layout: page
sidebar: false
---
<Adopters />

View File

@@ -8,6 +8,21 @@ editLink: false
::: v-pre
## v3.51.1 - 2026-05-16
- A significant performance boost was achieved for large Taskfiles (monorepos)
by skipping templating altogether when the string is static (#2820 by @romnn).
- Added `absPath` template function that resolves a path to its absolute form,
cleaning `..` and `.` components (#2681, #2788 by @mateenanjum).
- Added `joinEnv` function to join paths based on your oprating system: `;` for
Windows and `:` elsewhere, and `joinUrl` to join URL paths. Also, added two
new special variables: `FILE_PATH_SEPARATOR` which returns `\` on Windows
and `/` elsewhere, and `PATH_LIST_SEPARATOR` which returns `;` on Windows and
`:` elsewhere (#2406, #2408 by @solvingj).
- Update the shell interpreter with a regression fix (#2812, #2832 by
@andreynering).
- Fix potential panic with the shell interpreter (#2810 by @trulede).
## v3.50.0 - 2026-04-13
- Added `enum.ref` support in `requires`: enum constraints can now reference

View File

@@ -65,6 +65,19 @@ a human. Always remind contributors to disclose AI usage in their submissions.
## 1. Setup
The easiest way to install everything you need to work on Task is
[mise][mise]. From the repository root, run:
```shell
mise install
```
This installs the pinned versions of Go, Node.js, pnpm and the dev tools
(`golangci-lint`, `mockery`, `gotestsum`, `goreleaser` and `gorelease`)
declared in the `mise.toml` file.
If you'd rather install things manually, you'll need:
- **Go** - Task is written in [Go][go]. We always support the latest two major
Go versions, so make sure your version is recent enough.
- **Node.js** - [Node.js][nodejs] is used to host Task's documentation server
@@ -194,6 +207,7 @@ If you have questions, feel free to ask them in the `#help` forum channel on our
[prettier]: https://prettier.io
[nodejs]: https://nodejs.org/en/
[pnpm]: https://pnpm.io/
[mise]: https://mise.jdx.dev
[vitepress]: https://vitepress.dev
[json-schema]:
https://github.com/go-task/task/blob/main/website/src/public/schema.json

View File

@@ -2426,13 +2426,12 @@ the shell in real-time. This is good for having live feedback for logging
printed by commands, but the output can become messy if you have multiple
commands running simultaneously and printing lots of stuff.
To make this more customizable, there are currently four different output
To make this more customizable, there are currently three different output
options you can choose:
- `interleaved` (default)
- `group`
- `prefixed`
- `gitlab`
To choose another one, just set it to root in the Taskfile:
@@ -2536,44 +2535,6 @@ $ task default
[print-baz] baz
```
The `gitlab` output wraps each task's output in
[GitLab CI collapsible section markers](https://docs.gitlab.com/ci/jobs/job_logs/#create-custom-collapsible-sections).
Section names are generated automatically so that start and end markers always
match and stay unique per invocation — even when the same task runs multiple
times in the same job.
```yaml
version: '3'
output: gitlab
```
Two options are available:
- `collapsed`: maps to GitLab's native
[`[collapsed=true]`](https://docs.gitlab.com/ci/jobs/job_logs/#create-custom-collapsible-sections)
option, which tells GitLab to fold the section by default in the UI.
- `error_only`: a Task-level option (same as in the [`group`](#output-syntax)
style) that swallows the command output — markers included — for tasks that
exit with a zero status code.
```yaml
version: '3'
output:
gitlab:
collapsed: true
error_only: true
```
::: tip
Rather than hard-coding `output: gitlab` in your Taskfile (which also affects
local development), consider using [`output-ci-auto`](#automatic-ci-output) so
the mode is only activated in CI.
:::
::: tip
The `output` option can also be specified by the `--output` or `-o` flags.
@@ -2602,28 +2563,6 @@ summary, making it easier to spot failures without scrolling through logs.
This feature requires no configuration and works automatically.
### Automatic CI output
When `output-ci-auto: true` is set in a [`.taskrc.yml`](./taskrc.md) file, Task
will automatically select a CI-aware [output style](#output-syntax) based on
the environment it is running in, but only when no output style is configured
explicitly (via the Taskfile, `--output`, or `TASK_X_OUTPUT`).
Currently supported:
| Environment variable | Output style selected |
| -------------------- | --------------------- |
| `GITLAB_CI=true` | `gitlab` |
This lets you keep your Taskfile neutral — local developers get the default
`interleaved` output, while CI runs get their matching CI-flavored output
without any per-job configuration.
```yaml
# .taskrc.yml
output-ci-auto: true
```
## Interactive CLI application
When running interactive CLI applications inside Task they can sometimes behave

View File

@@ -224,8 +224,7 @@ task backup --global
#### `-o, --output <mode>`
Set output style. Available modes: `interleaved`, `group`, `prefixed`,
`gitlab`.
Set output style. Available modes: `interleaved`, `group`, `prefixed`.
```bash
task test --output group

View File

@@ -166,21 +166,6 @@ failfast: true
interactive: true
```
### `output-ci-auto`
- **Type**: `boolean`
- **Default**: `false`
- **Description**: Automatically select a CI-aware
[output style](../guide.md#output-syntax) when a supported CI environment
is detected and no output style is explicitly configured (via the Taskfile
or `--output`). Currently maps `GITLAB_CI=true` to the `gitlab` output
style.
- **Environment variable**: [`TASK_OUTPUT_CI_AUTO`](./environment.md#task-output-ci-auto)
```yaml
output-ci-auto: true
```
## Example Configuration
Here's a complete example of a `.taskrc.yml` file with all available options:

View File

@@ -81,16 +81,6 @@ variables. The priority order is: CLI flags > environment variables > config fil
- **Default**: `false`
- **Description**: Prompt for missing required variables
### `TASK_OUTPUT_CI_AUTO`
- **Type**: `boolean` (`true`, `false`, `1`, `0`)
- **Default**: `false`
- **Description**: Automatically select a CI-aware output style when a
supported CI environment is detected and no output style is explicitly
configured. See [output syntax](../guide.md#output-syntax) and
[automatic CI output](../guide.md#automatic-ci-output).
- **Config equivalent**: [`output-ci-auto`](./config.md#output-ci-auto)
### `TASK_TEMP_DIR`
Defines the location of Task's temporary directory which is used for storing

View File

@@ -29,7 +29,7 @@ version: '3'
- **Type**: `string` or `object`
- **Default**: `interleaved`
- **Options**: `interleaved`, `group`, `prefixed`, `gitlab`
- **Options**: `interleaved`, `group`, `prefixed`
- **Description**: Controls how task output is displayed
```yaml

View File

@@ -249,6 +249,32 @@ tasks:
- echo "Working {{.USER_WORKING_DIR}}"
```
#### `FILE_PATH_SEPARATOR`
- **Type**: `string`
- **Description**: OS-specific path separator: Windows = `\`, others = `/`
::: info
> See `joinPath` in [Path Functions](#path-functions) for joining filesystem paths for use with
> file system operations.
:::
### Environment Variables
#### `PATH_LIST_SEPARATOR`
- **Type**: `string`
- **Description**: OS-specific path separator for environment variables: Windows = `;`, others = `:`
::: info
> See `joinEnv` in [Environment Variable Functions](#environment-variable-functions) for joining
> paths for use in environment variables.
:::
### Status
#### `CHECKSUM`
@@ -597,9 +623,9 @@ tasks:
tasks:
platform:
cmds:
- echo "OS {{OS}}" # linux, darwin, windows, etc.
- echo "Architecture {{ARCH}}" # amd64, arm64, etc.
- echo "CPU cores {{numCPU}}" # Number of CPU cores
- echo "OS {{OS}}" # linux, darwin, windows, etc.
- echo "Architecture {{ARCH}}" # amd64, arm64, etc.
- echo "CPU cores {{numCPU}}" # Number of CPU cores
- echo "Building for {{OS}}/{{ARCH}}"
```
@@ -613,11 +639,52 @@ tasks:
OUTPUT_DIR: 'dist'
BINARY_NAME: 'myapp'
cmds:
- echo "{{.WIN_PATH | toSlash}}" # Convert to forward slashes
- echo "{{.WIN_PATH | fromSlash}}" # Convert to OS-specific slashes
- echo "{{joinPath .OUTPUT_DIR .BINARY_NAME}}" # Join path elements
- echo "Relative {{relPath .ROOT_DIR .TASKFILE_DIR}}" # Get relative path
- echo '{{absPath "../sibling"}}' # Resolve to an absolute path
- echo "{{.WIN_PATH | toSlash}}" # Convert to forward slashes
- echo "{{.WIN_PATH | fromSlash}}" # Convert to OS-specific slashes
- echo "{{joinPath .OUTPUT_DIR .BINARY_NAME}}" # Join path elements
- echo "Relative {{relPath .ROOT_DIR .TASKFILE_DIR}}" # Get relative path
- echo '{{absPath "../sibling"}}' # Resolve to an absolute path
```
#### Environment Variable Functions
```yaml
tasks:
paths:
vars:
WIN_PATH1: 'C:\Users\Person\bin'
WIN_PATH2: 'C:\Shared\bin'
cmds:
# Join paths for Windows ENV vars:
# C:\Users\Person\bin;C:\Shared\bin
- echo "{{joinEnv .WIN_PATH1 .WIN_PATH2}}"
```
```yaml
tasks:
paths:
vars:
POSIX_PATH1: '/users/person/.local/bin'
POSIX_PATH2: '/usr/bin'
cmds:
# Join paths for POSIX ENV vars:
# /users/person/.local/bin:/usr/bin
- echo "{{joinEnv .POSIX_PATH1 .POSIX_PATH2}}"
```
#### URLs
```yaml
tasks:
paths:
vars:
SERVER: 'http://localhost'
PATH1: 'path1'
PATH2: 'path2'
cmds:
# Join paths for URL:
# http://localhost/path1/path2
- echo "{{joinUrl .SERVER .PATH1 .PATH2}}"
```
### Data Structure Functions

View File

@@ -15,7 +15,8 @@ A member of the team will investigate as soon as possible and we will keep you
updated throughout the process.
You can read more about how we handle security-related issues in our [Incident
Response Plan][irp].
Response Plan][irp] and [Threat Model][tm].
[pvr]: https://github.com/go-task/task/security/advisories/new
[irp]: ./incident-response-plan
[tm]: ./threat-model

View File

@@ -0,0 +1,174 @@
---
title: Threat Model
outline: deep
---
# Threat Model
This document outlines the security threats, assets, and mitigations for the
Task project. It serves as a high-level, public guide and is published as part
of our commitment to transparency.
## Asset Inventory
### Critical Assets
- **Source Code:** The Task CLI, build scripts, and configuration files
(e.g., `Taskfile.yml`, `.goreleaser.yml`).
- **Build Artifacts:** Compiled binaries, packages, and containers distributed
to users.
- **Secrets:** API tokens, signing keys, and repository credentials used in
CI/CD and release pipelines.
- **Release Metadata:** Version numbers, changelogs, and checksums.
- **CI/CD Pipelines & Runners:** GitHub Actions workflows that build, test, and
release the project.
- **Third-party Dependencies:** Go modules and tools used to build and
distribute Task.
- **Website & Documentation:** The taskfile.dev site and installation scripts.
### Asset Locations
- Local developer machines
- GitHub Actions runners
- GitHub Releases
- Public package registries (npm, Homebrew, Winget, Cloudsmith)
- Source control platforms (GitHub)
- Netlify (website hosting)
## Threat Model
### Actors
- **Maintainers & Contributors:** Trusted users with varying levels of
repository access.
- **External Attackers:** Untrusted users seeking to compromise builds,
releases, or user systems.
- **Supply Chain Threats:** Malicious dependencies or compromised third-party
services.
- **CI/CD Systems:** Automated agents that may be exploited if misconfigured.
### Entry Points
- Source code contributions (pull requests, issues)
- Configuration files and build scripts
- CI/CD integration and environment variables
- Third-party dependencies
- Release pipelines and artifact repositories
- Remote Taskfile fetching (HTTP, Git)
- Installation scripts
### Trust Boundaries
- Between the project repository and the CI/CD environment
- Between Task and remote Taskfiles fetched over the network
- Between artifact generation and distribution channels
- Between the Task binary and user-defined shell commands
### Threats
#### Supply Chain Attacks
- Compromised Go dependencies or build tools
- Unauthorized changes to source code or configuration
- Exploitation of third-party CI/CD or package registry services
- Compromised installation scripts or distribution channels
#### Secrets Leakage
- Exposure of tokens, credentials, or signing keys in logs, error messages,
or artifacts
- Hardcoded secrets in code or configuration
- Improper secret management in CI/CD environments
#### Code Execution / Injection
- Malicious code execution via compromised pull requests or dependencies
- Remote code execution vulnerabilities in Task or its dependencies
- **Note:** Task intentionally executes user-defined shell commands as part of
its core functionality. Users are responsible for the commands they define in
their Taskfiles.
#### Unauthorized Access
- Unauthorized users triggering releases or accessing sensitive artifacts
- Insecure permissions on runners, repositories, or artifact stores
- Compromised maintainer accounts
#### Data Integrity & Tampering
- Tampering with build artifacts, changelogs, or metadata
- Compromise of signing keys, leading to malicious releases
- Man-in-the-middle attacks against remote Taskfile fetching
#### Denial of Service
- Abuse of CI/CD resources, bandwidth, or artifact storage
- Overloading automated processes or API endpoints
- Malicious Taskfiles designed to exhaust system resources
## Mitigations
### Supply Chain Security
- Pin dependencies and use trusted sources
- Mandatory code review and CI checks on all incoming pull requests
- Signed commits and release tags
- Enable immutable releases where supported
- Run `govulncheck` on every commit and tag
- Pin GitHub Actions to specific commit SHAs
### Secrets Management
- Secure storage using GitHub Secrets
- Never log or expose secrets in build or release outputs
- Regularly rotate secrets and monitor for suspicious activity
- Use least-privilege tokens scoped to specific repositories
### Secure Code Execution
- Validate and sanitize configuration files and user inputs
- Audit dependencies for vulnerabilities
- HTTP is rejected for remote Taskfiles by default (requires `--insecure` flag)
- TLS certificate verification for remote Git repositories
### Access Control
- Enforce least privilege for CI/CD runners, repositories, and artifact stores
- Require multi-factor authentication for maintainers
- Restrict release triggers to tagged releases only
- Lower permissions of less active maintainers
### Artifact Integrity
- Generate checksums for all release artifacts
- Distribute artifacts via trusted, access-controlled repositories
- Verify signatures and checksums in installation scripts where possible
### Availability Protection
- Implement rate limiting and resource quotas on CI/CD jobs
- Monitor for abnormal activity and automate alerts
- Set timeouts on network operations (e.g., remote Taskfile fetching)
## Residual Risks
- Zero-day vulnerabilities in dependencies, CI/CD systems, or Task itself
- Social engineering attacks targeting maintainers
- Unnoticed supply chain compromises
- Human error in configuration or secret management
- Users fetching malicious remote Taskfiles from untrusted sources
## Security Best Practices
- Regularly update dependencies and build tools
- Monitor security advisories and patch vulnerabilities promptly
- Educate contributors on secure coding and secrets hygiene
- Document security policies and incident response procedures
## References
- [Task Documentation](https://taskfile.dev/)
- [Incident Response Plan](./incident-response-plan)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [Supply Chain Security](https://slsa.dev/)
- [GitHub Security Best Practices](https://docs.github.com/en/code-security)

View File

@@ -1,4 +1 @@
<svg width="2507" height="687" viewBox="0 0 2507 687" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M499.999 325.98V360.535L361.332 498.757H326.665L187.999 360.535V325.98L326.665 187.757H361.332L499.999 325.98ZM343.997 436.159C395.468 436.159 437.193 394.568 437.193 343.263C437.193 291.957 395.468 250.366 343.997 250.366C292.527 250.366 250.802 291.957 250.802 343.263C250.802 394.568 292.527 436.159 343.997 436.159Z" fill="#2A6FE1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1440.66 186.26V433.188L1437.03 436.819H1411.61L1407.98 433.188L1404.35 418.663H1400.72C1389.82 432.461 1372.76 440.45 1353.51 440.45C1302.67 440.45 1273.62 400.506 1273.62 346.037C1273.62 291.567 1302.67 251.623 1353.51 251.623C1371.67 251.623 1389.46 258.886 1400.72 273.411H1404.35V186.26L1407.98 182.628H1437.03L1440.66 186.26ZM1357.14 407.769C1386.19 407.769 1404.35 389.612 1404.35 346.037C1404.35 302.461 1386.19 284.305 1357.14 284.305C1328.09 284.305 1309.93 305.729 1309.93 346.037C1309.93 386.344 1328.09 407.769 1357.14 407.769ZM884.959 346.037C884.959 404.137 917.641 440.45 972.11 440.45C1026.58 440.45 1059.26 404.137 1059.26 346.037C1059.26 287.936 1026.58 251.623 972.11 251.623C917.641 251.623 884.959 287.936 884.959 346.037ZM921.272 346.037C921.272 306.092 939.429 284.305 972.11 284.305C1004.79 284.305 1022.95 306.092 1022.95 346.037C1022.95 385.981 1004.79 407.769 972.11 407.769C939.429 407.769 921.272 385.981 921.272 346.037ZM1240.84 258.886L1237.21 255.254H1208.16L1204.53 258.886V346.037C1204.53 389.612 1186.01 407.769 1160.95 407.769C1131.9 407.769 1124.64 385.981 1124.64 364.193V258.886L1121.01 255.254H1091.96L1088.33 258.886V364.193C1088.33 403.774 1106.48 440.45 1157.32 440.45C1174.75 440.45 1191.45 431.735 1200.9 418.663H1204.53L1208.16 433.188L1211.79 436.819H1237.21L1240.84 433.188V258.886ZM1618.52 385.981C1618.52 353.299 1600.37 338.774 1564.05 331.511C1527.74 324.249 1513.22 320.618 1513.22 302.461C1513.22 287.936 1527.74 284.305 1545.9 284.305C1571.32 284.305 1578.58 295.199 1578.58 306.092L1582.21 309.724H1611.26L1614.89 306.092C1614.89 269.779 1585.84 251.623 1545.9 251.623C1495.06 251.623 1476.9 277.042 1476.9 302.461C1476.9 335.143 1498.69 349.668 1535 356.931C1571.32 364.193 1582.21 367.824 1582.21 385.981C1582.21 400.506 1571.32 407.769 1545.9 407.769C1520.48 407.769 1509.58 396.875 1509.58 378.718L1505.95 375.087H1476.9L1473.27 378.718C1473.27 418.663 1498.69 440.45 1545.9 440.45C1596.74 440.45 1618.52 415.031 1618.52 385.981ZM1843.65 251.623C1894.49 251.623 1905.38 284.305 1905.38 324.249V433.188L1901.75 436.819H1872.7L1869.07 433.188V324.249C1869.07 302.461 1865.44 284.305 1836.39 284.305C1810.97 284.305 1796.44 302.461 1796.44 331.511V433.188L1792.81 436.819H1763.76L1760.13 433.188V324.249C1760.13 302.461 1756.5 284.305 1727.45 284.305C1709.29 284.305 1687.5 295.199 1687.5 331.511V433.188L1683.87 436.819H1654.82L1651.19 433.188V258.886L1654.82 255.254H1680.24L1683.87 258.886L1687.5 273.411H1691.13C1698.4 262.517 1712.92 251.623 1738.34 251.623C1767.39 251.623 1778.29 266.148 1785.55 277.042H1789.18C1800.07 262.517 1818.23 251.623 1843.65 251.623ZM1981.48 222.573V193.522L1977.85 189.891H1948.8L1945.17 193.522V222.573L1948.8 226.204H1977.85L1981.48 222.573ZM1981.48 258.886L1977.85 255.254H1948.8L1945.17 258.886V433.188L1948.8 436.819H1977.85L1981.48 433.188V258.886ZM2126.63 407.769V433.188L2123 436.819H2093.95C2068.53 436.819 2043.11 422.294 2043.11 389.612V291.567L2039.48 287.936H2014.06L2010.43 284.305V258.886L2014.06 255.254H2039.48L2043.11 251.623V218.941L2046.75 215.31H2075.8L2079.43 218.941V251.623L2083.06 255.254H2123L2126.63 258.886V284.305L2123 287.936H2083.06L2079.43 291.567V389.612C2079.43 400.506 2086.69 404.137 2093.95 404.137H2123L2126.63 407.769ZM2319.05 433.188V327.88C2319.05 288.299 2304.53 251.623 2250.06 251.623C2230.81 251.623 2213.38 259.612 2202.85 273.411H2199.22V186.26L2195.59 182.628H2166.54L2162.91 186.26V433.188L2166.54 436.819H2195.59L2199.22 433.188V342.405C2199.22 298.83 2217.37 284.305 2246.42 284.305C2275.48 284.305 2282.74 306.092 2282.74 327.88V433.188L2286.37 436.819H2315.42L2319.05 433.188ZM631.176 345.959C631.176 305.983 649.347 284.178 682.055 284.178C711.129 284.178 722.031 298.715 725.665 313.252L729.3 316.886H758.373L762.007 313.252C758.373 280.544 732.934 251.47 682.055 251.47C627.542 251.47 594.834 287.812 594.834 345.959C594.834 404.107 627.542 440.449 682.055 440.449C732.934 440.449 758.373 411.375 762.007 378.667L758.373 375.033H729.3L725.665 378.667C722.031 393.204 711.129 407.741 682.055 407.741C649.347 407.741 631.176 385.936 631.176 345.959ZM868.926 408.456V433.875L865.294 437.507H846.064C820.645 437.507 795.226 422.982 795.226 390.3L795.224 186.26L798.856 182.628H827.906L831.537 186.26L831.539 390.3C831.539 401.194 838.802 404.825 846.064 404.825H865.294L868.926 408.456Z" fill="#092F45"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="2507" height="687" fill="none" viewBox="0 0 2507 687"><path fill="#2A6FE1" fill-rule="evenodd" d="M499.999 325.98V360.535L361.332 498.757H326.665L187.999 360.535V325.98L326.665 187.757H361.332L499.999 325.98ZM343.997 436.159C395.468 436.159 437.193 394.568 437.193 343.263C437.193 291.957 395.468 250.366 343.997 250.366C292.527 250.366 250.802 291.957 250.802 343.263C250.802 394.568 292.527 436.159 343.997 436.159Z" clip-rule="evenodd"/><path fill="#092F45" fill-rule="evenodd" d="M1440.66 186.26V433.188L1437.03 436.819H1411.61L1407.98 433.188L1404.35 418.663H1400.72C1389.82 432.461 1372.76 440.45 1353.51 440.45C1302.67 440.45 1273.62 400.506 1273.62 346.037C1273.62 291.567 1302.67 251.623 1353.51 251.623C1371.67 251.623 1389.46 258.886 1400.72 273.411H1404.35V186.26L1407.98 182.628H1437.03L1440.66 186.26ZM1357.14 407.769C1386.19 407.769 1404.35 389.612 1404.35 346.037C1404.35 302.461 1386.19 284.305 1357.14 284.305C1328.09 284.305 1309.93 305.729 1309.93 346.037C1309.93 386.344 1328.09 407.769 1357.14 407.769ZM884.959 346.037C884.959 404.137 917.641 440.45 972.11 440.45C1026.58 440.45 1059.26 404.137 1059.26 346.037C1059.26 287.936 1026.58 251.623 972.11 251.623C917.641 251.623 884.959 287.936 884.959 346.037ZM921.272 346.037C921.272 306.092 939.429 284.305 972.11 284.305C1004.79 284.305 1022.95 306.092 1022.95 346.037C1022.95 385.981 1004.79 407.769 972.11 407.769C939.429 407.769 921.272 385.981 921.272 346.037ZM1240.84 258.886L1237.21 255.254H1208.16L1204.53 258.886V346.037C1204.53 389.612 1186.01 407.769 1160.95 407.769C1131.9 407.769 1124.64 385.981 1124.64 364.193V258.886L1121.01 255.254H1091.96L1088.33 258.886V364.193C1088.33 403.774 1106.48 440.45 1157.32 440.45C1174.75 440.45 1191.45 431.735 1200.9 418.663H1204.53L1208.16 433.188L1211.79 436.819H1237.21L1240.84 433.188V258.886ZM1618.52 385.981C1618.52 353.299 1600.37 338.774 1564.05 331.511C1527.74 324.249 1513.22 320.618 1513.22 302.461C1513.22 287.936 1527.74 284.305 1545.9 284.305C1571.32 284.305 1578.58 295.199 1578.58 306.092L1582.21 309.724H1611.26L1614.89 306.092C1614.89 269.779 1585.84 251.623 1545.9 251.623C1495.06 251.623 1476.9 277.042 1476.9 302.461C1476.9 335.143 1498.69 349.668 1535 356.931C1571.32 364.193 1582.21 367.824 1582.21 385.981C1582.21 400.506 1571.32 407.769 1545.9 407.769C1520.48 407.769 1509.58 396.875 1509.58 378.718L1505.95 375.087H1476.9L1473.27 378.718C1473.27 418.663 1498.69 440.45 1545.9 440.45C1596.74 440.45 1618.52 415.031 1618.52 385.981ZM1843.65 251.623C1894.49 251.623 1905.38 284.305 1905.38 324.249V433.188L1901.75 436.819H1872.7L1869.07 433.188V324.249C1869.07 302.461 1865.44 284.305 1836.39 284.305C1810.97 284.305 1796.44 302.461 1796.44 331.511V433.188L1792.81 436.819H1763.76L1760.13 433.188V324.249C1760.13 302.461 1756.5 284.305 1727.45 284.305C1709.29 284.305 1687.5 295.199 1687.5 331.511V433.188L1683.87 436.819H1654.82L1651.19 433.188V258.886L1654.82 255.254H1680.24L1683.87 258.886L1687.5 273.411H1691.13C1698.4 262.517 1712.92 251.623 1738.34 251.623C1767.39 251.623 1778.29 266.148 1785.55 277.042H1789.18C1800.07 262.517 1818.23 251.623 1843.65 251.623ZM1981.48 222.573V193.522L1977.85 189.891H1948.8L1945.17 193.522V222.573L1948.8 226.204H1977.85L1981.48 222.573ZM1981.48 258.886L1977.85 255.254H1948.8L1945.17 258.886V433.188L1948.8 436.819H1977.85L1981.48 433.188V258.886ZM2126.63 407.769V433.188L2123 436.819H2093.95C2068.53 436.819 2043.11 422.294 2043.11 389.612V291.567L2039.48 287.936H2014.06L2010.43 284.305V258.886L2014.06 255.254H2039.48L2043.11 251.623V218.941L2046.75 215.31H2075.8L2079.43 218.941V251.623L2083.06 255.254H2123L2126.63 258.886V284.305L2123 287.936H2083.06L2079.43 291.567V389.612C2079.43 400.506 2086.69 404.137 2093.95 404.137H2123L2126.63 407.769ZM2319.05 433.188V327.88C2319.05 288.299 2304.53 251.623 2250.06 251.623C2230.81 251.623 2213.38 259.612 2202.85 273.411H2199.22V186.26L2195.59 182.628H2166.54L2162.91 186.26V433.188L2166.54 436.819H2195.59L2199.22 433.188V342.405C2199.22 298.83 2217.37 284.305 2246.42 284.305C2275.48 284.305 2282.74 306.092 2282.74 327.88V433.188L2286.37 436.819H2315.42L2319.05 433.188ZM631.176 345.959C631.176 305.983 649.347 284.178 682.055 284.178C711.129 284.178 722.031 298.715 725.665 313.252L729.3 316.886H758.373L762.007 313.252C758.373 280.544 732.934 251.47 682.055 251.47C627.542 251.47 594.834 287.812 594.834 345.959C594.834 404.107 627.542 440.449 682.055 440.449C732.934 440.449 758.373 411.375 762.007 378.667L758.373 375.033H729.3L725.665 378.667C722.031 393.204 711.129 407.741 682.055 407.741C649.347 407.741 631.176 385.936 631.176 345.959ZM868.926 408.456V433.875L865.294 437.507H846.064C820.645 437.507 795.226 422.982 795.226 390.3L795.224 186.26L798.856 182.628H827.906L831.537 186.26L831.539 390.3C831.539 401.194 838.802 404.825 846.064 404.825H865.294L868.926 408.456Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -595,7 +595,7 @@
},
"outputString": {
"type": "string",
"enum": ["interleaved", "prefixed", "group", "gitlab"],
"enum": ["interleaved", "prefixed", "group"],
"default": "interleaved"
},
"outputObject": {
@@ -616,22 +616,6 @@
"default": false
}
}
},
"gitlab": {
"type": "object",
"description": "Wraps each task's output in GitLab CI collapsible section markers",
"properties": {
"collapsed": {
"description": "Passes the native GitLab [collapsed=true] option so sections are folded by default in the GitLab CI UI",
"type": "boolean",
"default": false
},
"error_only": {
"description": "Swallows command output on zero exit code (Task-level behavior, identical to group.error_only)",
"type": "boolean",
"default": false
}
}
}
},
"additionalProperties": false