mirror of
https://github.com/go-task/task.git
synced 2026-05-18 13:15:41 +02:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09e7247d05 | ||
|
|
502f24a2ad | ||
|
|
f09f31c6d5 | ||
|
|
5a78808caa | ||
|
|
026c899d90 | ||
|
|
f6720760b4 | ||
|
|
065236f076 | ||
|
|
1bd5aa6bd5 | ||
|
|
c3fd3c4b5e | ||
|
|
299232ee7d | ||
|
|
12a26fa15e | ||
|
|
4ab5dec8ae | ||
|
|
af311229fe | ||
|
|
1443e2d989 | ||
|
|
5bf4e4a29b | ||
|
|
f9052c9fdf | ||
|
|
0a82e2e053 | ||
|
|
6dedcafd7d | ||
|
|
c84cfa41f7 | ||
|
|
9bc1efbc47 | ||
|
|
da7eb0c855 | ||
|
|
edb491a4d0 | ||
|
|
2ad3d26f4a | ||
|
|
cdfcd08213 | ||
|
|
382c37bc2a | ||
|
|
618cd8956f | ||
|
|
b53e5da41a | ||
|
|
b9c1ab8eae | ||
|
|
e47f55783e | ||
|
|
d5f071c096 | ||
|
|
fb784f4e3d | ||
|
|
91f9299c98 | ||
|
|
145412a75c | ||
|
|
ba38344ca6 | ||
|
|
4e963f8714 | ||
|
|
3d4d189bcd | ||
|
|
179bde1f37 | ||
|
|
e4de687aee | ||
|
|
06538860a8 | ||
|
|
8a37bf5c1f | ||
|
|
ca99266aea | ||
|
|
8dfafe507f | ||
|
|
678fdec7d2 | ||
|
|
3626b271a7 | ||
|
|
fc378cfb92 | ||
|
|
9488a2a744 | ||
|
|
6ece2445ae | ||
|
|
9b95e758f4 |
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.7.2
|
||||
version: v2.8.0
|
||||
|
||||
lint-jsonschema:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
69
.github/workflows/pr-build.yml
vendored
Normal file
69
.github/workflows/pr-build.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: PR Build
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled, synchronize]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'needs-build')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25.x'
|
||||
cache: true
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: '~> v2'
|
||||
args: release --snapshot --clean --config .goreleaser-pr.yml
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: task_linux_amd64
|
||||
path: dist/task_linux_amd64.tar.gz
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: task_linux_arm64
|
||||
path: dist/task_linux_arm64.tar.gz
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: task_darwin_amd64
|
||||
path: dist/task_darwin_amd64.tar.gz
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: task_darwin_arm64
|
||||
path: dist/task_darwin_arm64.tar.gz
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: task_windows_amd64
|
||||
path: dist/task_windows_amd64.zip
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: checksums
|
||||
path: dist/task_checksums.txt
|
||||
- uses: peter-evans/find-comment@v3
|
||||
id: find-comment
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT || github.token }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-includes: '📦 Build artifacts ready!'
|
||||
- uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT || github.token }}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
## 📦 Build artifacts ready!
|
||||
|
||||
Download binaries from [this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
|
||||
|
||||
Available platforms: Linux, macOS, Windows (amd64, arm64)
|
||||
edit-mode: replace
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,3 +35,4 @@ tags
|
||||
/testdata/vars/v1
|
||||
/tmp
|
||||
node_modules
|
||||
website/.netlify/
|
||||
|
||||
31
.goreleaser-pr.yml
Normal file
31
.goreleaser-pr.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- binary: task
|
||||
main: ./cmd/task
|
||||
goos: [windows, darwin, linux]
|
||||
goarch: [amd64, arm64]
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- "-s -w"
|
||||
|
||||
archives:
|
||||
- name_template: '{{.Binary}}_{{.Os}}_{{.Arch}}'
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- completion/**/*
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats: [zip]
|
||||
|
||||
snapshot:
|
||||
version_template: 'pr-{{ .ShortCommit }}'
|
||||
|
||||
checksum:
|
||||
name_template: 'task_checksums.txt'
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -1,6 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
## v3.46.0 - 2025-12-18
|
||||
## v3.48.0 - 2026-01-26
|
||||
|
||||
- Fixed `if:` conditions when using to check dynamic variables. Also, skip
|
||||
variable prompt if task would be skipped by `if:` (#2658, #2660 by @vmaerten).
|
||||
- Fixed `ROOT_TASKFILE` variable pointing to directory instead of the actual
|
||||
Taskfile path when no explicit `-t` flag is provided (#2635, #1706 by
|
||||
@trulede).
|
||||
- Included Taskfiles with `silent: true` now properly propagate silence to their
|
||||
tasks, while still allowing individual tasks to override with `silent: false`
|
||||
(#2640, #1319 by @trulede).
|
||||
- Added TLS certificate options for Remote Taskfiles: use `--cacert` for
|
||||
self-signed certificates and `--cert`/`--cert-key` for mTLS authentication
|
||||
(#2537, #2242 by @vmaerten).
|
||||
|
||||
## v3.47.0 - 2026-01-24
|
||||
|
||||
- Fixed remote git Taskfiles: cloning now works without explicit ref, and
|
||||
directory includes are properly resolved (#2602 by @vmaerten).
|
||||
- For `output: prefixed`, print `prefix:` if set instead of task name (#1566,
|
||||
#2633 by @trulede).
|
||||
- Ensure no ANSI sequences are printed for `--color=false` (#2560, #2584 by
|
||||
@trulede).
|
||||
- Task aliases can now contain wildcards and will match accordingly (e.g., `s-*`
|
||||
as alias for `start-*`) (#1900, #2234 by @vmaerten).
|
||||
- Added conditional execution with the `if` field: skip tasks, commands, or task
|
||||
calls based on shell exit codes or template expressions like
|
||||
`{{ eq .ENV "prod" }}` (#2564, #608 by @vmaerten).
|
||||
- Task can now interactively prompt for missing required variables when running
|
||||
in a TTY, with support for enum selection menus. Enable with `--interactive`
|
||||
flag or `interactive: true` in `.taskrc.yml` (#2579, #2079 by @vmaerten).
|
||||
|
||||
## v3.46.4 - 2025-12-24
|
||||
|
||||
- Fixed regressions in completion script for Fish (#2591, #2604, #2592 by
|
||||
@WinkelCode).
|
||||
|
||||
## v3.46.3 - 2025-12-19
|
||||
|
||||
- Fixed regression in completion script for zsh (#2593, #2594 by @vmaerten).
|
||||
|
||||
## v3.46.2 - 2025-12-18
|
||||
|
||||
- Fixed a regression on previous release that affected variables passed via
|
||||
command line (#2588, #2589 by @vmaerten).
|
||||
|
||||
## v3.46.1 - 2025-12-18
|
||||
|
||||
### ✨ Features
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ tasks:
|
||||
aliases: [i]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- go.mod
|
||||
cmds:
|
||||
- go install -v ./cmd/task
|
||||
|
||||
@@ -226,7 +227,7 @@ tasks:
|
||||
- "git add --all"
|
||||
- "git commit -m v{{.VERSION}}"
|
||||
- "git push"
|
||||
- "git tag v{{.VERSION}}"
|
||||
- "git tag -a v{{.VERSION}} -m v{{.VERSION}}"
|
||||
- "git push origin tag v{{.VERSION}}"
|
||||
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
|
||||
silent: true
|
||||
|
||||
@@ -172,18 +172,23 @@ func run() error {
|
||||
calls = append(calls, &task.Call{Task: "default"})
|
||||
}
|
||||
|
||||
// Merge CLI variables first (e.g. FOO=bar) so they take priority over Taskfile defaults
|
||||
e.Taskfile.Vars.Merge(globals, nil)
|
||||
|
||||
// Then ReverseMerge special variables so they're available for templating
|
||||
cliArgsPostDashQuoted, err := args.ToQuotedString(cliArgsPostDash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
globals.Set("CLI_ARGS", ast.Var{Value: cliArgsPostDashQuoted})
|
||||
globals.Set("CLI_ARGS_LIST", ast.Var{Value: cliArgsPostDash})
|
||||
globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
||||
globals.Set("CLI_ASSUME_YES", ast.Var{Value: flags.AssumeYes})
|
||||
e.Taskfile.Vars.ReverseMerge(globals, nil)
|
||||
specialVars := ast.NewVars()
|
||||
specialVars.Set("CLI_ARGS", ast.Var{Value: cliArgsPostDashQuoted})
|
||||
specialVars.Set("CLI_ARGS_LIST", ast.Var{Value: cliArgsPostDash})
|
||||
specialVars.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||
specialVars.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||
specialVars.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||
specialVars.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
||||
specialVars.Set("CLI_ASSUME_YES", ast.Var{Value: flags.AssumeYes})
|
||||
e.Taskfile.Vars.ReverseMerge(specialVars, nil)
|
||||
if !flags.Watch {
|
||||
e.InterceptInterruptSignals()
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ function _task()
|
||||
_filedir -d
|
||||
return $?
|
||||
;;
|
||||
--cacert|--cert|--cert-key)
|
||||
_filedir
|
||||
return $?
|
||||
;;
|
||||
-t|--taskfile)
|
||||
_filedir yaml || return $?
|
||||
_filedir yml
|
||||
|
||||
@@ -54,7 +54,7 @@ function __task_get_tasks --description "Prints all available tasks with their d
|
||||
end
|
||||
|
||||
# Grab names and descriptions (if any) of the tasks
|
||||
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):\s\{2,\}\(.*\)\s\{2,\}(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):\s\{2,\}\(.*\)/\1\t\2/'| string split0)
|
||||
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):[[:space:]]\{2,\}\(.*\)[[:space:]]\{2,\}(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):[[:space:]]\{2,\}\(.*\)/\1\t\2/'| string split0)
|
||||
if test $output
|
||||
echo $output
|
||||
end
|
||||
@@ -86,6 +86,7 @@ complete -c $GO_TASK_PROGNAME -s j -l json -d 'format task
|
||||
complete -c $GO_TASK_PROGNAME -s l -l list -d 'list tasks with descriptions'
|
||||
complete -c $GO_TASK_PROGNAME -l nested -d 'nest namespaces when listing as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -l no-status -d 'ignore status when listing as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -l interactive -d 'prompt for missing required variables'
|
||||
complete -c $GO_TASK_PROGNAME -s o -l output -d 'set output style' -xa "interleaved group prefixed"
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-begin -d 'message template before grouped output'
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-end -d 'message template after grouped output'
|
||||
@@ -110,6 +111,9 @@ complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES"
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l timeout -d 'timeout for remote Taskfile downloads'
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l expiry -d 'cache expiry duration'
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l remote-cache-dir -d 'directory to cache remote Taskfiles' -xa "(__fish_complete_directories)"
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cacert -d 'custom CA certificate for TLS' -r
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cert -d 'client certificate for mTLS' -r
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cert-key -d 'client certificate private key' -r
|
||||
|
||||
# RemoteTaskfiles experiment - Operations
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l download -d 'download remote Taskfile'
|
||||
|
||||
@@ -40,6 +40,7 @@ Register-ArgumentCompleter -CommandName task -ScriptBlock {
|
||||
[CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'list tasks'),
|
||||
[CompletionResult]::new('--nested', '--nested', [CompletionResultType]::ParameterName, 'nest namespaces in JSON'),
|
||||
[CompletionResult]::new('--no-status', '--no-status', [CompletionResultType]::ParameterName, 'ignore status in JSON'),
|
||||
[CompletionResult]::new('--interactive', '--interactive', [CompletionResultType]::ParameterName, 'prompt for missing required variables'),
|
||||
[CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'set output style'),
|
||||
[CompletionResult]::new('--output', '--output', [CompletionResultType]::ParameterName, 'set output style'),
|
||||
[CompletionResult]::new('--output-group-begin', '--output-group-begin', [CompletionResultType]::ParameterName, 'template before group'),
|
||||
@@ -76,6 +77,9 @@ Register-ArgumentCompleter -CommandName task -ScriptBlock {
|
||||
$completions += [CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'download timeout')
|
||||
$completions += [CompletionResult]::new('--expiry', '--expiry', [CompletionResultType]::ParameterName, 'cache expiry')
|
||||
$completions += [CompletionResult]::new('--remote-cache-dir', '--remote-cache-dir', [CompletionResultType]::ParameterName, 'cache directory')
|
||||
$completions += [CompletionResult]::new('--cacert', '--cacert', [CompletionResultType]::ParameterName, 'custom CA certificate')
|
||||
$completions += [CompletionResult]::new('--cert', '--cert', [CompletionResultType]::ParameterName, 'client certificate')
|
||||
$completions += [CompletionResult]::new('--cert-key', '--cert-key', [CompletionResultType]::ParameterName, 'client private key')
|
||||
# Operations
|
||||
$completions += [CompletionResult]::new('--download', '--download', [CompletionResultType]::ParameterName, 'download remote Taskfile')
|
||||
$completions += [CompletionResult]::new('--clear-cache', '--clear-cache', [CompletionResultType]::ParameterName, 'clear cache')
|
||||
|
||||
@@ -34,7 +34,7 @@ function __task_list() {
|
||||
fi
|
||||
|
||||
# Check if global flag is set
|
||||
if (( ${+opt_args[(i)-g|--global]} )); then
|
||||
if (( ${+opt_args[-g]} || ${+opt_args[--global]} )); then
|
||||
cmd+=(--global)
|
||||
fi
|
||||
|
||||
@@ -90,6 +90,7 @@ _task() {
|
||||
'(-j --json)'{-j,--json}'[format task list as JSON]'
|
||||
'(--nested)--nested[nest namespaces when listing as JSON]'
|
||||
'(--no-status)--no-status[ignore status when listing as JSON]'
|
||||
'(--interactive)--interactive[prompt for missing required variables]'
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)'
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: '
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: '
|
||||
@@ -116,6 +117,9 @@ _task() {
|
||||
'(--timeout)--timeout[timeout for remote Taskfile downloads]:duration: '
|
||||
'(--expiry)--expiry[cache expiry duration]:duration: '
|
||||
'(--remote-cache-dir)--remote-cache-dir[directory to cache remote Taskfiles]:cache dir:_dirs'
|
||||
'(--cacert)--cacert[custom CA certificate for TLS]:file:_files'
|
||||
'(--cert)--cert[client certificate for mTLS]:file:_files'
|
||||
'(--cert-key)--cert-key[client certificate private key]:file:_files'
|
||||
)
|
||||
fi
|
||||
|
||||
|
||||
57
executor.go
57
executor.go
@@ -38,12 +38,16 @@ type (
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
RemoteCacheDir string
|
||||
CACert string
|
||||
Cert string
|
||||
CertKey string
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
DisableFuzzy bool
|
||||
AssumeYes bool
|
||||
AssumeTerm bool // Used for testing
|
||||
Interactive bool
|
||||
Dry bool
|
||||
Summary bool
|
||||
Parallel bool
|
||||
@@ -70,6 +74,7 @@ type (
|
||||
fuzzyModel *fuzzy.Model
|
||||
fuzzyModelOnce sync.Once
|
||||
|
||||
promptedVars *ast.Vars // vars collected via interactive prompts
|
||||
concurrencySemaphore chan struct{}
|
||||
taskCallCount map[string]*int32
|
||||
mkdirMutexMap map[string]*sync.Mutex
|
||||
@@ -285,6 +290,45 @@ func (o *remoteCacheDirOption) ApplyToExecutor(e *Executor) {
|
||||
e.RemoteCacheDir = o.dir
|
||||
}
|
||||
|
||||
// WithCACert sets the path to a custom CA certificate for TLS connections.
|
||||
func WithCACert(caCert string) ExecutorOption {
|
||||
return &caCertOption{caCert: caCert}
|
||||
}
|
||||
|
||||
type caCertOption struct {
|
||||
caCert string
|
||||
}
|
||||
|
||||
func (o *caCertOption) ApplyToExecutor(e *Executor) {
|
||||
e.CACert = o.caCert
|
||||
}
|
||||
|
||||
// WithCert sets the path to a client certificate for TLS connections.
|
||||
func WithCert(cert string) ExecutorOption {
|
||||
return &certOption{cert: cert}
|
||||
}
|
||||
|
||||
type certOption struct {
|
||||
cert string
|
||||
}
|
||||
|
||||
func (o *certOption) ApplyToExecutor(e *Executor) {
|
||||
e.Cert = o.cert
|
||||
}
|
||||
|
||||
// WithCertKey sets the path to a client certificate key for TLS connections.
|
||||
func WithCertKey(certKey string) ExecutorOption {
|
||||
return &certKeyOption{certKey: certKey}
|
||||
}
|
||||
|
||||
type certKeyOption struct {
|
||||
certKey string
|
||||
}
|
||||
|
||||
func (o *certKeyOption) ApplyToExecutor(e *Executor) {
|
||||
e.CertKey = o.certKey
|
||||
}
|
||||
|
||||
// WithWatch tells the [Executor] to keep running in the background and watch
|
||||
// for changes to the fingerprint of the tasks that are run. When changes are
|
||||
// detected, a new task run is triggered.
|
||||
@@ -367,6 +411,19 @@ func (o *assumeTermOption) ApplyToExecutor(e *Executor) {
|
||||
e.AssumeTerm = o.assumeTerm
|
||||
}
|
||||
|
||||
// WithInteractive tells the [Executor] to prompt for missing required variables.
|
||||
func WithInteractive(interactive bool) ExecutorOption {
|
||||
return &interactiveOption{interactive}
|
||||
}
|
||||
|
||||
type interactiveOption struct {
|
||||
interactive bool
|
||||
}
|
||||
|
||||
func (o *interactiveOption) ApplyToExecutor(e *Executor) {
|
||||
e.Interactive = o.interactive
|
||||
}
|
||||
|
||||
// WithDry tells the [Executor] to output the commands that would be run without
|
||||
// actually running them.
|
||||
func WithDry(dry bool) ExecutorOption {
|
||||
|
||||
117
executor_test.go
117
executor_test.go
@@ -263,6 +263,23 @@ func TestVars(t *testing.T) {
|
||||
task.WithSilent(true),
|
||||
),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("cli-var-priority-default"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/vars"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithTask("cli-var-priority"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("cli-var-priority-override"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/vars"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithTask("cli-var-priority"),
|
||||
WithVar("CLI_VAR", "from_cli"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestRequires(t *testing.T) {
|
||||
@@ -347,6 +364,7 @@ func TestSpecialVars(t *testing.T) {
|
||||
// Root
|
||||
"print-task",
|
||||
"print-root-dir",
|
||||
"print-root-taskfile",
|
||||
"print-taskfile",
|
||||
"print-taskfile-dir",
|
||||
"print-task-dir",
|
||||
@@ -700,6 +718,27 @@ func TestLabel(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
func TestPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("up to date"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/prefix_uptodate"),
|
||||
task.WithOutputStyle(ast.Output{Name: "prefixed"}),
|
||||
),
|
||||
WithTask("foo"),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("up to dat with no output style"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/prefix_uptodate"),
|
||||
),
|
||||
WithTask("foo"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestPromptInSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1021,6 +1060,18 @@ func TestIncludeChecksum(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
func TestIncludeSilent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("include-taskfile-silent"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/includes_silent"),
|
||||
),
|
||||
WithTask("default"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestFailfast(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1067,3 +1118,69 @@ func TestFailfast(t *testing.T) {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
task string
|
||||
vars map[string]any
|
||||
verbose bool
|
||||
}{
|
||||
// Basic command-level if
|
||||
{name: "cmd-if-true", task: "cmd-if-true"},
|
||||
{name: "cmd-if-false", task: "cmd-if-false"},
|
||||
|
||||
// Task-level if
|
||||
{name: "task-if-true", task: "task-if-true"},
|
||||
{name: "task-if-false", task: "task-if-false", verbose: true},
|
||||
|
||||
// Task call with if
|
||||
{name: "task-call-if-true", task: "task-call-if-true"},
|
||||
{name: "task-call-if-false", task: "task-call-if-false", verbose: true},
|
||||
|
||||
// Go template conditions
|
||||
{name: "template-eq-true", task: "template-eq-true"},
|
||||
{name: "template-eq-false", task: "template-eq-false", verbose: true},
|
||||
{name: "template-ne", task: "template-ne"},
|
||||
{name: "template-bool-true", task: "template-bool-true"},
|
||||
{name: "template-bool-false", task: "template-bool-false"},
|
||||
{name: "template-direct-true", task: "template-direct-true"},
|
||||
{name: "template-direct-false", task: "template-direct-false"},
|
||||
{name: "template-and", task: "template-and"},
|
||||
{name: "template-or", task: "template-or"},
|
||||
|
||||
// CLI variable override
|
||||
{name: "template-cli-var", task: "template-cli-var", vars: map[string]any{"MY_VAR": "yes"}},
|
||||
|
||||
// Task-level if with template
|
||||
{name: "task-level-template", task: "task-level-template"},
|
||||
{name: "task-level-template-false", task: "task-level-template-false", verbose: true},
|
||||
|
||||
// For loop with if
|
||||
{name: "if-in-for-loop", task: "if-in-for-loop", verbose: true},
|
||||
|
||||
// Task-level if with dynamic variable
|
||||
{name: "task-if-dynamic-true", task: "task-if-dynamic-true"},
|
||||
{name: "task-if-dynamic-false", task: "task-if-dynamic-false", verbose: true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
opts := []ExecutorTestOption{
|
||||
WithName(test.name),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/if"),
|
||||
task.WithSilent(true),
|
||||
task.WithVerbose(test.verbose),
|
||||
),
|
||||
WithTask(test.task),
|
||||
}
|
||||
if test.vars != nil {
|
||||
for k, v := range test.vars {
|
||||
opts = append(opts, WithVar(k, v))
|
||||
}
|
||||
}
|
||||
NewExecutorTest(t, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
149
go.mod
149
go.mod
@@ -1,13 +1,18 @@
|
||||
module github.com/go-task/task/v3
|
||||
|
||||
go 1.24.0
|
||||
go 1.24.6
|
||||
|
||||
toolchain go1.25.6
|
||||
|
||||
require (
|
||||
charm.land/bubbles/v2 v2.0.0-rc.1
|
||||
charm.land/bubbletea/v2 v2.0.0-rc.2
|
||||
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7
|
||||
github.com/Ladicle/tabwriter v1.0.0
|
||||
github.com/Masterminds/semver/v3 v3.4.0
|
||||
github.com/alecthomas/chroma/v2 v2.20.0
|
||||
github.com/alecthomas/chroma/v2 v2.23.0
|
||||
github.com/chainguard-dev/git-urls v1.0.2
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
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.18.0
|
||||
@@ -15,10 +20,10 @@ require (
|
||||
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.3
|
||||
github.com/hashicorp/go-getter v1.8.4
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0
|
||||
github.com/sajari/fuzzy v1.0.0
|
||||
github.com/sebdah/goldie/v2 v2.8.0
|
||||
github.com/spf13/pflag v1.0.10
|
||||
@@ -26,68 +31,110 @@ require (
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/term v0.38.0
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20251109230715-65adef8e2c5b
|
||||
mvdan.cc/sh/v3 v3.12.0
|
||||
golang.org/x/term v0.39.0
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997
|
||||
mvdan.cc/sh/v3 v3.12.1-0.20260124232039-e74afc18e65b
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/iam v0.13.0 // indirect
|
||||
cloud.google.com/go/storage v1.29.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.68 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.20 // indirect
|
||||
github.com/aws/smithy-go v1.22.3 // indirect
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.17.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.3 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
cloud.google.com/go/storage v1.58.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||
github.com/aws/smithy-go v1.24.0 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.3.3 // indirect
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.1 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.5.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/u-root/u-root v0.15.1-0.20251014130006-62f7144b33da // indirect
|
||||
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/oauth2 v0.27.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.114.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.56.3 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/oauth2 v0.33.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
google.golang.org/api v0.256.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/grpc v1.76.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
407
go.sum
407
go.sum
@@ -1,73 +1,125 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
|
||||
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k=
|
||||
cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
|
||||
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
|
||||
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
|
||||
cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI=
|
||||
cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
charm.land/bubbles/v2 v2.0.0-rc.1 h1:EiIFVAc3Zi/yY86td+79mPhHR7AqZ1OxF+6ztpOCRaM=
|
||||
charm.land/bubbles/v2 v2.0.0-rc.1/go.mod h1:5AbN6cEd/47gkEf8TgiQ2O3RZ5QxMS14l9W+7F9fPC4=
|
||||
charm.land/bubbletea/v2 v2.0.0-rc.2 h1:TdTbUOFzbufDJmSz/3gomL6q+fR6HwfY+P13hXQzD7k=
|
||||
charm.land/bubbletea/v2 v2.0.0-rc.2/go.mod h1:IXFmnCnMLTWw/KQ9rEatSYqbAPAYi8kA3Yqwa1SFnLk=
|
||||
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7 h1:059k1h5vvZ4ASinki9nmBguxu9Rq0UDDSa6q8LOUphk=
|
||||
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7/go.mod h1:1qZyvvVCenJO2M1ac2mX0yyiIZJoZmDM4DG4s0udJkU=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
|
||||
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
||||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
||||
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
|
||||
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
|
||||
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
|
||||
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
|
||||
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
|
||||
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
|
||||
cloud.google.com/go/storage v1.58.0 h1:PflFXlmFJjG/nBeR9B7pKddLQWaFaRWx4uUi/LyNxxo=
|
||||
cloud.google.com/go/storage v1.58.0/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=
|
||||
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
|
||||
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.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/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.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
||||
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.15 h1:I5XjesVMpDZXZEZonVfjI12VNMrYa38LtLnw4NtY5Ss=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.15/go.mod h1:tNIp4JIPonlsgaO5hxO372a6gjhN63aSWl2GVl5QoBQ=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.68 h1:cFb9yjI02/sWHBSYXAtkamjzCuRymvmeFmt0TC0MbYY=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.68/go.mod h1:H6E+jBzyqUu8u0vGaU6POkK3P0NylYEeRZ6ynBpMqIk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2 h1:BCG7DCXEXpNCcpwCxg1oi9pkJWH2+eZzTn9MY56MbVw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.1 h1:xYEAf/6QHiTZDccKnPMbsMwlau13GsDsTgdue3wmHGw=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.1/go.mod h1:qbn305Je/IofWBJ4bJz/Q7pDEtnnoInw/dGt71v6rHE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.20 h1:oIaQ1e17CSKaWmUTu62MtraRWVIosn/iONMuZt0gbqc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.20/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
|
||||
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
|
||||
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/alecthomas/chroma/v2 v2.23.0 h1:u/Orux1J0eLuZDeQ44froV8smumheieI0EofhbyKhhk=
|
||||
github.com/alecthomas/chroma/v2 v2.23.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
||||
github.com/alecthomas/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=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
|
||||
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
|
||||
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 h1:7Rs87fbKJoIIxsQS8YKJYGYa0tlsDwwb0twQjV1KB+g=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38/go.mod h1:6lfcr3MNP+kZR25sF1nQwJFuQnNYBlFy3PGX5rvslXc=
|
||||
github.com/charmbracelet/x/ansi v0.11.1 h1:iXAC8SyMQDJgtcz9Jnw+HU8WMEctHzoTAETIeA3JXMk=
|
||||
github.com/charmbracelet/x/ansi v0.11.1/go.mod h1:M49wjzpIujwPceJ+t5w3qh2i87+HRtHohgb5iTyepL0=
|
||||
github.com/charmbracelet/x/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=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
|
||||
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
||||
github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
|
||||
github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||
@@ -76,68 +128,61 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
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/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviBE=
|
||||
github.com/go-task/template v0.2.0/go.mod h1:dbdoUb6qKnHQi1y6o+IdIrs0J4o/SEhSTA6bbzZmdtc=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
|
||||
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A=
|
||||
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow=
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65/go.mod h1:WtMzv9T++tfWVea+qB2MXoaqxw33S8bpJslzUike2mQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 h1:0HADrxxqaQkGycO1JoUUA+B4FnIkuo8d2bz/hSaTFFQ=
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70/go.mod h1:fm2FdDCzJdtbXF7WKAMvBb5NEPouXPHFbGNYs9ShFns=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-getter v1.8.3 h1:gIS+oTNv3kyYAvlUVgMR46MiG0bM0KuSON/KZEvRoRg=
|
||||
github.com/hashicorp/go-getter v1.8.3/go.mod h1:CUTt9x2bCtJ/sV8ihgrITL3IUE+0BE1j/e4n5P/GIM4=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-getter v1.8.4 h1:hGEd2xsuVKgwkMtPVufq73fAmZU/x65PPcqH3cb0D9A=
|
||||
github.com/hashicorp/go-getter v1.8.4/go.mod h1:x27pPGSg9kzoB147QXI8d/nDvp2IgYGcwuRjpaXE9Yg=
|
||||
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
|
||||
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
@@ -149,22 +194,32 @@ 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/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.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/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=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0 h1:w/bWkEJdYuRNYhHn5eXnIT8LzDM1O629X1I9MJSkD7Q=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/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=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||
@@ -176,118 +231,94 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/u-root/u-root v0.15.1-0.20251014130006-62f7144b33da h1:Vst9Tvq3G6f6pYBvxy7coi2arDsnOZ3Mkj8MkNarSK8=
|
||||
github.com/u-root/u-root v0.15.1-0.20251014130006-62f7144b33da/go.mod h1:R49zft13memK20EgFAvmTbXBS0t29UvglnM0BCA1ldQ=
|
||||
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8 h1:cq+DjLAjz3ZPwh0+G571O/jMH0c0DzReDPLjQGL2/BA=
|
||||
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8/go.mod h1:JNauIV2zopCBv/6o+umxcT3bKe8YUqYJaTZQYSYpKss=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
|
||||
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
|
||||
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
|
||||
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
|
||||
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc=
|
||||
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20251109230715-65adef8e2c5b h1:vTpx76nZDTP/BAGnnhEXYjM+8nPKe9+I86qCErBvjCw=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20251109230715-65adef8e2c5b/go.mod h1:bDyKbUYKqkFunWmxxuSPrkYpln9QZcUsqu7W128qYW4=
|
||||
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
|
||||
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
|
||||
mvdan.cc/sh/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.12.1-0.20260124232039-e74afc18e65b h1:PUPnLxbDzRO9kg/03l7TZk7+ywTv7FxmOhDHOtOdOtk=
|
||||
mvdan.cc/sh/v3 v3.12.1-0.20260124232039-e74afc18e65b/go.mod h1:mencVHx2sy9XZG5wJbCA9nRUOE3zvMtoRXOmXMxH7sc=
|
||||
|
||||
@@ -10,6 +10,15 @@ type Copier[T any] interface {
|
||||
DeepCopy() T
|
||||
}
|
||||
|
||||
func Scalar[T any](orig *T) *T {
|
||||
if orig == nil {
|
||||
return nil
|
||||
} else {
|
||||
v := *orig
|
||||
return &v
|
||||
}
|
||||
}
|
||||
|
||||
func Slice[T any](orig []T) []T {
|
||||
if orig == nil {
|
||||
return nil
|
||||
|
||||
@@ -127,12 +127,8 @@ func ExpandFields(s string) ([]string, error) {
|
||||
s = escape(s)
|
||||
p := syntax.NewParser()
|
||||
var words []*syntax.Word
|
||||
err := p.Words(strings.NewReader(s), func(w *syntax.Word) bool {
|
||||
for w := range p.WordsSeq(strings.NewReader(s)) {
|
||||
words = append(words, w)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := &expand.Config{
|
||||
Env: expand.FuncEnviron(os.Getenv),
|
||||
|
||||
@@ -83,6 +83,10 @@ var (
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
RemoteCacheDir string
|
||||
CACert string
|
||||
Cert string
|
||||
CertKey string
|
||||
Interactive bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -132,6 +136,7 @@ func init() {
|
||||
pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.")
|
||||
pflag.BoolVar(&DisableFuzzy, "disable-fuzzy", getConfig(config, func() *bool { return config.DisableFuzzy }, false), "Disables fuzzy matching for task names.")
|
||||
pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.")
|
||||
pflag.BoolVar(&Interactive, "interactive", getConfig(config, func() *bool { return config.Interactive }, false), "Prompt for missing required variables.")
|
||||
pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.")
|
||||
pflag.BoolVarP(&Dry, "dry", "n", false, "Compiles and prints tasks in the order that they would be run, without executing them.")
|
||||
pflag.BoolVar(&Summary, "summary", false, "Show summary about a task.")
|
||||
@@ -166,6 +171,9 @@ func init() {
|
||||
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
|
||||
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
|
||||
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
|
||||
pflag.StringVar(&CACert, "cacert", getConfig(config, func() *string { return config.Remote.CACert }, ""), "Path to a custom CA certificate for HTTPS connections.")
|
||||
pflag.StringVar(&Cert, "cert", getConfig(config, func() *string { return config.Remote.Cert }, ""), "Path to a client certificate for HTTPS connections.")
|
||||
pflag.StringVar(&CertKey, "cert-key", getConfig(config, func() *string { return config.Remote.CertKey }, ""), "Path to a client certificate key for HTTPS connections.")
|
||||
}
|
||||
pflag.Parse()
|
||||
|
||||
@@ -234,6 +242,11 @@ func Validate() error {
|
||||
return errors.New("task: --nested only applies to --json with --list or --list-all")
|
||||
}
|
||||
|
||||
// Validate certificate flags
|
||||
if (Cert != "" && CertKey == "") || (Cert == "" && CertKey != "") {
|
||||
return errors.New("task: --cert and --cert-key must be provided together")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -276,11 +289,15 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
task.WithTimeout(Timeout),
|
||||
task.WithCacheExpiryDuration(CacheExpiryDuration),
|
||||
task.WithRemoteCacheDir(RemoteCacheDir),
|
||||
task.WithCACert(CACert),
|
||||
task.WithCert(Cert),
|
||||
task.WithCertKey(CertKey),
|
||||
task.WithWatch(Watch),
|
||||
task.WithVerbose(Verbose),
|
||||
task.WithSilent(Silent),
|
||||
task.WithDisableFuzzy(DisableFuzzy),
|
||||
task.WithAssumeYes(AssumeYes),
|
||||
task.WithInteractive(Interactive),
|
||||
task.WithDry(Dry || Status),
|
||||
task.WithSummary(Summary),
|
||||
task.WithParallel(Parallel),
|
||||
|
||||
211
internal/input/input.go
Normal file
211
internal/input/input.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"charm.land/bubbles/v2/textinput"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
var ErrCancelled = errors.New("prompt cancelled")
|
||||
|
||||
var (
|
||||
promptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6")).Bold(true) // cyan bold
|
||||
cursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6")).Bold(true) // cyan bold
|
||||
selectedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("2")).Bold(true) // green bold
|
||||
dimStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8")) // gray
|
||||
)
|
||||
|
||||
// Prompter handles interactive variable prompting
|
||||
type Prompter struct {
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
// Text prompts the user for a text value
|
||||
func (p *Prompter) Text(varName string) (string, error) {
|
||||
m := newTextModel(varName)
|
||||
|
||||
prog := tea.NewProgram(m,
|
||||
tea.WithInput(p.Stdin),
|
||||
tea.WithOutput(p.Stderr),
|
||||
)
|
||||
|
||||
result, err := prog.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
model := result.(textModel)
|
||||
if model.cancelled {
|
||||
return "", ErrCancelled
|
||||
}
|
||||
|
||||
return model.value, nil
|
||||
}
|
||||
|
||||
// Select prompts the user to select from a list of options
|
||||
func (p *Prompter) Select(varName string, options []string) (string, error) {
|
||||
if len(options) == 0 {
|
||||
return "", errors.New("no options provided")
|
||||
}
|
||||
|
||||
m := newSelectModel(varName, options)
|
||||
|
||||
prog := tea.NewProgram(m,
|
||||
tea.WithInput(p.Stdin),
|
||||
tea.WithOutput(p.Stderr),
|
||||
)
|
||||
|
||||
result, err := prog.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
model := result.(selectModel)
|
||||
if model.cancelled {
|
||||
return "", ErrCancelled
|
||||
}
|
||||
|
||||
return model.options[model.cursor], nil
|
||||
}
|
||||
|
||||
// Prompt prompts for a variable value, using Select if enum is provided, Text otherwise
|
||||
func (p *Prompter) Prompt(varName string, enum []string) (string, error) {
|
||||
if len(enum) > 0 {
|
||||
return p.Select(varName, enum)
|
||||
}
|
||||
return p.Text(varName)
|
||||
}
|
||||
|
||||
// textModel is the Bubble Tea model for text input
|
||||
type textModel struct {
|
||||
varName string
|
||||
textInput textinput.Model
|
||||
value string
|
||||
cancelled bool
|
||||
done bool
|
||||
}
|
||||
|
||||
func newTextModel(varName string) textModel {
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = ""
|
||||
ti.CharLimit = 256
|
||||
ti.SetWidth(40)
|
||||
ti.Focus()
|
||||
|
||||
return textModel{
|
||||
varName: varName,
|
||||
textInput: ti,
|
||||
}
|
||||
}
|
||||
|
||||
func (m textModel) Init() tea.Cmd {
|
||||
return tea.Batch(m.textInput.Focus(), textinput.Blink)
|
||||
}
|
||||
|
||||
func (m textModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyPressMsg:
|
||||
switch msg.Keystroke() {
|
||||
case "ctrl+c", "escape":
|
||||
m.cancelled = true
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
case "enter":
|
||||
m.value = m.textInput.Value()
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.textInput, cmd = m.textInput.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m textModel) View() tea.View {
|
||||
if m.done {
|
||||
return tea.NewView("")
|
||||
}
|
||||
|
||||
prompt := promptStyle.Render(fmt.Sprintf("? Enter value for %s: ", m.varName))
|
||||
return tea.NewView(prompt + m.textInput.View() + "\n")
|
||||
}
|
||||
|
||||
// selectModel is the Bubble Tea model for selection
|
||||
type selectModel struct {
|
||||
varName string
|
||||
options []string
|
||||
cursor int
|
||||
cancelled bool
|
||||
done bool
|
||||
}
|
||||
|
||||
func newSelectModel(varName string, options []string) selectModel {
|
||||
return selectModel{
|
||||
varName: varName,
|
||||
options: options,
|
||||
cursor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (m selectModel) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m selectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyPressMsg:
|
||||
switch msg.Keystroke() {
|
||||
case "ctrl+c", "escape":
|
||||
m.cancelled = true
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
case "up", "shift+tab", "k":
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
}
|
||||
case "down", "tab", "j":
|
||||
if m.cursor < len(m.options)-1 {
|
||||
m.cursor++
|
||||
}
|
||||
case "enter":
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m selectModel) View() tea.View {
|
||||
if m.done {
|
||||
return tea.NewView("")
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(promptStyle.Render(fmt.Sprintf("? Select value for %s:", m.varName)))
|
||||
b.WriteString("\n")
|
||||
|
||||
for i, opt := range m.options {
|
||||
if i == m.cursor {
|
||||
b.WriteString(cursorStyle.Render("❯ "))
|
||||
b.WriteString(selectedStyle.Render(opt))
|
||||
} else {
|
||||
b.WriteString(" " + opt)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
b.WriteString(dimStyle.Render(" (↑/↓ to move, enter to select, esc to cancel)"))
|
||||
|
||||
return tea.NewView(b.String())
|
||||
}
|
||||
@@ -42,6 +42,12 @@ type (
|
||||
PrintFunc func(io.Writer, string, ...any)
|
||||
)
|
||||
|
||||
func None() PrintFunc {
|
||||
c := color.New()
|
||||
c.DisableColor()
|
||||
return c.FprintfFunc()
|
||||
}
|
||||
|
||||
func Default() PrintFunc {
|
||||
return color.New(attrsReset...).FprintfFunc()
|
||||
}
|
||||
@@ -144,7 +150,7 @@ func (l *Logger) FOutf(w io.Writer, color Color, s string, args ...any) {
|
||||
s, args = "%s", []any{s}
|
||||
}
|
||||
if !l.Color {
|
||||
color = Default
|
||||
color = None
|
||||
}
|
||||
print := color()
|
||||
print(w, s, args...)
|
||||
@@ -163,7 +169,7 @@ func (l *Logger) Errf(color Color, s string, args ...any) {
|
||||
s, args = "%s", []any{s}
|
||||
}
|
||||
if !l.Color {
|
||||
color = Default
|
||||
color = None
|
||||
}
|
||||
print := color()
|
||||
print(l.Stderr, s, args...)
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.46.1
|
||||
3.48.0
|
||||
|
||||
174
requires.go
174
requires.go
@@ -4,35 +4,180 @@ import (
|
||||
"slices"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/input"
|
||||
"github.com/go-task/task/v3/internal/term"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func (e *Executor) areTaskRequiredVarsSet(t *ast.Task) error {
|
||||
if t.Requires == nil || len(t.Requires.Vars) == 0 {
|
||||
func (e *Executor) canPrompt() bool {
|
||||
return e.Interactive && (e.AssumeTerm || term.IsTerminal())
|
||||
}
|
||||
|
||||
func (e *Executor) newPrompter() *input.Prompter {
|
||||
return &input.Prompter{
|
||||
Stdin: e.Stdin,
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
}
|
||||
}
|
||||
|
||||
// promptDepsVars traverses the dependency tree, collects all missing required
|
||||
// variables, and prompts for them upfront. This is used for deps which execute
|
||||
// in parallel, so all prompts must happen before execution to avoid interleaving.
|
||||
// Prompted values are stored in e.promptedVars for injection into task calls.
|
||||
func (e *Executor) promptDepsVars(calls []*Call) error {
|
||||
if !e.canPrompt() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var missingVars []errors.MissingVar
|
||||
for _, requiredVar := range t.Requires.Vars {
|
||||
_, ok := t.Vars.Get(requiredVar.Name)
|
||||
if !ok {
|
||||
missingVars = append(missingVars, errors.MissingVar{
|
||||
Name: requiredVar.Name,
|
||||
AllowedValues: requiredVar.Enum,
|
||||
})
|
||||
// Collect all missing vars from the dependency tree
|
||||
visited := make(map[string]bool)
|
||||
varsMap := make(map[string]*ast.VarsWithValidation)
|
||||
|
||||
var collect func(call *Call) error
|
||||
collect = func(call *Call) error {
|
||||
compiledTask, err := e.FastCompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range getMissingRequiredVars(compiledTask) {
|
||||
if _, exists := varsMap[v.Name]; !exists {
|
||||
varsMap[v.Name] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Check visited AFTER collecting vars to handle duplicate task calls with different vars
|
||||
if visited[call.Task] {
|
||||
return nil
|
||||
}
|
||||
visited[call.Task] = true
|
||||
|
||||
for _, dep := range compiledTask.Deps {
|
||||
depCall := &Call{
|
||||
Task: dep.Task,
|
||||
Vars: dep.Vars,
|
||||
Silent: dep.Silent,
|
||||
}
|
||||
if err := collect(depCall); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, call := range calls {
|
||||
if err := collect(call); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingVars) > 0 {
|
||||
return &errors.TaskMissingRequiredVarsError{
|
||||
TaskName: t.Name(),
|
||||
MissingVars: missingVars,
|
||||
if len(varsMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
prompter := e.newPrompter()
|
||||
e.promptedVars = ast.NewVars()
|
||||
|
||||
for _, v := range varsMap {
|
||||
value, err := prompter.Prompt(v.Name, v.Enum)
|
||||
if err != nil {
|
||||
if errors.Is(err, input.ErrCancelled) {
|
||||
return &errors.TaskCancelledByUserError{TaskName: "interactive prompt"}
|
||||
}
|
||||
return err
|
||||
}
|
||||
e.promptedVars.Set(v.Name, ast.Var{Value: value})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// promptTaskVars prompts for any missing required vars from a single task.
|
||||
// Used for sequential task calls (cmds) where we can prompt just-in-time.
|
||||
// Returns true if any vars were prompted (caller should recompile the task).
|
||||
func (e *Executor) promptTaskVars(t *ast.Task, call *Call) (bool, error) {
|
||||
if !e.canPrompt() || t.Requires == nil || len(t.Requires.Vars) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Find missing vars, excluding already prompted ones
|
||||
var missing []*ast.VarsWithValidation
|
||||
for _, v := range getMissingRequiredVars(t) {
|
||||
if e.promptedVars != nil {
|
||||
if _, ok := e.promptedVars.Get(v.Name); ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
missing = append(missing, v)
|
||||
}
|
||||
|
||||
if len(missing) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
prompter := e.newPrompter()
|
||||
|
||||
for _, v := range missing {
|
||||
value, err := prompter.Prompt(v.Name, v.Enum)
|
||||
if err != nil {
|
||||
if errors.Is(err, input.ErrCancelled) {
|
||||
return false, &errors.TaskCancelledByUserError{TaskName: t.Name()}
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Add to call.Vars for recompilation
|
||||
if call.Vars == nil {
|
||||
call.Vars = ast.NewVars()
|
||||
}
|
||||
call.Vars.Set(v.Name, ast.Var{Value: value})
|
||||
|
||||
// Cache for reuse by other tasks
|
||||
if e.promptedVars == nil {
|
||||
e.promptedVars = ast.NewVars()
|
||||
}
|
||||
e.promptedVars.Set(v.Name, ast.Var{Value: value})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// getMissingRequiredVars returns required vars that are not set in the task's vars.
|
||||
func getMissingRequiredVars(t *ast.Task) []*ast.VarsWithValidation {
|
||||
if t.Requires == nil {
|
||||
return nil
|
||||
}
|
||||
var missing []*ast.VarsWithValidation
|
||||
for _, v := range t.Requires.Vars {
|
||||
if _, ok := t.Vars.Get(v.Name); !ok {
|
||||
missing = append(missing, v)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
}
|
||||
|
||||
func (e *Executor) areTaskRequiredVarsSet(t *ast.Task) error {
|
||||
missing := getMissingRequiredVars(t)
|
||||
if len(missing) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
missingVars := make([]errors.MissingVar, len(missing))
|
||||
for i, v := range missing {
|
||||
missingVars[i] = errors.MissingVar{
|
||||
Name: v.Name,
|
||||
AllowedValues: v.Enum,
|
||||
}
|
||||
}
|
||||
|
||||
return &errors.TaskMissingRequiredVarsError{
|
||||
TaskName: t.Name(),
|
||||
MissingVars: missingVars,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) areTaskRequiredVarsAllowedValuesSet(t *ast.Task) error {
|
||||
if t.Requires == nil || len(t.Requires.Vars) == 0 {
|
||||
return nil
|
||||
@@ -50,7 +195,6 @@ func (e *Executor) areTaskRequiredVarsAllowedValuesSet(t *ast.Task) error {
|
||||
Name: requiredVar.Name,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(notAllowedValuesVars) > 0 {
|
||||
|
||||
10
setup.go
10
setup.go
@@ -55,7 +55,11 @@ func (e *Executor) Setup() error {
|
||||
}
|
||||
|
||||
func (e *Executor) getRootNode() (taskfile.Node, error) {
|
||||
node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout)
|
||||
node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout,
|
||||
taskfile.WithCACert(e.CACert),
|
||||
taskfile.WithCert(e.Cert),
|
||||
taskfile.WithCertKey(e.CertKey),
|
||||
)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.TaskfileNotFoundError{
|
||||
URI: fsext.DefaultDir(e.Entrypoint, e.Dir),
|
||||
@@ -67,6 +71,7 @@ func (e *Executor) getRootNode() (taskfile.Node, error) {
|
||||
return nil, err
|
||||
}
|
||||
e.Dir = node.Dir()
|
||||
e.Entrypoint = node.Location()
|
||||
return node, err
|
||||
}
|
||||
|
||||
@@ -86,6 +91,9 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
|
||||
taskfile.WithTrustedHosts(e.TrustedHosts),
|
||||
taskfile.WithTempDir(e.TempDir.Remote),
|
||||
taskfile.WithCacheExpiryDuration(e.CacheExpiryDuration),
|
||||
taskfile.WithReaderCACert(e.CACert),
|
||||
taskfile.WithReaderCert(e.Cert),
|
||||
taskfile.WithReaderCertKey(e.CertKey),
|
||||
taskfile.WithDebugFunc(debugFunc),
|
||||
taskfile.WithPromptFunc(promptFunc),
|
||||
)
|
||||
|
||||
153
task.go
153
task.go
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -73,6 +74,11 @@ func (e *Executor) Run(ctx context.Context, calls ...*Call) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prompt for all required vars from deps upfront (parallel execution)
|
||||
if err := e.promptDepsVars(calls); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
regularCalls, watchCalls, err := e.splitRegularAndWatchCalls(calls...)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -120,6 +126,19 @@ func (e *Executor) splitRegularAndWatchCalls(calls ...*Call) (regularCalls []*Ca
|
||||
|
||||
// RunTask runs a task by its name
|
||||
func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
// Inject prompted vars into call if available
|
||||
if e.promptedVars != nil {
|
||||
if call.Vars == nil {
|
||||
call.Vars = ast.NewVars()
|
||||
}
|
||||
for name, v := range e.promptedVars.All() {
|
||||
// Only inject if not already set in call
|
||||
if _, ok := call.Vars.Get(name); !ok {
|
||||
call.Vars.Set(name, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t, err := e.FastCompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -129,8 +148,12 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := e.areTaskRequiredVarsSet(t); err != nil {
|
||||
return err
|
||||
// Check required vars early (before template compilation) if we can't prompt.
|
||||
// This gives a clear "missing required variables" error instead of a template error.
|
||||
if !e.canPrompt() {
|
||||
if err := e.areTaskRequiredVarsSet(t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
t, err = e.CompiledTask(call)
|
||||
@@ -138,6 +161,35 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if condition after CompiledTask so dynamic variables are resolved
|
||||
if strings.TrimSpace(t.If) != "" {
|
||||
if err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: t.If,
|
||||
Dir: t.Dir,
|
||||
Env: env.Get(t),
|
||||
}); err != nil {
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: if condition not met - skipped: %q\n", call.Task)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Prompt for missing required vars after if check (avoid prompting if task won't run)
|
||||
prompted, err := e.promptTaskVars(t, call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if prompted {
|
||||
// Recompile with the new vars
|
||||
t, err = e.FastCompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.areTaskRequiredVarsSet(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.areTaskRequiredVarsAllowedValuesSet(t); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -185,8 +237,12 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
}
|
||||
|
||||
if upToDate && preCondMet {
|
||||
if e.Verbose || (!call.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
|
||||
e.Logger.Errf(logger.Magenta, "task: Task %q is up to date\n", t.Name())
|
||||
if e.Verbose || (!call.Silent && !t.IsSilent() && !e.Taskfile.Silent && !e.Silent) {
|
||||
name := t.Name()
|
||||
if e.OutputStyle.Name == "prefixed" {
|
||||
name = t.Prefix
|
||||
}
|
||||
e.Logger.Errf(logger.Magenta, "task: Task %q is up to date\n", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -295,6 +351,7 @@ func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, d
|
||||
|
||||
cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
|
||||
cmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
|
||||
cmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra)
|
||||
cmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
|
||||
|
||||
if err := e.runCommand(ctx, t, call, i); err != nil {
|
||||
@@ -305,6 +362,18 @@ func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, d
|
||||
func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i int) error {
|
||||
cmd := t.Cmds[i]
|
||||
|
||||
// Check if condition for any command type
|
||||
if strings.TrimSpace(cmd.If) != "" {
|
||||
if err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: cmd.If,
|
||||
Dir: t.Dir,
|
||||
Env: env.Get(t),
|
||||
}); err != nil {
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: [%s] if condition not met - skipped\n", t.Name())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case cmd.Task != "":
|
||||
reacquire := e.releaseConcurrencyLimit()
|
||||
@@ -323,7 +392,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
|
||||
return nil
|
||||
}
|
||||
|
||||
if e.Verbose || (!call.Silent && !cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
|
||||
if e.Verbose || (!call.Silent && !cmd.Silent && !t.IsSilent() && !e.Taskfile.Silent && !e.Silent) {
|
||||
e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmd.Cmd)
|
||||
}
|
||||
|
||||
@@ -400,19 +469,40 @@ func (e *Executor) startExecution(ctx context.Context, t *ast.Task, execute func
|
||||
}
|
||||
|
||||
// FindMatchingTasks returns a list of tasks that match the given call. A task
|
||||
// matches a call if its name is equal to the call's task name or if it matches
|
||||
// matches a call if its name is equal to the call's task name, or one of aliases, or if it matches
|
||||
// a wildcard pattern. The function returns a list of MatchingTask structs, each
|
||||
// containing a task and a list of wildcards that were matched.
|
||||
func (e *Executor) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
// If multiple tasks match due to aliases, a TaskNameConflictError is returned.
|
||||
func (e *Executor) FindMatchingTasks(call *Call) ([]*MatchingTask, error) {
|
||||
if call == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
var matchingTasks []*MatchingTask
|
||||
// If there is a direct match, return it
|
||||
if task, ok := e.Taskfile.Tasks.Get(call.Task); ok {
|
||||
matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil})
|
||||
return matchingTasks
|
||||
return matchingTasks, nil
|
||||
}
|
||||
var aliasedTasks []string
|
||||
for task := range e.Taskfile.Tasks.Values(nil) {
|
||||
if slices.Contains(task.Aliases, call.Task) {
|
||||
aliasedTasks = append(aliasedTasks, task.Task)
|
||||
matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil})
|
||||
}
|
||||
}
|
||||
|
||||
if len(aliasedTasks) == 1 {
|
||||
return matchingTasks, nil
|
||||
}
|
||||
|
||||
// If we found multiple tasks
|
||||
if len(aliasedTasks) > 1 {
|
||||
return nil, &errors.TaskNameConflictError{
|
||||
Call: call.Task,
|
||||
TaskNames: aliasedTasks,
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt a wildcard match
|
||||
for _, value := range e.Taskfile.Tasks.All(nil) {
|
||||
if match, wildcards := value.WildcardMatch(call.Task); match {
|
||||
@@ -422,7 +512,7 @@ func (e *Executor) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
})
|
||||
}
|
||||
}
|
||||
return matchingTasks
|
||||
return matchingTasks, nil
|
||||
}
|
||||
|
||||
// GetTask will return the task with the name matching the given call from the taskfile.
|
||||
@@ -430,7 +520,11 @@ func (e *Executor) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
// If multiple tasks contain the same alias or no matches are found an error is returned.
|
||||
func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
|
||||
// Search for a matching task
|
||||
matchingTasks := e.FindMatchingTasks(call)
|
||||
matchingTasks, err := e.FindMatchingTasks(call)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(matchingTasks) > 0 {
|
||||
if call.Vars == nil {
|
||||
call.Vars = ast.NewVars()
|
||||
@@ -439,37 +533,18 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
|
||||
return matchingTasks[0].Task, nil
|
||||
}
|
||||
|
||||
// If didn't find one, search for a task with a matching alias
|
||||
var matchingTask *ast.Task
|
||||
var aliasedTasks []string
|
||||
for task := range e.Taskfile.Tasks.Values(nil) {
|
||||
if slices.Contains(task.Aliases, call.Task) {
|
||||
aliasedTasks = append(aliasedTasks, task.Task)
|
||||
matchingTask = task
|
||||
}
|
||||
}
|
||||
// If we found multiple tasks
|
||||
if len(aliasedTasks) > 1 {
|
||||
return nil, &errors.TaskNameConflictError{
|
||||
Call: call.Task,
|
||||
TaskNames: aliasedTasks,
|
||||
}
|
||||
}
|
||||
// If we found no tasks
|
||||
if len(aliasedTasks) == 0 {
|
||||
didYouMean := ""
|
||||
if !e.DisableFuzzy {
|
||||
e.fuzzyModelOnce.Do(e.setupFuzzyModel)
|
||||
if e.fuzzyModel != nil {
|
||||
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
||||
}
|
||||
}
|
||||
return nil, &errors.TaskNotFoundError{
|
||||
TaskName: call.Task,
|
||||
DidYouMean: didYouMean,
|
||||
didYouMean := ""
|
||||
if !e.DisableFuzzy {
|
||||
e.fuzzyModelOnce.Do(e.setupFuzzyModel)
|
||||
if e.fuzzyModel != nil {
|
||||
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
||||
}
|
||||
}
|
||||
return matchingTask, nil
|
||||
return nil, &errors.TaskNotFoundError{
|
||||
TaskName: call.Task,
|
||||
DidYouMean: didYouMean,
|
||||
}
|
||||
}
|
||||
|
||||
type FilterFunc func(task *ast.Task) bool
|
||||
|
||||
@@ -2612,6 +2612,11 @@ func TestWildcard(t *testing.T) {
|
||||
call: "start-foo",
|
||||
expectedOutput: "Starting foo\n",
|
||||
},
|
||||
{
|
||||
name: "alias",
|
||||
call: "s-foo",
|
||||
expectedOutput: "Starting foo\n",
|
||||
},
|
||||
{
|
||||
name: "matches exactly",
|
||||
call: "matches-exactly-*",
|
||||
|
||||
@@ -12,6 +12,7 @@ type Cmd struct {
|
||||
Cmd string
|
||||
Task string
|
||||
For *For
|
||||
If string
|
||||
Silent bool
|
||||
Set []string
|
||||
Shopt []string
|
||||
@@ -29,6 +30,7 @@ func (c *Cmd) DeepCopy() *Cmd {
|
||||
Cmd: c.Cmd,
|
||||
Task: c.Task,
|
||||
For: c.For.DeepCopy(),
|
||||
If: c.If,
|
||||
Silent: c.Silent,
|
||||
Set: deepcopy.Slice(c.Set),
|
||||
Shopt: deepcopy.Slice(c.Shopt),
|
||||
@@ -55,6 +57,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
Cmd string
|
||||
Task string
|
||||
For *For
|
||||
If string
|
||||
Silent bool
|
||||
Set []string
|
||||
Shopt []string
|
||||
@@ -92,6 +95,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
c.Task = cmdStruct.Task
|
||||
c.Vars = cmdStruct.Vars
|
||||
c.For = cmdStruct.For
|
||||
c.If = cmdStruct.If
|
||||
c.Silent = cmdStruct.Silent
|
||||
c.IgnoreError = cmdStruct.IgnoreError
|
||||
return nil
|
||||
@@ -101,6 +105,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
if cmdStruct.Cmd != "" {
|
||||
c.Cmd = cmdStruct.Cmd
|
||||
c.For = cmdStruct.For
|
||||
c.If = cmdStruct.If
|
||||
c.Silent = cmdStruct.Silent
|
||||
c.Set = cmdStruct.Set
|
||||
c.Shopt = cmdStruct.Shopt
|
||||
|
||||
@@ -32,7 +32,7 @@ type Task struct {
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Silent *bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
@@ -40,6 +40,7 @@ type Task struct {
|
||||
IgnoreError bool
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
If string
|
||||
Watch bool
|
||||
Location *Location
|
||||
Failfast bool
|
||||
@@ -68,28 +69,37 @@ func (t *Task) LocalName() string {
|
||||
return name
|
||||
}
|
||||
|
||||
// IsSilent returns true if the task has silent mode explicitly enabled.
|
||||
// Returns false if Silent is nil (not set) or explicitly set to false.
|
||||
func (t *Task) IsSilent() bool {
|
||||
return t.Silent != nil && *t.Silent
|
||||
}
|
||||
|
||||
// WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values.
|
||||
func (t *Task) WildcardMatch(name string) (bool, []string) {
|
||||
// Convert the name into a regex string
|
||||
regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(t.Task, "*", "(.*)"))
|
||||
regex := regexp.MustCompile(regexStr)
|
||||
wildcards := regex.FindStringSubmatch(name)
|
||||
wildcardCount := strings.Count(t.Task, "*")
|
||||
names := append([]string{t.Task}, t.Aliases...)
|
||||
|
||||
// If there are no wildcards, return false
|
||||
if len(wildcards) == 0 {
|
||||
return false, nil
|
||||
for _, taskName := range names {
|
||||
regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(taskName, "*", "(.*)"))
|
||||
regex := regexp.MustCompile(regexStr)
|
||||
wildcards := regex.FindStringSubmatch(name)
|
||||
|
||||
if len(wildcards) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove the first match, which is the full string
|
||||
wildcards = wildcards[1:]
|
||||
wildcardCount := strings.Count(taskName, "*")
|
||||
|
||||
if len(wildcards) != wildcardCount {
|
||||
continue
|
||||
}
|
||||
|
||||
return true, wildcards
|
||||
}
|
||||
|
||||
// Remove the first match, which is the full string
|
||||
wildcards = wildcards[1:]
|
||||
|
||||
// If there are more/less wildcards than matches, return false
|
||||
if len(wildcards) != wildcardCount {
|
||||
return false, wildcards
|
||||
}
|
||||
|
||||
return true, wildcards
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
@@ -134,7 +144,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Silent *bool `yaml:"silent,omitempty"`
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
@@ -142,6 +152,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
If string
|
||||
Requires *Requires
|
||||
Watch bool
|
||||
Failfast bool
|
||||
@@ -173,7 +184,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
t.Vars = task.Vars
|
||||
t.Env = task.Env
|
||||
t.Dotenv = task.Dotenv
|
||||
t.Silent = task.Silent
|
||||
t.Silent = deepcopy.Scalar(task.Silent)
|
||||
t.Interactive = task.Interactive
|
||||
t.Internal = task.Internal
|
||||
t.Method = task.Method
|
||||
@@ -181,6 +192,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
t.IgnoreError = task.IgnoreError
|
||||
t.Run = task.Run
|
||||
t.Platforms = task.Platforms
|
||||
t.If = task.If
|
||||
t.Requires = task.Requires
|
||||
t.Watch = task.Watch
|
||||
t.Failfast = task.Failfast
|
||||
@@ -215,7 +227,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
Vars: t.Vars.DeepCopy(),
|
||||
Env: t.Env.DeepCopy(),
|
||||
Dotenv: deepcopy.Slice(t.Dotenv),
|
||||
Silent: t.Silent,
|
||||
Silent: deepcopy.Scalar(t.Silent),
|
||||
Interactive: t.Interactive,
|
||||
Internal: t.Internal,
|
||||
Method: t.Method,
|
||||
@@ -225,6 +237,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
IncludeVars: t.IncludeVars.DeepCopy(),
|
||||
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
||||
Platforms: deepcopy.Slice(t.Platforms),
|
||||
If: t.If,
|
||||
Location: t.Location.DeepCopy(),
|
||||
Requires: t.Requires.DeepCopy(),
|
||||
Namespace: t.Namespace,
|
||||
|
||||
@@ -59,6 +59,14 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
||||
if t1.Tasks == nil {
|
||||
t1.Tasks = NewTasks()
|
||||
}
|
||||
if t2.Silent {
|
||||
for _, t := range t2.Tasks.All(nil) {
|
||||
if t.Silent == nil {
|
||||
v := true
|
||||
t.Silent = &v
|
||||
}
|
||||
}
|
||||
}
|
||||
t1.Vars.Merge(t2.Vars, include)
|
||||
t1.Env.Merge(t2.Env, include)
|
||||
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
|
||||
|
||||
@@ -34,13 +34,14 @@ func NewRootNode(
|
||||
dir string,
|
||||
insecure bool,
|
||||
timeout time.Duration,
|
||||
opts ...NodeOption,
|
||||
) (Node, error) {
|
||||
dir = fsext.DefaultDir(entrypoint, dir)
|
||||
// If the entrypoint is "-", we read from stdin
|
||||
if entrypoint == "-" {
|
||||
return NewStdinNode(dir)
|
||||
}
|
||||
return NewNode(entrypoint, dir, insecure)
|
||||
return NewNode(entrypoint, dir, insecure, opts...)
|
||||
}
|
||||
|
||||
func NewNode(
|
||||
|
||||
@@ -10,6 +10,9 @@ type (
|
||||
parent Node
|
||||
dir string
|
||||
checksum string
|
||||
caCert string
|
||||
cert string
|
||||
certKey string
|
||||
}
|
||||
)
|
||||
|
||||
@@ -54,3 +57,21 @@ func (node *baseNode) Checksum() string {
|
||||
func (node *baseNode) Verify(checksum string) bool {
|
||||
return node.checksum == "" || node.checksum == checksum
|
||||
}
|
||||
|
||||
func WithCACert(caCert string) NodeOption {
|
||||
return func(node *baseNode) {
|
||||
node.caCert = caCert
|
||||
}
|
||||
}
|
||||
|
||||
func WithCert(cert string) NodeOption {
|
||||
return func(node *baseNode) {
|
||||
node.cert = cert
|
||||
}
|
||||
}
|
||||
|
||||
func WithCertKey(certKey string) NodeOption {
|
||||
return func(node *baseNode) {
|
||||
node.certKey = certKey
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/fsext"
|
||||
)
|
||||
|
||||
// An GitNode is a node that reads a Taskfile from a remote location via Git.
|
||||
@@ -104,13 +105,13 @@ func (node *GitNode) buildURL() string {
|
||||
// Get the base URL
|
||||
baseURL := node.url.String()
|
||||
|
||||
ref := node.ref
|
||||
if ref == "" {
|
||||
ref = "HEAD"
|
||||
}
|
||||
// Always use git:: prefix for git URLs (following Terraform's pattern)
|
||||
// This forces go-getter to use git protocol
|
||||
return fmt.Sprintf("git::%s?ref=%s&depth=1", baseURL, ref)
|
||||
if node.ref != "" {
|
||||
return fmt.Sprintf("git::%s?ref=%s&depth=1", baseURL, node.ref)
|
||||
}
|
||||
// When no ref is specified, omit it entirely to let git clone the default branch
|
||||
return fmt.Sprintf("git::%s?depth=1", baseURL)
|
||||
}
|
||||
|
||||
// getOrCloneRepo returns the path to a cached git repository.
|
||||
@@ -164,11 +165,16 @@ func (node *GitNode) ReadContext(ctx context.Context) ([]byte, error) {
|
||||
}
|
||||
|
||||
// Build path to Taskfile in the cached repo
|
||||
taskfilePath := node.path
|
||||
if taskfilePath == "" {
|
||||
taskfilePath = "Taskfile.yml"
|
||||
// If node.path is empty, search in repo root; otherwise search in the specified path
|
||||
// fsext.SearchPath handles both files and directories (searching for DefaultTaskfiles)
|
||||
searchPath := repoDir
|
||||
if node.path != "" {
|
||||
searchPath = filepath.Join(repoDir, node.path)
|
||||
}
|
||||
filePath, err := fsext.SearchPath(searchPath, DefaultTaskfiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filePath := filepath.Join(repoDir, taskfilePath)
|
||||
|
||||
// Read file from cached repo
|
||||
b, err := os.ReadFile(filePath)
|
||||
@@ -230,7 +236,7 @@ func (node *GitNode) repoCacheKey() string {
|
||||
|
||||
ref := node.ref
|
||||
if ref == "" {
|
||||
ref = "HEAD"
|
||||
ref = "_default_" // Placeholder for the remote's default branch
|
||||
}
|
||||
|
||||
return filepath.Join(node.url.Host, repoPath, ref)
|
||||
|
||||
@@ -127,9 +127,9 @@ func TestGitNode_buildURL(t *testing.T) {
|
||||
expectedURL: "git::https://github.com/foo/bar.git?ref=v1.0.0&depth=1",
|
||||
},
|
||||
{
|
||||
name: "HTTPS without ref (uses remote HEAD)",
|
||||
name: "HTTPS without ref (uses remote default branch)",
|
||||
entrypoint: "https://github.com/foo/bar.git//Taskfile.yml",
|
||||
expectedURL: "git::https://github.com/foo/bar.git?ref=HEAD&depth=1",
|
||||
expectedURL: "git::https://github.com/foo/bar.git?depth=1",
|
||||
},
|
||||
{
|
||||
name: "SSH with directory path",
|
||||
@@ -198,20 +198,20 @@ func TestRepoCacheKey_DifferentRepos(t *testing.T) {
|
||||
assert.NotEqual(t, key1, key2, "Different repos should generate different cache keys")
|
||||
}
|
||||
|
||||
func TestRepoCacheKey_NoRefVsHead(t *testing.T) {
|
||||
func TestRepoCacheKey_NoRefVsExplicitRef(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// No ref (defaults to HEAD) vs explicit HEAD should have SAME cache key
|
||||
// No ref (uses default branch) vs explicit ref should have DIFFERENT cache keys
|
||||
node1, err := NewGitNode("https://github.com/foo/bar.git//file.yml", "", false)
|
||||
require.NoError(t, err)
|
||||
|
||||
node2, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=HEAD", "", false)
|
||||
node2, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=main", "", false)
|
||||
require.NoError(t, err)
|
||||
|
||||
key1 := node1.repoCacheKey()
|
||||
key2 := node2.repoCacheKey()
|
||||
|
||||
assert.Equal(t, key1, key2, "No ref and explicit HEAD should generate same cache key")
|
||||
assert.NotEqual(t, key1, key2, "No ref and explicit ref should generate different cache keys")
|
||||
}
|
||||
|
||||
func TestRepoCacheKey_SSHvsHTTPS(t *testing.T) {
|
||||
|
||||
@@ -2,10 +2,13 @@ package taskfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -17,7 +20,54 @@ import (
|
||||
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
|
||||
type HTTPNode struct {
|
||||
*baseNode
|
||||
url *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml)
|
||||
url *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml)
|
||||
client *http.Client // HTTP client with optional TLS configuration
|
||||
}
|
||||
|
||||
// buildHTTPClient creates an HTTP client with optional TLS configuration.
|
||||
// If no certificate options are provided, it returns http.DefaultClient.
|
||||
func buildHTTPClient(insecure bool, caCert, cert, certKey string) (*http.Client, error) {
|
||||
// Validate that cert and certKey are provided together
|
||||
if (cert != "" && certKey == "") || (cert == "" && certKey != "") {
|
||||
return nil, fmt.Errorf("both --cert and --cert-key must be provided together")
|
||||
}
|
||||
|
||||
// If no TLS customization is needed, return the default client
|
||||
if !insecure && caCert == "" && cert == "" {
|
||||
return http.DefaultClient, nil
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: insecure,
|
||||
}
|
||||
|
||||
// Load custom CA certificate if provided
|
||||
if caCert != "" {
|
||||
caCertData, err := os.ReadFile(caCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read CA certificate: %w", err)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM(caCertData) {
|
||||
return nil, fmt.Errorf("failed to parse CA certificate")
|
||||
}
|
||||
tlsConfig.RootCAs = caCertPool
|
||||
}
|
||||
|
||||
// Load client certificate and key if provided
|
||||
if cert != "" && certKey != "" {
|
||||
clientCert, err := tls.LoadX509KeyPair(cert, certKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load client certificate: %w", err)
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{clientCert}
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewHTTPNode(
|
||||
@@ -34,9 +84,16 @@ func NewHTTPNode(
|
||||
if url.Scheme == "http" && !insecure {
|
||||
return nil, &errors.TaskfileNotSecureError{URI: url.Redacted()}
|
||||
}
|
||||
|
||||
client, err := buildHTTPClient(insecure, base.caCert, base.cert, base.certKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &HTTPNode{
|
||||
baseNode: base,
|
||||
url: url,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -49,7 +106,7 @@ func (node *HTTPNode) Read() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (node *HTTPNode) ReadContext(ctx context.Context) ([]byte, error) {
|
||||
url, err := RemoteExists(ctx, *node.url)
|
||||
url, err := RemoteExists(ctx, *node.url, node.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -58,7 +115,7 @@ func (node *HTTPNode) ReadContext(ctx context.Context) ([]byte, error) {
|
||||
return nil, errors.TaskfileFetchFailedError{URI: node.Location()}
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||
resp, err := node.client.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -47,3 +58,227 @@ func TestHTTPNode_CacheKey(t *testing.T) {
|
||||
assert.Equal(t, tt.expectedKey, key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHTTPClient_Default(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// When no TLS customization is needed, should return http.DefaultClient
|
||||
client, err := buildHTTPClient(false, "", "", "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.DefaultClient, client)
|
||||
}
|
||||
|
||||
func TestBuildHTTPClient_Insecure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, err := buildHTTPClient(true, "", "", "")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, client)
|
||||
assert.NotEqual(t, http.DefaultClient, client)
|
||||
|
||||
// Check that InsecureSkipVerify is set
|
||||
transport, ok := client.Transport.(*http.Transport)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, transport.TLSClientConfig)
|
||||
assert.True(t, transport.TLSClientConfig.InsecureSkipVerify)
|
||||
}
|
||||
|
||||
func TestBuildHTTPClient_CACert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a temporary CA cert file
|
||||
tempDir := t.TempDir()
|
||||
caCertPath := filepath.Join(tempDir, "ca.crt")
|
||||
|
||||
// Generate a valid CA certificate
|
||||
caCertPEM := generateTestCACert(t)
|
||||
err := os.WriteFile(caCertPath, caCertPEM, 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := buildHTTPClient(false, caCertPath, "", "")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, client)
|
||||
assert.NotEqual(t, http.DefaultClient, client)
|
||||
|
||||
// Check that custom RootCAs is set
|
||||
transport, ok := client.Transport.(*http.Transport)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, transport.TLSClientConfig)
|
||||
assert.NotNil(t, transport.TLSClientConfig.RootCAs)
|
||||
}
|
||||
|
||||
func TestBuildHTTPClient_CACertNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, err := buildHTTPClient(false, "/nonexistent/ca.crt", "", "")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, client)
|
||||
assert.Contains(t, err.Error(), "failed to read CA certificate")
|
||||
}
|
||||
|
||||
func TestBuildHTTPClient_CACertInvalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a temporary file with invalid content
|
||||
tempDir := t.TempDir()
|
||||
caCertPath := filepath.Join(tempDir, "invalid.crt")
|
||||
err := os.WriteFile(caCertPath, []byte("not a valid certificate"), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := buildHTTPClient(false, caCertPath, "", "")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, client)
|
||||
assert.Contains(t, err.Error(), "failed to parse CA certificate")
|
||||
}
|
||||
|
||||
func TestBuildHTTPClient_CertWithoutKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, err := buildHTTPClient(false, "", "/path/to/cert.crt", "")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, client)
|
||||
assert.Contains(t, err.Error(), "both --cert and --cert-key must be provided together")
|
||||
}
|
||||
|
||||
func TestBuildHTTPClient_KeyWithoutCert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, err := buildHTTPClient(false, "", "", "/path/to/key.pem")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, client)
|
||||
assert.Contains(t, err.Error(), "both --cert and --cert-key must be provided together")
|
||||
}
|
||||
|
||||
func TestBuildHTTPClient_CertAndKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create temporary cert and key files
|
||||
tempDir := t.TempDir()
|
||||
certPath := filepath.Join(tempDir, "client.crt")
|
||||
keyPath := filepath.Join(tempDir, "client.key")
|
||||
|
||||
// Generate a self-signed certificate and key for testing
|
||||
cert, key := generateTestCertAndKey(t)
|
||||
err := os.WriteFile(certPath, cert, 0o600)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(keyPath, key, 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
client, err := buildHTTPClient(false, "", certPath, keyPath)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, client)
|
||||
assert.NotEqual(t, http.DefaultClient, client)
|
||||
|
||||
// Check that client certificate is set
|
||||
transport, ok := client.Transport.(*http.Transport)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, transport.TLSClientConfig)
|
||||
assert.Len(t, transport.TLSClientConfig.Certificates, 1)
|
||||
}
|
||||
|
||||
func TestBuildHTTPClient_CertNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, err := buildHTTPClient(false, "", "/nonexistent/cert.crt", "/nonexistent/key.pem")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, client)
|
||||
assert.Contains(t, err.Error(), "failed to load client certificate")
|
||||
}
|
||||
|
||||
func TestBuildHTTPClient_InsecureWithCACert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a temporary CA cert file
|
||||
tempDir := t.TempDir()
|
||||
caCertPath := filepath.Join(tempDir, "ca.crt")
|
||||
|
||||
// Generate a valid CA certificate
|
||||
caCertPEM := generateTestCACert(t)
|
||||
err := os.WriteFile(caCertPath, caCertPEM, 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Both insecure and CA cert can be set together
|
||||
client, err := buildHTTPClient(true, caCertPath, "", "")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, client)
|
||||
|
||||
transport, ok := client.Transport.(*http.Transport)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, transport.TLSClientConfig)
|
||||
assert.True(t, transport.TLSClientConfig.InsecureSkipVerify)
|
||||
assert.NotNil(t, transport.TLSClientConfig.RootCAs)
|
||||
}
|
||||
|
||||
// generateTestCertAndKey generates a self-signed certificate and key for testing
|
||||
func generateTestCertAndKey(t *testing.T) (certPEM, keyPEM []byte) {
|
||||
t.Helper()
|
||||
|
||||
// Generate a new ECDSA private key
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a certificate template
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Task Org"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
// Create the certificate
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Encode certificate to PEM
|
||||
certPEM = pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certDER,
|
||||
})
|
||||
|
||||
// Encode private key to PEM
|
||||
keyDER, err := x509.MarshalECPrivateKey(privateKey)
|
||||
require.NoError(t, err)
|
||||
keyPEM = pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: keyDER,
|
||||
})
|
||||
|
||||
return certPEM, keyPEM
|
||||
}
|
||||
|
||||
// generateTestCACert generates a self-signed CA certificate for testing
|
||||
func generateTestCACert(t *testing.T) []byte {
|
||||
t.Helper()
|
||||
|
||||
// Generate a new ECDSA private key
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a CA certificate template
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Test CA"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
IsCA: true,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
// Create the certificate
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Encode certificate to PEM
|
||||
return pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certDER,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ type (
|
||||
trustedHosts []string
|
||||
tempDir string
|
||||
cacheExpiryDuration time.Duration
|
||||
caCert string
|
||||
cert string
|
||||
certKey string
|
||||
debugFunc DebugFunc
|
||||
promptFunc PromptFunc
|
||||
promptMutex sync.Mutex
|
||||
@@ -199,6 +202,45 @@ func (o *promptFuncOption) ApplyToReader(r *Reader) {
|
||||
r.promptFunc = o.promptFunc
|
||||
}
|
||||
|
||||
// WithReaderCACert sets the path to a custom CA certificate for TLS connections.
|
||||
func WithReaderCACert(caCert string) ReaderOption {
|
||||
return &readerCACertOption{caCert: caCert}
|
||||
}
|
||||
|
||||
type readerCACertOption struct {
|
||||
caCert string
|
||||
}
|
||||
|
||||
func (o *readerCACertOption) ApplyToReader(r *Reader) {
|
||||
r.caCert = o.caCert
|
||||
}
|
||||
|
||||
// WithReaderCert sets the path to a client certificate for TLS connections.
|
||||
func WithReaderCert(cert string) ReaderOption {
|
||||
return &readerCertOption{cert: cert}
|
||||
}
|
||||
|
||||
type readerCertOption struct {
|
||||
cert string
|
||||
}
|
||||
|
||||
func (o *readerCertOption) ApplyToReader(r *Reader) {
|
||||
r.cert = o.cert
|
||||
}
|
||||
|
||||
// WithReaderCertKey sets the path to a client certificate key for TLS connections.
|
||||
func WithReaderCertKey(certKey string) ReaderOption {
|
||||
return &readerCertKeyOption{certKey: certKey}
|
||||
}
|
||||
|
||||
type readerCertKeyOption struct {
|
||||
certKey string
|
||||
}
|
||||
|
||||
func (o *readerCertKeyOption) ApplyToReader(r *Reader) {
|
||||
r.certKey = o.certKey
|
||||
}
|
||||
|
||||
// Read will read the Taskfile defined by the [Reader]'s [Node] and recurse
|
||||
// through any [ast.Includes] it finds, reading each included Taskfile and
|
||||
// building an [ast.TaskfileGraph] as it goes. If any errors occur, they will be
|
||||
@@ -314,6 +356,9 @@ func (r *Reader) include(ctx context.Context, node Node) error {
|
||||
includeNode, err := NewNode(entrypoint, include.Dir, r.insecure,
|
||||
WithParent(node),
|
||||
WithChecksum(include.Checksum),
|
||||
WithCACert(r.caCert),
|
||||
WithCert(r.cert),
|
||||
WithCertKey(r.certKey),
|
||||
)
|
||||
if err != nil {
|
||||
if include.Optional {
|
||||
|
||||
@@ -38,7 +38,7 @@ var (
|
||||
// at the given URL with any of the default Taskfile files names. If any of
|
||||
// these match a file, the first matching path will be returned. If no files are
|
||||
// found, an error will be returned.
|
||||
func RemoteExists(ctx context.Context, u url.URL) (*url.URL, error) {
|
||||
func RemoteExists(ctx context.Context, u url.URL, client *http.Client) (*url.URL, error) {
|
||||
// Create a new HEAD request for the given URL to check if the resource exists
|
||||
req, err := http.NewRequestWithContext(ctx, "HEAD", u.String(), nil)
|
||||
if err != nil {
|
||||
@@ -46,7 +46,7 @@ func RemoteExists(ctx context.Context, u url.URL) (*url.URL, error) {
|
||||
}
|
||||
|
||||
// Request the given URL
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return nil, fmt.Errorf("checking remote file: %w", ctx.Err())
|
||||
@@ -78,7 +78,7 @@ func RemoteExists(ctx context.Context, u url.URL) (*url.URL, error) {
|
||||
req.URL = alt
|
||||
|
||||
// Try the alternative URL
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted()}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type TaskRC struct {
|
||||
Color *bool `yaml:"color"`
|
||||
DisableFuzzy *bool `yaml:"disable-fuzzy"`
|
||||
Concurrency *int `yaml:"concurrency"`
|
||||
Interactive *bool `yaml:"interactive"`
|
||||
Remote Remote `yaml:"remote"`
|
||||
Failfast bool `yaml:"failfast"`
|
||||
Experiments map[string]int `yaml:"experiments"`
|
||||
@@ -27,6 +28,9 @@ type Remote struct {
|
||||
CacheExpiry *time.Duration `yaml:"cache-expiry"`
|
||||
CacheDir *string `yaml:"cache-dir"`
|
||||
TrustedHosts []string `yaml:"trusted-hosts"`
|
||||
CACert *string `yaml:"cacert"`
|
||||
Cert *string `yaml:"cert"`
|
||||
CertKey *string `yaml:"cert-key"`
|
||||
}
|
||||
|
||||
// Merge combines the current TaskRC with another TaskRC, prioritizing non-nil fields from the other TaskRC.
|
||||
@@ -54,10 +58,14 @@ func (t *TaskRC) Merge(other *TaskRC) {
|
||||
slices.Sort(merged)
|
||||
t.Remote.TrustedHosts = slices.Compact(merged)
|
||||
}
|
||||
t.Remote.CACert = cmp.Or(other.Remote.CACert, t.Remote.CACert)
|
||||
t.Remote.Cert = cmp.Or(other.Remote.Cert, t.Remote.Cert)
|
||||
t.Remote.CertKey = cmp.Or(other.Remote.CertKey, t.Remote.CertKey)
|
||||
|
||||
t.Verbose = cmp.Or(other.Verbose, t.Verbose)
|
||||
t.Color = cmp.Or(other.Color, t.Color)
|
||||
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.Failfast = cmp.Or(other.Failfast, t.Failfast)
|
||||
}
|
||||
|
||||
178
testdata/if/Taskfile.yml
vendored
Normal file
178
testdata/if/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
SHOULD_RUN: "yes"
|
||||
ENV: "prod"
|
||||
FEATURE_ENABLED: "true"
|
||||
FEATURE_DISABLED: "false"
|
||||
|
||||
tasks:
|
||||
# Basic command-level if (condition met)
|
||||
cmd-if-true:
|
||||
cmds:
|
||||
- cmd: echo "executed"
|
||||
if: "true"
|
||||
|
||||
# Basic command-level if (condition not met)
|
||||
cmd-if-false:
|
||||
cmds:
|
||||
- cmd: echo "should not appear"
|
||||
if: "false"
|
||||
- echo "this runs"
|
||||
|
||||
# Task-level if (condition met)
|
||||
task-if-true:
|
||||
if: "true"
|
||||
cmds:
|
||||
- echo "task executed"
|
||||
|
||||
# Task-level if (condition not met)
|
||||
task-if-false:
|
||||
if: "false"
|
||||
cmds:
|
||||
- echo "should not appear"
|
||||
|
||||
# With template variables
|
||||
if-with-template:
|
||||
cmds:
|
||||
- cmd: echo "Running because SHOULD_RUN={{.SHOULD_RUN}}"
|
||||
if: '[ "{{.SHOULD_RUN}}" = "yes" ]'
|
||||
|
||||
# If inside for loop
|
||||
if-in-for-loop:
|
||||
cmds:
|
||||
- for: ["a", "b", "c"]
|
||||
cmd: echo "processing {{.ITEM}}"
|
||||
if: '[ "{{.ITEM}}" != "b" ]'
|
||||
|
||||
# If on task call
|
||||
if-on-task-call:
|
||||
cmds:
|
||||
- task: subtask
|
||||
if: "true"
|
||||
|
||||
subtask:
|
||||
internal: true
|
||||
cmds:
|
||||
- echo "subtask ran"
|
||||
|
||||
# If combined with platforms (both must pass)
|
||||
if-with-platforms:
|
||||
cmds:
|
||||
- cmd: echo "condition and platform met"
|
||||
platforms: [linux, darwin, windows]
|
||||
if: "true"
|
||||
|
||||
# Skip task call
|
||||
skip-task-call:
|
||||
cmds:
|
||||
- task: subtask
|
||||
if: "false"
|
||||
- echo "after skipped task call"
|
||||
|
||||
# Task call in cmds with if condition met
|
||||
task-call-if-true:
|
||||
cmds:
|
||||
- task: subtask
|
||||
if: "true"
|
||||
- echo "after task call"
|
||||
|
||||
# Task call in cmds with if condition not met
|
||||
task-call-if-false:
|
||||
cmds:
|
||||
- task: subtask
|
||||
if: "false"
|
||||
- echo "continues after skipped task"
|
||||
|
||||
# Template eq - condition met
|
||||
template-eq-true:
|
||||
cmds:
|
||||
- cmd: echo "env is prod"
|
||||
if: '{{ eq .ENV "prod" }}'
|
||||
|
||||
# Template eq - condition not met
|
||||
template-eq-false:
|
||||
cmds:
|
||||
- cmd: echo "should not appear"
|
||||
if: '{{ eq .ENV "dev" }}'
|
||||
- echo "this runs"
|
||||
|
||||
# Template ne (not equal)
|
||||
template-ne:
|
||||
cmds:
|
||||
- cmd: echo "env is not dev"
|
||||
if: '{{ ne .ENV "dev" }}'
|
||||
|
||||
# Template with boolean-like variable
|
||||
template-bool-true:
|
||||
cmds:
|
||||
- cmd: echo "feature enabled"
|
||||
if: '{{ eq .FEATURE_ENABLED "true" }}'
|
||||
|
||||
# Template with boolean-like variable (false)
|
||||
template-bool-false:
|
||||
cmds:
|
||||
- cmd: echo "should not appear"
|
||||
if: '{{ eq .FEATURE_DISABLED "true" }}'
|
||||
- echo "feature was disabled"
|
||||
|
||||
# Direct true/false from template
|
||||
template-direct-true:
|
||||
cmds:
|
||||
- cmd: echo "direct true works"
|
||||
if: '{{ .FEATURE_ENABLED }}'
|
||||
|
||||
# Direct true/false from template (false case)
|
||||
template-direct-false:
|
||||
cmds:
|
||||
- cmd: echo "should not appear"
|
||||
if: '{{ .FEATURE_DISABLED }}'
|
||||
- echo "direct false skipped correctly"
|
||||
|
||||
# Template with CLI variable override
|
||||
template-cli-var:
|
||||
cmds:
|
||||
- cmd: echo "MY_VAR is yes"
|
||||
if: '{{ eq .MY_VAR "yes" }}'
|
||||
|
||||
# Combined template conditions with and
|
||||
template-and:
|
||||
cmds:
|
||||
- cmd: echo "both conditions met"
|
||||
if: '{{ and (eq .ENV "prod") (eq .FEATURE_ENABLED "true") }}'
|
||||
|
||||
# Combined template conditions with or
|
||||
template-or:
|
||||
cmds:
|
||||
- cmd: echo "at least one condition met"
|
||||
if: '{{ or (eq .ENV "dev") (eq .ENV "prod") }}'
|
||||
|
||||
# Task-level if with template
|
||||
task-level-template:
|
||||
if: '{{ eq .ENV "prod" }}'
|
||||
cmds:
|
||||
- echo "task runs in prod"
|
||||
|
||||
# Task-level if with template (not met)
|
||||
task-level-template-false:
|
||||
if: '{{ eq .ENV "dev" }}'
|
||||
cmds:
|
||||
- echo "should not appear"
|
||||
|
||||
# Task-level if with dynamic variable (condition met)
|
||||
task-if-dynamic-true:
|
||||
vars:
|
||||
ENABLE_FEATURE:
|
||||
sh: 'echo "true"'
|
||||
if: '{{ eq .ENABLE_FEATURE "true" }}'
|
||||
cmds:
|
||||
- echo "dynamic feature enabled"
|
||||
|
||||
# Task-level if with dynamic variable (condition not met)
|
||||
task-if-dynamic-false:
|
||||
vars:
|
||||
ENABLE_FEATURE:
|
||||
sh: 'echo "false"'
|
||||
if: '{{ eq .ENABLE_FEATURE "true" }}'
|
||||
cmds:
|
||||
- echo "should not appear"
|
||||
1
testdata/if/testdata/TestIf-cmd-if-false.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-cmd-if-false.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
this runs
|
||||
1
testdata/if/testdata/TestIf-cmd-if-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-cmd-if-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
executed
|
||||
7
testdata/if/testdata/TestIf-if-in-for-loop.golden
vendored
Normal file
7
testdata/if/testdata/TestIf-if-in-for-loop.golden
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
task: "if-in-for-loop" started
|
||||
task: [if-in-for-loop] echo "processing a"
|
||||
processing a
|
||||
task: [if-in-for-loop] if condition not met - skipped
|
||||
task: [if-in-for-loop] echo "processing c"
|
||||
processing c
|
||||
task: "if-in-for-loop" finished
|
||||
5
testdata/if/testdata/TestIf-task-call-if-false.golden
vendored
Normal file
5
testdata/if/testdata/TestIf-task-call-if-false.golden
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
task: "task-call-if-false" started
|
||||
task: [task-call-if-false] if condition not met - skipped
|
||||
task: [task-call-if-false] echo "continues after skipped task"
|
||||
continues after skipped task
|
||||
task: "task-call-if-false" finished
|
||||
2
testdata/if/testdata/TestIf-task-call-if-true.golden
vendored
Normal file
2
testdata/if/testdata/TestIf-task-call-if-true.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
subtask ran
|
||||
after task call
|
||||
2
testdata/if/testdata/TestIf-task-if-dynamic-false.golden
vendored
Normal file
2
testdata/if/testdata/TestIf-task-if-dynamic-false.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
task: dynamic variable: "echo \"false\"" result: "false"
|
||||
task: if condition not met - skipped: "task-if-dynamic-false"
|
||||
1
testdata/if/testdata/TestIf-task-if-dynamic-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-task-if-dynamic-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
dynamic feature enabled
|
||||
1
testdata/if/testdata/TestIf-task-if-false.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-task-if-false.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: if condition not met - skipped: "task-if-false"
|
||||
1
testdata/if/testdata/TestIf-task-if-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-task-if-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task executed
|
||||
1
testdata/if/testdata/TestIf-task-level-template-false.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-task-level-template-false.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: if condition not met - skipped: "task-level-template-false"
|
||||
1
testdata/if/testdata/TestIf-task-level-template.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-task-level-template.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task runs in prod
|
||||
1
testdata/if/testdata/TestIf-template-and.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-and.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
both conditions met
|
||||
1
testdata/if/testdata/TestIf-template-bool-false.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-bool-false.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
feature was disabled
|
||||
1
testdata/if/testdata/TestIf-template-bool-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-bool-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
feature enabled
|
||||
1
testdata/if/testdata/TestIf-template-cli-var.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-cli-var.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
MY_VAR is yes
|
||||
1
testdata/if/testdata/TestIf-template-direct-false.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-direct-false.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
direct false skipped correctly
|
||||
1
testdata/if/testdata/TestIf-template-direct-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-direct-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
direct true works
|
||||
5
testdata/if/testdata/TestIf-template-eq-false.golden
vendored
Normal file
5
testdata/if/testdata/TestIf-template-eq-false.golden
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
task: "template-eq-false" started
|
||||
task: [template-eq-false] if condition not met - skipped
|
||||
task: [template-eq-false] echo "this runs"
|
||||
this runs
|
||||
task: "template-eq-false" finished
|
||||
1
testdata/if/testdata/TestIf-template-eq-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-eq-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
env is prod
|
||||
1
testdata/if/testdata/TestIf-template-ne.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-ne.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
env is not dev
|
||||
1
testdata/if/testdata/TestIf-template-or.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-or.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
at least one condition met
|
||||
22
testdata/includes_silent/Taskfile-inc.yml
vendored
Normal file
22
testdata/includes_silent/Taskfile-inc.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
version: '3'
|
||||
|
||||
silent: true
|
||||
|
||||
tasks:
|
||||
hello:
|
||||
cmds:
|
||||
- echo "Hello from include"
|
||||
|
||||
hello-silent:
|
||||
silent: true
|
||||
cmds:
|
||||
- echo "Hello from include silent task"
|
||||
|
||||
hello-silent-not-set:
|
||||
cmds:
|
||||
- echo "Hello from include silent not set task"
|
||||
|
||||
hello-silent-set-false:
|
||||
silent: false
|
||||
cmds:
|
||||
- echo "Hello from include silent false task"
|
||||
13
testdata/includes_silent/Taskfile.yml
vendored
Normal file
13
testdata/includes_silent/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
inc: Taskfile-inc.yml
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "Hello from root Taskfile"
|
||||
- task: inc:hello
|
||||
- task: inc:hello-silent
|
||||
- task: inc:hello-silent-not-set
|
||||
- task: inc:hello-silent-set-false
|
||||
7
testdata/includes_silent/testdata/TestIncludeSilent-include-taskfile-silent.golden
vendored
Normal file
7
testdata/includes_silent/testdata/TestIncludeSilent-include-taskfile-silent.golden
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
task: [default] echo "Hello from root Taskfile"
|
||||
Hello from root Taskfile
|
||||
Hello from include
|
||||
Hello from include silent task
|
||||
Hello from include silent not set task
|
||||
task: [inc:hello-silent-set-false] echo "Hello from include silent false task"
|
||||
Hello from include silent false task
|
||||
1
testdata/interactive_vars/.taskrc.yml
vendored
Normal file
1
testdata/interactive_vars/.taskrc.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
interactive: true
|
||||
108
testdata/interactive_vars/Taskfile.yml
vendored
Normal file
108
testdata/interactive_vars/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
# Simple text input prompt
|
||||
greet:
|
||||
desc: Greet someone by name
|
||||
requires:
|
||||
vars:
|
||||
- NAME
|
||||
cmds:
|
||||
- echo "Hello, {{.NAME}}!"
|
||||
|
||||
# Enum selection (dropdown menu)
|
||||
deploy:
|
||||
desc: Deploy to an environment
|
||||
requires:
|
||||
vars:
|
||||
- name: ENVIRONMENT
|
||||
enum: [dev, staging, prod]
|
||||
cmds:
|
||||
- echo "Deploying to {{.ENVIRONMENT}}..."
|
||||
|
||||
# Multiple variables at once
|
||||
release:
|
||||
desc: Create a release with version and environment
|
||||
requires:
|
||||
vars:
|
||||
- VERSION
|
||||
- name: ENVIRONMENT
|
||||
enum: [dev, staging, prod]
|
||||
cmds:
|
||||
- echo "Releasing {{.VERSION}} to {{.ENVIRONMENT}}"
|
||||
|
||||
# Nested dependencies - all prompts happen upfront
|
||||
full-deploy:
|
||||
desc: Full deployment pipeline with nested deps
|
||||
deps:
|
||||
- task: build
|
||||
- task: test
|
||||
cmds:
|
||||
- task: deploy
|
||||
|
||||
build:
|
||||
requires:
|
||||
vars:
|
||||
- name: BUILD_MODE
|
||||
enum: [debug, release]
|
||||
cmds:
|
||||
- echo "Building in {{.BUILD_MODE}} mode..."
|
||||
|
||||
test:
|
||||
requires:
|
||||
vars:
|
||||
- name: TEST_SUITE
|
||||
enum: [unit, integration, e2e, all]
|
||||
cmds:
|
||||
- echo "Running {{.TEST_SUITE}} tests..."
|
||||
|
||||
# Variable already set - no prompt shown
|
||||
greet-world:
|
||||
desc: Greet the world (no prompt needed)
|
||||
vars:
|
||||
NAME: World
|
||||
requires:
|
||||
vars:
|
||||
- NAME
|
||||
cmds:
|
||||
- echo "Hello, {{.NAME}}!"
|
||||
|
||||
# Complex scenario with multiple levels
|
||||
pipeline:
|
||||
desc: Run the full CI/CD pipeline
|
||||
cmds:
|
||||
- task: setup
|
||||
- task: build
|
||||
- task: test
|
||||
- task: deploy
|
||||
|
||||
setup:
|
||||
requires:
|
||||
vars:
|
||||
- PROJECT_NAME
|
||||
cmds:
|
||||
- echo "Setting up project {{.PROJECT_NAME}}..."
|
||||
|
||||
# Docker example with multiple selections
|
||||
docker-build:
|
||||
desc: Build a Docker image
|
||||
requires:
|
||||
vars:
|
||||
- IMAGE_NAME
|
||||
- IMAGE_TAG
|
||||
- name: PLATFORM
|
||||
enum: [linux/amd64, linux/arm64, linux/arm/v7]
|
||||
cmds:
|
||||
- echo "Building {{.IMAGE_NAME}}:{{.IMAGE_TAG}} for {{.PLATFORM}}"
|
||||
|
||||
# Database migration example
|
||||
db-migrate:
|
||||
desc: Run database migrations
|
||||
requires:
|
||||
vars:
|
||||
- name: DIRECTION
|
||||
enum: [up, down]
|
||||
- name: DATABASE
|
||||
enum: [postgres, mysql, sqlite]
|
||||
cmds:
|
||||
- echo "Running {{.DIRECTION}} migrations on {{.DATABASE}}"
|
||||
7
testdata/prefix_uptodate/Taskfile.yml
vendored
Normal file
7
testdata/prefix_uptodate/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
prefix: "foobar"
|
||||
status:
|
||||
- echo "I'm ok"
|
||||
1
testdata/prefix_uptodate/testdata/TestPrefix-up_to_dat_with_no_output_style.golden
vendored
Normal file
1
testdata/prefix_uptodate/testdata/TestPrefix-up_to_dat_with_no_output_style.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Task "foo" is up to date
|
||||
1
testdata/prefix_uptodate/testdata/TestPrefix-up_to_date.golden
vendored
Normal file
1
testdata/prefix_uptodate/testdata/TestPrefix-up_to_date.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Task "foobar" is up to date
|
||||
1
testdata/special_vars/Taskfile.yml
vendored
1
testdata/special_vars/Taskfile.yml
vendored
@@ -11,6 +11,7 @@ tasks:
|
||||
cmds:
|
||||
- echo {{.TASK}}
|
||||
print-root-dir: echo {{.ROOT_DIR}}
|
||||
print-root-taskfile: echo {{.ROOT_TASKFILE}}
|
||||
print-taskfile: echo {{.TASKFILE}}
|
||||
print-taskfile-dir: echo {{.TASKFILE_DIR}}
|
||||
print-task-version: echo {{.TASK_VERSION}}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{.TEST_DIR}}/testdata/special_vars/Taskfile.yml
|
||||
@@ -0,0 +1 @@
|
||||
{{.TEST_DIR}}/testdata/special_vars/Taskfile.yml
|
||||
7
testdata/vars/Taskfile.yml
vendored
7
testdata/vars/Taskfile.yml
vendored
@@ -49,3 +49,10 @@ tasks:
|
||||
- echo "{{.MESSAGE}}"
|
||||
|
||||
from-dot-env: echo '{{.DOT_ENV_VAR}}'
|
||||
|
||||
# Test that CLI variables take priority over Taskfile defaults
|
||||
cli-var-priority:
|
||||
vars:
|
||||
CLI_VAR: '{{.CLI_VAR | default "default_value"}}'
|
||||
cmds:
|
||||
- echo '{{.CLI_VAR}}'
|
||||
|
||||
1
testdata/vars/testdata/TestVars-cli-var-priority-default.golden
vendored
Normal file
1
testdata/vars/testdata/TestVars-cli-var-priority-default.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
default_value
|
||||
1
testdata/vars/testdata/TestVars-cli-var-priority-override.golden
vendored
Normal file
1
testdata/vars/testdata/TestVars-cli-var-priority-override.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
from_cli
|
||||
2
testdata/wildcards/Taskfile.yml
vendored
2
testdata/wildcards/Taskfile.yml
vendored
@@ -19,6 +19,8 @@ tasks:
|
||||
- "echo \"I don't consume matches: {{.MATCH}}\""
|
||||
|
||||
start-*:
|
||||
aliases:
|
||||
- s-*
|
||||
vars:
|
||||
SERVICE: "{{index .MATCH 0}}"
|
||||
cmds:
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
@@ -57,7 +58,7 @@ func (e *Executor) CompiledTaskForTaskList(call *Call) (*ast.Task, error) {
|
||||
Vars: vars,
|
||||
Env: nil,
|
||||
Dotenv: origTask.Dotenv,
|
||||
Silent: origTask.Silent,
|
||||
Silent: deepcopy.Scalar(origTask.Silent),
|
||||
Interactive: origTask.Interactive,
|
||||
Internal: origTask.Internal,
|
||||
Method: origTask.Method,
|
||||
@@ -113,7 +114,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
Vars: vars,
|
||||
Env: nil,
|
||||
Dotenv: templater.Replace(origTask.Dotenv, cache),
|
||||
Silent: origTask.Silent,
|
||||
Silent: deepcopy.Scalar(origTask.Silent),
|
||||
Interactive: origTask.Interactive,
|
||||
Internal: origTask.Internal,
|
||||
Method: templater.Replace(origTask.Method, cache),
|
||||
@@ -123,6 +124,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
IncludeVars: origTask.IncludeVars,
|
||||
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
|
||||
Platforms: origTask.Platforms,
|
||||
If: templater.Replace(origTask.If, cache),
|
||||
Location: origTask.Location,
|
||||
Requires: origTask.Requires,
|
||||
Watch: origTask.Watch,
|
||||
@@ -228,6 +230,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
newCmd := cmd.DeepCopy()
|
||||
newCmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
|
||||
newCmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
|
||||
newCmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra)
|
||||
newCmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
|
||||
new.Cmds = append(new.Cmds, newCmd)
|
||||
}
|
||||
@@ -242,6 +245,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
newCmd := cmd.DeepCopy()
|
||||
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
|
||||
newCmd.Task = templater.Replace(cmd.Task, cache)
|
||||
newCmd.If = templater.Replace(cmd.If, cache)
|
||||
newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
|
||||
new.Cmds = append(new.Cmds, newCmd)
|
||||
}
|
||||
|
||||
@@ -35,40 +35,23 @@ export default defineConfig({
|
||||
description: taskDescription,
|
||||
lang: 'en-US',
|
||||
head: [
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/x-icon',
|
||||
href: '/img/favicon.ico',
|
||||
sizes: '48x48'
|
||||
}
|
||||
],
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'icon',
|
||||
sizes: 'any',
|
||||
type: 'image/svg+xml',
|
||||
href: '/img/logo.svg'
|
||||
}
|
||||
],
|
||||
// Favicon ICO for legacy browsers (auto-discovery)
|
||||
['link', { rel: 'icon', href: '/favicon.ico', sizes: '48x48' }],
|
||||
// Favicon SVG for modern browsers (scalable)
|
||||
['link', { rel: 'icon', href: '/img/logo.svg', type: 'image/svg+xml' }],
|
||||
// Apple Touch Icon for iOS devices
|
||||
['link', { rel: 'apple-touch-icon', href: '/img/logo.png' }],
|
||||
[
|
||||
'meta',
|
||||
{ name: 'author', content: `${team.map((c) => c.name).join(', ')}` }
|
||||
],
|
||||
// Open Graph
|
||||
['meta', { property: 'og:type', content: 'website' }],
|
||||
['meta', { property: 'og:site_name', content: taskName }],
|
||||
['meta', { property: 'og:title', content: taskName }],
|
||||
['meta', { property: 'og:description', content: taskDescription }],
|
||||
['meta', { property: 'og:site_name', content: 'Task' }],
|
||||
['meta', { property: 'og:image', content: ogImage }],
|
||||
['meta', { property: 'og:url', content: ogUrl }],
|
||||
// Twitter Card
|
||||
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
|
||||
['meta', { name: 'twitter:site', content: '@taskfiledev' }],
|
||||
['meta', { name: 'twitter:title', content: taskName }],
|
||||
['meta', { name: 'twitter:description', content: taskDescription }],
|
||||
['meta', { name: 'twitter:image', content: ogImage }],
|
||||
[
|
||||
'meta',
|
||||
@@ -85,6 +68,16 @@ export default defineConfig({
|
||||
src: "https://u.taskfile.dev/script.js",
|
||||
"data-website-id": "084030b0-0e3f-4891-8d2a-0c12c40f5933"
|
||||
}
|
||||
],
|
||||
[
|
||||
"script",
|
||||
{ type: "application/ld+json" },
|
||||
JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "Task",
|
||||
"url": "https://taskfile.dev/"
|
||||
})
|
||||
]
|
||||
],
|
||||
transformHead({ pageData }) {
|
||||
@@ -96,6 +89,15 @@ export default defineConfig({
|
||||
.replace(/index$/, '')}`
|
||||
head.push(['link', { rel: 'canonical', href: canonicalUrl }])
|
||||
|
||||
// Dynamic Open Graph and Twitter meta tags
|
||||
const pageTitle = pageData.frontmatter.title || pageData.title || taskName
|
||||
const pageDescription = pageData.frontmatter.description || pageData.description || taskDescription
|
||||
head.push(['meta', { property: 'og:title', content: `${pageTitle} | Task` }])
|
||||
head.push(['meta', { property: 'og:description', content: pageDescription }])
|
||||
head.push(['meta', { property: 'og:url', content: canonicalUrl }])
|
||||
head.push(['meta', { name: 'twitter:title', content: `${pageTitle} | Task` }])
|
||||
head.push(['meta', { name: 'twitter:description', content: pageDescription }])
|
||||
|
||||
// Noindex pour 404
|
||||
if (pageData.relativePath === '404.md') {
|
||||
head.push(['meta', { name: 'robots', content: 'noindex, nofollow' }])
|
||||
@@ -200,6 +202,16 @@ export default defineConfig({
|
||||
|
||||
sidebar: {
|
||||
'/blog/': [
|
||||
{
|
||||
text: '2026',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
text: 'New `if:` Control and Variable Prompt',
|
||||
link: '/blog/if-and-variable-prompt'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '2025',
|
||||
collapsed: false,
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
"vitepress-plugin-llms": "^1.9.1",
|
||||
"vue": "^3.5.18"
|
||||
},
|
||||
"packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402"
|
||||
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316"
|
||||
}
|
||||
|
||||
1564
website/pnpm-lock.yaml
generated
1564
website/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
126
website/src/blog/if-and-variable-prompt.md
Normal file
126
website/src/blog/if-and-variable-prompt.md
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
title: New `if:` Control and Variable Prompt
|
||||
description: Introduction of the `if:` control and required variable prompts.
|
||||
author: vmaerten
|
||||
date: 2026-01-24
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# New `if:` control and interactivity support
|
||||
|
||||
<AuthorCard :author="$frontmatter.author" />
|
||||
|
||||
The [v3.47.0][release] release is here, and it brings two exciting new features
|
||||
to Task. Let's take a closer look at them!
|
||||
|
||||
## The New `if:` Control
|
||||
|
||||
This first feature is simply the second most upvoted issue of all time (!) with
|
||||
58 :thumbsup:s (!!) at the time of writing.
|
||||
|
||||
It introduces the `if:` control, which allow you to conditionally skip the
|
||||
execution of certain tasks and proceeding. `if:` can be set on a task-level or
|
||||
command-level, and can be either a Bash command or a Go template expression.
|
||||
|
||||
Let me show a couple of examples.
|
||||
|
||||
Task-level with Bash expression:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
deploy:
|
||||
if: '[ "$CI" = "true" ]'
|
||||
cmds:
|
||||
- echo "Deploying..."
|
||||
- ./deploy.sh
|
||||
```
|
||||
|
||||
Command-level with Go template expression:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
conditional:
|
||||
vars:
|
||||
ENABLE_FEATURE: "true"
|
||||
cmds:
|
||||
- cmd: echo "Feature is enabled"
|
||||
if: '{{eq .ENABLE_FEATURE "true"}}'
|
||||
- cmd: echo "Feature is disabled"
|
||||
if: '{{ne .ENABLE_FEATURE "true"}}'
|
||||
```
|
||||
|
||||
For more details, please check out the [documentation][if-docs].
|
||||
The [examples][if-examples] from the test suite may be useful too.
|
||||
|
||||
::: info
|
||||
|
||||
We had similar functionality before, but nothing that perfectly fits this use
|
||||
case. There were [`sources:`][sources] and [`status:`][status], but those were
|
||||
meant to check if a task was up-to-date, and [`preconditions:`][preconditions],
|
||||
but this would halt the execution of the task instead of skipping it.
|
||||
|
||||
:::
|
||||
|
||||
## Prompt for Required Variables
|
||||
|
||||
For backward-compatibility reasons, this feature is disabled by default.
|
||||
To enable it, either pass `--interactive` flag or add `interactive: true` to
|
||||
your `.taskrc.yml`.
|
||||
|
||||
Once you do that, Task will basically starting prompting you in runtime for any
|
||||
required variables. In the example below, `NAME` will be prompted at runtime:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
# Simple text input prompt
|
||||
greet:
|
||||
desc: Greet someone by name
|
||||
requires:
|
||||
vars:
|
||||
- NAME
|
||||
cmds:
|
||||
- echo "Hello, {{.NAME}}!"
|
||||
```
|
||||
|
||||
If a given variable has an enum, Task will actually show a selection menu so you
|
||||
can choose the right option instead of typing:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
# Enum selection (dropdown menu)
|
||||
deploy:
|
||||
desc: Deploy to an environment
|
||||
requires:
|
||||
vars:
|
||||
- name: ENVIRONMENT
|
||||
enum: [dev, staging, prod]
|
||||
cmds:
|
||||
- echo "Deploying to {{.ENVIRONMENT}}..."
|
||||
```
|
||||
|
||||
Once again, check out the [documentation][prompt-docs] for more details, and the
|
||||
[prompt examples][prompt-examples] from the test suite.
|
||||
|
||||
## Feedback
|
||||
|
||||
Let's us know if you have any feedback! You can find us on our
|
||||
[Discord server][discord].
|
||||
|
||||
[release]: https://github.com/go-task/task/releases/tag/v3.47.0
|
||||
[vmaerten]: https://github.com/vmaerten
|
||||
[sources]: https://taskfile.dev/docs/guide#by-fingerprinting-locally-generated-files-and-their-sources
|
||||
[status]: https://taskfile.dev/docs/guide#using-programmatic-checks-to-indicate-a-task-is-up-to-date
|
||||
[preconditions]: https://taskfile.dev/docs/guide#using-programmatic-checks-to-cancel-the-execution-of-a-task-and-its-dependencies
|
||||
[if-docs]: https://taskfile.dev/docs/guide#conditional-execution-with-if
|
||||
[if-examples]: https://github.com/go-task/task/blob/main/testdata/if/Taskfile.yml
|
||||
[prompt-docs]: https://taskfile.dev/docs/guide#prompting-for-missing-variables-interactively
|
||||
[prompt-examples]: https://github.com/go-task/task/blob/main/testdata/interactive_vars/Taskfile.yml
|
||||
[discord]: https://discord.com/invite/6TY36E39UK
|
||||
@@ -3,6 +3,15 @@ title: Blog
|
||||
description: Latest news and updates from the Task team
|
||||
---
|
||||
|
||||
<BlogPost
|
||||
title="New `if:` Control and Variable Prompt"
|
||||
url="/blog/if-and-variable-prompt"
|
||||
date="2026-01-24"
|
||||
author="vmaerten"
|
||||
description="The v3.47.0 release is here, and it brings two exciting new features to Task. Let's take a closer look at them!"
|
||||
:tags="['new-features', 'variables']"
|
||||
/>
|
||||
|
||||
<BlogPost
|
||||
title="Announcing Built-in Core Utilities for Windows"
|
||||
url="/blog/windows-core-utils"
|
||||
|
||||
@@ -7,7 +7,52 @@ outline: deep
|
||||
|
||||
::: v-pre
|
||||
|
||||
## v3.46.0 - 2025-12-18
|
||||
## v3.48.0 - 2026-01-26
|
||||
|
||||
- Fixed `if:` conditions when using to check dynamic variables. Also, skip
|
||||
variable prompt if task would be skipped by `if:` (#2658, #2660 by @vmaerten).
|
||||
- Fixed `ROOT_TASKFILE` variable pointing to directory instead of the actual
|
||||
Taskfile path when no explicit `-t` flag is provided (#2635, #1706 by
|
||||
@trulede).
|
||||
- Included Taskfiles with `silent: true` now properly propagate silence to their
|
||||
tasks, while still allowing individual tasks to override with `silent: false`
|
||||
(#2640, #1319 by @trulede).
|
||||
- Added TLS certificate options for Remote Taskfiles: use `--cacert` for
|
||||
self-signed certificates and `--cert`/`--cert-key` for mTLS authentication
|
||||
(#2537, #2242 by @vmaerten).
|
||||
|
||||
## v3.47.0 - 2026-01-24
|
||||
|
||||
- Fixed remote git Taskfiles: cloning now works without explicit ref, and
|
||||
directory includes are properly resolved (#2602 by @vmaerten).
|
||||
- For `output: prefixed`, print `prefix:` if set instead of task name (#1566,
|
||||
#2633 by @trulede).
|
||||
- Ensure no ANSI sequences are printed for `--color=false` (#2560, #2584 by
|
||||
@trulede).
|
||||
- Task aliases can now contain wildcards and will match accordingly (e.g., `s-*`
|
||||
as alias for `start-*`) (#1900, #2234 by @vmaerten).
|
||||
- Added conditional execution with the `if` field: skip tasks, commands, or task
|
||||
calls based on shell exit codes or template expressions like
|
||||
`{{ eq .ENV "prod" }}` (#2564, #608 by @vmaerten).
|
||||
- Task can now interactively prompt for missing required variables when running
|
||||
in a TTY, with support for enum selection menus. Enable with `--interactive`
|
||||
flag or `interactive: true` in `.taskrc.yml` (#2579, #2079 by @vmaerten).
|
||||
|
||||
## v3.46.4 - 2025-12-24
|
||||
|
||||
- Fixed regressions in completion script for Fish (#2591, #2604, #2592 by
|
||||
@WinkelCode).
|
||||
|
||||
## v3.46.3 - 2025-12-19
|
||||
|
||||
- Fixed regression in completion script for zsh (#2593, #2594 by @vmaerten).
|
||||
|
||||
## v3.46.2 - 2025-12-18
|
||||
|
||||
- Fixed a regression on previous release that affected variables passed via
|
||||
command line (#2588, #2589 by @vmaerten).
|
||||
|
||||
## v3.46.1 - 2025-12-18
|
||||
|
||||
### ✨ Features
|
||||
|
||||
|
||||
@@ -263,6 +263,38 @@ Taskfile that is downloaded via an unencrypted connection. Sources that are not
|
||||
protected by TLS are vulnerable to man-in-the-middle attacks and should be
|
||||
avoided unless you know what you are doing.
|
||||
|
||||
#### Custom Certificates
|
||||
|
||||
If your remote Taskfiles are hosted on a server that uses a custom CA
|
||||
certificate (e.g., a corporate internal server), you can specify the CA
|
||||
certificate using the `--cacert` flag:
|
||||
|
||||
```shell
|
||||
task --taskfile https://internal.example.com/Taskfile.yml --cacert /path/to/ca.crt
|
||||
```
|
||||
|
||||
For servers that require client certificate authentication (mTLS), you can
|
||||
provide a client certificate and key:
|
||||
|
||||
```shell
|
||||
task --taskfile https://secure.example.com/Taskfile.yml \
|
||||
--cert /path/to/client.crt \
|
||||
--cert-key /path/to/client.key
|
||||
```
|
||||
|
||||
::: warning
|
||||
|
||||
Encrypted private keys are not currently supported. If your key is encrypted,
|
||||
you must decrypt it first:
|
||||
|
||||
```shell
|
||||
openssl rsa -in encrypted.key -out decrypted.key
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
These options can also be configured in the [configuration file](#configuration).
|
||||
|
||||
## Caching & Running Offline
|
||||
|
||||
Whenever you run a remote Taskfile, the latest copy will be downloaded from the
|
||||
@@ -313,6 +345,9 @@ remote:
|
||||
trusted-hosts:
|
||||
- github.com
|
||||
- gitlab.com
|
||||
cacert: ""
|
||||
cert: ""
|
||||
cert-key: ""
|
||||
```
|
||||
|
||||
#### `insecure`
|
||||
@@ -410,3 +445,36 @@ task --trusted-hosts github.com,gitlab.com -t https://github.com/user/repo.git//
|
||||
# Trust a host with a specific port
|
||||
task --trusted-hosts example.com:8080 -t https://example.com:8080/Taskfile.yml
|
||||
```
|
||||
|
||||
#### `cacert`
|
||||
|
||||
- **Type**: `string`
|
||||
- **Default**: `""`
|
||||
- **Description**: Path to a custom CA certificate file for TLS verification
|
||||
|
||||
```yaml
|
||||
remote:
|
||||
cacert: "/path/to/ca.crt"
|
||||
```
|
||||
|
||||
#### `cert`
|
||||
|
||||
- **Type**: `string`
|
||||
- **Default**: `""`
|
||||
- **Description**: Path to a client certificate file for mTLS authentication
|
||||
|
||||
```yaml
|
||||
remote:
|
||||
cert: "/path/to/client.crt"
|
||||
```
|
||||
|
||||
#### `cert-key`
|
||||
|
||||
- **Type**: `string`
|
||||
- **Default**: `""`
|
||||
- **Description**: Path to the client certificate private key file
|
||||
|
||||
```yaml
|
||||
remote:
|
||||
cert-key: "/path/to/client.key"
|
||||
```
|
||||
|
||||
@@ -174,6 +174,19 @@ tasks:
|
||||
- echo "Using $KEYNAME and endpoint $ENDPOINT"
|
||||
```
|
||||
|
||||
When the same variable is defined in multiple dotenv files, the **first file in
|
||||
the list takes precedence**. This allows you to set up override patterns by
|
||||
placing higher-priority files first:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
dotenv:
|
||||
- .env.local # Highest priority - local developer overrides
|
||||
- .env.{{.ENV}} # Environment-specific settings
|
||||
- .env # Base defaults (lowest priority)
|
||||
```
|
||||
|
||||
Dotenv files can also be specified at the task level:
|
||||
|
||||
```yaml
|
||||
@@ -1007,6 +1020,99 @@ tasks:
|
||||
- echo "I will not run"
|
||||
```
|
||||
|
||||
### Conditional execution with `if`
|
||||
|
||||
The `if` attribute allows you to conditionally skip tasks or commands based on a
|
||||
shell command's exit code. Unlike `preconditions` which fail and stop execution,
|
||||
`if` simply skips the task or command when the condition is not met and continues
|
||||
with the rest of the Taskfile.
|
||||
|
||||
#### Task-level `if`
|
||||
|
||||
When `if` is set on a task, the entire task is skipped if the condition fails:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
deploy:
|
||||
if: '[ "$CI" = "true" ]'
|
||||
cmds:
|
||||
- echo "Deploying..."
|
||||
- ./deploy.sh
|
||||
```
|
||||
|
||||
#### Command-level `if`
|
||||
|
||||
When `if` is set on a command, only that specific command is skipped:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- cmd: echo "Building for production"
|
||||
if: '[ "$ENV" = "production" ]'
|
||||
- cmd: echo "Building for development"
|
||||
if: '[ "$ENV" = "development" ]'
|
||||
- go build ./...
|
||||
```
|
||||
|
||||
#### Using templates in `if` conditions
|
||||
|
||||
You can use Go template expressions in `if` conditions. Template expressions like
|
||||
<span v-pre>`{{eq .VAR "value"}}`</span> evaluate to `true` or `false`, which are valid shell
|
||||
commands (`true` exits with 0, `false` exits with 1):
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
conditional:
|
||||
vars:
|
||||
ENABLE_FEATURE: "true"
|
||||
cmds:
|
||||
- cmd: echo "Feature is enabled"
|
||||
if: '{{eq .ENABLE_FEATURE "true"}}'
|
||||
- cmd: echo "Feature is disabled"
|
||||
if: '{{ne .ENABLE_FEATURE "true"}}'
|
||||
```
|
||||
|
||||
#### Using `if` with `for` loops
|
||||
|
||||
When used inside a `for` loop, the `if` condition is evaluated for each iteration:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
process-items:
|
||||
cmds:
|
||||
- for: ['a', 'b', 'c']
|
||||
cmd: echo "processing {{.ITEM}}"
|
||||
if: '[ "{{.ITEM}}" != "b" ]'
|
||||
```
|
||||
|
||||
This will output:
|
||||
|
||||
```
|
||||
processing a
|
||||
processing c
|
||||
```
|
||||
|
||||
#### `if` vs `preconditions`
|
||||
|
||||
| Aspect | `if` | `preconditions` |
|
||||
|--------|------|-----------------|
|
||||
| On failure | Skips (continues) | Fails (stops) |
|
||||
| Message | Only in verbose mode | Always shown |
|
||||
| Use case | "Run if possible" | "Must be true" |
|
||||
|
||||
Use `if` when you want optional conditional execution that shouldn't stop the
|
||||
workflow. Use `preconditions` when the condition must be met for the task to
|
||||
make sense.
|
||||
|
||||
### Limiting when tasks run
|
||||
|
||||
If a task executed by multiple `cmds` or multiple `deps` you can control when it
|
||||
@@ -1127,6 +1233,65 @@ This is supported only for string variables.
|
||||
|
||||
:::
|
||||
|
||||
### Prompting for missing variables interactively
|
||||
|
||||
If you want Task to prompt users for missing required variables instead of
|
||||
failing, you can enable interactive mode in your `.taskrc.yml`:
|
||||
|
||||
```yaml
|
||||
# ~/.taskrc.yml
|
||||
interactive: true
|
||||
```
|
||||
|
||||
When enabled, Task will display an interactive prompt for any missing required
|
||||
variable. For variables with an `enum`, a selection menu is shown. For variables
|
||||
without an enum, a text input is displayed.
|
||||
|
||||
```yaml
|
||||
# Taskfile.yml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
deploy:
|
||||
requires:
|
||||
vars:
|
||||
- name: ENVIRONMENT
|
||||
enum: [dev, staging, prod]
|
||||
- VERSION
|
||||
cmds:
|
||||
- echo "Deploying {{.VERSION}} to {{.ENVIRONMENT}}"
|
||||
```
|
||||
|
||||
```shell
|
||||
$ task deploy
|
||||
? Select value for ENVIRONMENT:
|
||||
❯ dev
|
||||
staging
|
||||
prod
|
||||
? Enter value for VERSION: 1.0.0
|
||||
Deploying 1.0.0 to prod
|
||||
```
|
||||
|
||||
If the variable is already set (via CLI, environment, or Taskfile), no prompt
|
||||
is shown:
|
||||
|
||||
```shell
|
||||
$ task deploy ENVIRONMENT=prod VERSION=1.0.0
|
||||
Deploying 1.0.0 to prod
|
||||
```
|
||||
|
||||
::: info
|
||||
|
||||
Interactive prompts require a TTY (terminal). Task automatically detects
|
||||
non-interactive environments like GitHub Actions, GitLab CI, and other CI
|
||||
pipelines where stdin/stdout are not connected to a terminal. In these cases,
|
||||
prompts are skipped and missing variables will cause an error as usual.
|
||||
|
||||
You can enable prompts from the command line with `--interactive` or by setting
|
||||
`interactive: true` in your `.taskrc.yml`.
|
||||
|
||||
:::
|
||||
|
||||
## Variables
|
||||
|
||||
Task allows you to set variables using the `vars` keyword. The following
|
||||
@@ -1779,6 +1944,27 @@ $ task start:foo:3
|
||||
Starting foo with 3 replicas
|
||||
```
|
||||
|
||||
Using wildcards with aliases
|
||||
Wildcards also work with aliases. If a task has an alias, you can use the alias name with wildcards to capture arguments. For example:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
start:*:
|
||||
aliases: [run:*]
|
||||
vars:
|
||||
SERVICE: "{{index .MATCH 0}}"
|
||||
cmds:
|
||||
- echo "Running {{.SERVICE}}"
|
||||
```
|
||||
In this example, you can call the task using the alias run:*:
|
||||
|
||||
```shell
|
||||
$ task run:foo
|
||||
Running foo
|
||||
```
|
||||
|
||||
## Doing task cleanup with `defer`
|
||||
|
||||
With the `defer` keyword, it's possible to schedule cleanup to be run once the
|
||||
|
||||
@@ -301,6 +301,19 @@ Automatically answer "yes" to all prompts.
|
||||
task deploy --yes
|
||||
```
|
||||
|
||||
#### `--interactive`
|
||||
|
||||
Enable interactive prompts for missing required variables. When a required
|
||||
variable is not provided, Task will prompt for input instead of failing.
|
||||
|
||||
Task automatically detects non-TTY environments (like CI pipelines) and
|
||||
skips prompts. This flag can also be set in `.taskrc.yml` to enable prompts
|
||||
by default.
|
||||
|
||||
```bash
|
||||
task deploy --interactive
|
||||
```
|
||||
|
||||
## Exit Codes
|
||||
|
||||
Task uses specific exit codes to indicate different types of errors:
|
||||
|
||||
@@ -135,6 +135,20 @@ concurrency: 4
|
||||
failfast: true
|
||||
```
|
||||
|
||||
### `interactive`
|
||||
|
||||
- **Type**: `boolean`
|
||||
- **Default**: `false`
|
||||
- **Description**: Prompt for missing required variables instead of failing.
|
||||
When enabled, Task will display an interactive prompt for any missing required
|
||||
variable. Requires a TTY. Task automatically detects non-TTY environments
|
||||
(CI pipelines, etc.) and skips prompts.
|
||||
- **CLI equivalent**: [`--interactive`](./cli.md#--interactive)
|
||||
|
||||
```yaml
|
||||
interactive: true
|
||||
```
|
||||
|
||||
## Example Configuration
|
||||
|
||||
Here's a complete example of a `.taskrc.yml` file with all available options:
|
||||
|
||||
@@ -155,12 +155,14 @@ silent: true
|
||||
### `dotenv`
|
||||
|
||||
- **Type**: `[]string`
|
||||
- **Description**: Load environment variables from .env files
|
||||
- **Description**: Load environment variables from .env files. When the same
|
||||
variable is defined in multiple files, the first file in the list takes
|
||||
precedence.
|
||||
|
||||
```yaml
|
||||
dotenv:
|
||||
- .env
|
||||
- .env.local
|
||||
- .env.local # Highest priority
|
||||
- .env # Lowest priority
|
||||
```
|
||||
|
||||
### `run`
|
||||
@@ -614,6 +616,27 @@ tasks:
|
||||
- ./deploy.sh
|
||||
```
|
||||
|
||||
#### `if`
|
||||
|
||||
- **Type**: `string`
|
||||
- **Description**: Shell command to conditionally execute the task. If the
|
||||
command exits with a non-zero code, the task is skipped (not failed).
|
||||
|
||||
```yaml
|
||||
tasks:
|
||||
# Task only runs in CI environment
|
||||
deploy:
|
||||
if: '[ "$CI" = "true" ]'
|
||||
cmds:
|
||||
- ./deploy.sh
|
||||
|
||||
# Using Go template expressions
|
||||
build-prod:
|
||||
if: '{{eq .ENV "production"}}'
|
||||
cmds:
|
||||
- go build -ldflags="-s -w" ./...
|
||||
```
|
||||
|
||||
### `dir`
|
||||
|
||||
- **Type**: `string`
|
||||
@@ -632,7 +655,7 @@ tasks:
|
||||
#### `requires`
|
||||
|
||||
- **Type**: `Requires`
|
||||
- **Description**: Required variables with optional enums
|
||||
- **Description**: Required variables with optional enum validation
|
||||
|
||||
```yaml
|
||||
tasks:
|
||||
@@ -657,6 +680,9 @@ tasks:
|
||||
- ./deploy.sh
|
||||
```
|
||||
|
||||
See [Prompting for missing variables interactively](/docs/guide#prompting-for-missing-variables-interactively)
|
||||
for information on enabling interactive prompts for missing required variables.
|
||||
|
||||
#### `watch`
|
||||
|
||||
- **Type**: `bool`
|
||||
@@ -810,6 +836,27 @@ tasks:
|
||||
SERVICE: '{{.ITEM}}'
|
||||
```
|
||||
|
||||
### Conditional Commands
|
||||
|
||||
Use `if` to conditionally execute a command. If the shell command exits with a
|
||||
non-zero code, the command is skipped.
|
||||
|
||||
```yaml
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
# Only run in production
|
||||
- cmd: echo "Optimizing for production"
|
||||
if: '[ "$ENV" = "production" ]'
|
||||
# Using Go templates
|
||||
- cmd: echo "Feature enabled"
|
||||
if: '{{eq .ENABLE_FEATURE "true"}}'
|
||||
# Inside for loops (evaluated per iteration)
|
||||
- for: [a, b, c]
|
||||
cmd: echo "processing {{.ITEM}}"
|
||||
if: '[ "{{.ITEM}}" != "b" ]'
|
||||
```
|
||||
|
||||
## Shell Options
|
||||
|
||||
### Set Options
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 170 KiB |
4
website/src/public/robots.txt
Normal file
4
website/src/public/robots.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://taskfile.dev/sitemap.xml
|
||||
@@ -43,6 +43,10 @@
|
||||
"description": "Expiry duration for cached remote Taskfiles (e.g., '1h', '24h')",
|
||||
"pattern": "^[0-9]+(ns|us|µs|ms|s|m|h)$"
|
||||
},
|
||||
"cache-dir": {
|
||||
"type": "string",
|
||||
"description": "Directory to cache remote Taskfiles"
|
||||
},
|
||||
"trusted-hosts": {
|
||||
"type": "array",
|
||||
"description": "List of trusted hosts for remote Taskfiles (e.g., 'github.com', 'gitlab.com', 'example.com:8080').",
|
||||
@@ -74,6 +78,11 @@
|
||||
"description": "When running tasks in parallel, stop all tasks if one fails.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"interactive": {
|
||||
"description": "Prompt for missing required variables instead of failing. Requires a TTY.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -193,6 +193,10 @@
|
||||
"description": "Specifies which platforms the task should be run on.",
|
||||
"$ref": "#/definitions/platforms"
|
||||
},
|
||||
"if": {
|
||||
"description": "A shell command to evaluate. If the exit code is non-zero, the task is skipped.",
|
||||
"type": "string"
|
||||
},
|
||||
"requires": {
|
||||
"description": "A list of variables which should be set if this task is to run, if any of these variables are unset the task will error and not run",
|
||||
"$ref": "#/definitions/requires_obj"
|
||||
@@ -332,6 +336,10 @@
|
||||
"silent": {
|
||||
"description": "Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"if": {
|
||||
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -369,6 +377,10 @@
|
||||
"platforms": {
|
||||
"description": "Specifies which platforms the command should be run on.",
|
||||
"$ref": "#/definitions/platforms"
|
||||
},
|
||||
"if": {
|
||||
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -447,6 +459,10 @@
|
||||
"platforms": {
|
||||
"description": "Specifies which platforms the command should be run on.",
|
||||
"$ref": "#/definitions/platforms"
|
||||
},
|
||||
"if": {
|
||||
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -619,7 +635,7 @@
|
||||
"name": { "type": "string" },
|
||||
"enum": { "type": "array", "items": { "type": "string" } }
|
||||
},
|
||||
"required": ["name", "enum"],
|
||||
"required": ["name"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user