Compare commits

...

48 Commits

Author SHA1 Message Date
qingyingliu
b5861b1e27 fix: reject include without taskfile or dir (#2892)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-07-01 19:48:47 +00:00
Pete Davison
b5f671e098 feat: changelog for #2905 2026-07-01 14:46:48 +00:00
Pete Davison
c7cb5e1aaa feat: readd example hosted remote taskfile (#2905) 2026-07-01 15:44:33 +01:00
Valentin Maerten
11010d0d81 chore: changelog for #2904 2026-07-01 14:40:32 +02:00
Pete Davison
a6cedbe732 fix: remote taskfiles from ADO git path (#2904) 2026-07-01 14:38:38 +02:00
renovate[bot]
6cf0303ab8 chore(deps): update all non-major dependencies (#2889) 2026-06-30 23:19:27 +02:00
renovate[bot]
b779f852e8 chore(deps): update module golang.org/x/crypto to v0.52.0 [security] (#2903) 2026-06-30 23:11:06 +02:00
Valentin Maerten
48b215db0a ci: enable indirect Go deps and group action digest updates (#2902) 2026-06-30 21:01:24 +00:00
Valentin Maerten
0aa0496f85 ci: fix golangci-lint custom manager and enable OSV alerts in Renovate (#2901) 2026-06-30 20:50:31 +00:00
otjdiepluong
d601746d4b fix: handle nil Vars in ToCacheMap (#2842) 2026-06-30 22:32:07 +02:00
Valentin Maerten
e415d7135e fix(docs): wrap join example in v-pre to fix VitePress build
The inline `{{join " " .WORDS}}` example was parsed as a Vue
interpolation, which broke the website build. Wrap it in <span v-pre>
so it renders literally, matching the existing escaped example in the
same file.
2026-06-30 22:25:27 +02:00
Pete Davison
a61f8ade36 feat: update changelog for #2847 2026-06-29 18:27:59 +00:00
Valentin Maerten
9910c33557 fix(remote): define special variables behavior (#2847) 2026-06-29 19:25:25 +01:00
Markus
b93897a6c3 docs: clarify join argument order in templating reference (#2887)
Co-authored-by: Markus Stark <markusstark@MacBook-Air-von-Markus.local>
2026-06-29 17:08:44 +02:00
Valentin Maerten
7fa9d657cd feat(completion): complete task aliases in zsh (#2865) 2026-06-29 14:21:47 +00:00
kjasn
1c743de2b7 feat: Add temp dir option (#2891)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-06-29 14:13:37 +00:00
Valentin Maerten
f9e52fab40 chore: changelog for #2873 2026-06-29 15:50:50 +02:00
renovate[bot]
80693c1208 chore(deps): update pnpm/action-setup digest to 0ebf471 (#2888) 2026-06-29 15:50:06 +02:00
Or Carmi
d6cc0ce070 feat: configure output flags via environment variables (#2873)
Co-authored-by: Or Carmi <ocarmi@paloaltonetworks.com>
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-06-29 13:48:37 +00:00
Valentin Maerten
d4dc3dc448 chore: changelog for #2773 2026-06-29 15:42:22 +02:00
Valentin Maerten
616433df75 feat: add use_gitignore option to exclude ignored files from sources/generates (#2773) 2026-06-29 15:39:33 +02:00
Valentin Maerten
a03aa7ba69 chore: changelog for #2514 2026-06-29 14:53:00 +02:00
Valentin Maerten
6abbbcb265 feat: do not log secret variables (#2514) 2026-06-29 14:50:08 +02:00
renovate[bot]
c73d53f4e9 chore(deps): update actions/checkout action to v7 (#2896) 2026-06-29 10:06:34 +02:00
Valentin Maerten
d0b903c772 ci: remove needs-build PR workflow (#2895) 2026-06-28 20:16:31 +00:00
Valentin Maerten
1d9b3cb7db chore: changelog for #2894 2026-06-28 22:03:20 +02:00
Amit Mishra
1a0f146888 fix: don't mutate shared matrix AST when resolving ref: rows (#2894)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-06-28 20:03:11 +00:00
Vessel9817
b9b50ca79c fix: $schema according to schemastore standards (#2893) 2026-06-28 19:18:31 +00:00
Valentin Maerten
3dcaa7db89 chore: changelog for #2886 2026-06-21 16:25:14 +02:00
renovate[bot]
a72eb84c15 chore(deps): update all non-major dependencies (#2869) 2026-06-21 16:24:20 +02:00
SEONGHYUN HONG
91b9e42f17 fix: sanitize all invalid characters in checksum filenames (#2886) 2026-06-21 16:23:45 +02:00
SEONGHYUN HONG
f1ab404fbb docs: fix duplicated word in guide (#2885)
Signed-off-by: s3onghyun <s3onghyun.hong@gmail.com>
2026-06-21 14:19:20 +00:00
Valentin Maerten
24a3ccdf42 chore: changelog for #2871 2026-06-07 16:39:02 +02:00
Christian Provenzano
b455286b63 fix: deterministic ordering of required var prompts (#2871) 2026-06-07 16:36:58 +02:00
Valentin Maerten
81d621d8aa chore: add mise config for dev environment setup (#2863)
Co-authored-by: Andrey Nering <andreynering@users.noreply.github.com>
2026-06-07 14:46:01 +02:00
dependabot[bot]
f50327f2a2 chore(deps): bump mdast-util-to-hast from 13.2.0 to 13.2.1 in /website (#2846)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-31 11:13:28 +02:00
Valentin Maerten
dd810d42e1 chore: changelog for #2852 2026-05-31 11:08:42 +02:00
Koji Ishii
de1717b05f feat(completion): ps completion supports aliases (#2852)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-05-31 09:06:39 +00:00
Valentin Maerten
7766e8add5 chore: changelog for #2859 2026-05-31 11:03:32 +02:00
Zhaowei Li
e61d0f0f7c fix: install fish completions to vendor directory (#2859) 2026-05-31 11:01:13 +02:00
renovate[bot]
9c8799480d chore(deps): update goreleaser/goreleaser-action digest to 5daf1e9 (#2857)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-05-31 08:47:42 +00:00
renovate[bot]
7f04f7057c chore(deps): update all non-major dependencies (#2858)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-05-31 10:38:31 +02:00
renovate[bot]
48dd62cdc1 chore(deps): update go-task/setup-task digest to 01a4adf (#2848) 2026-05-18 15:37:33 +02:00
renovate[bot]
b27a6653f6 chore(deps): update all non-major dependencies (#2816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-05-17 12:40:51 +02:00
renovate[bot]
76269b03c4 chore(deps): update pnpm to v11 (#2844)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2026-05-17 12:20:33 +02:00
renovate[bot]
ed492ac862 chore(deps): update pnpm/action-setup digest to 0e279bb (#2843)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-17 11:59:56 +02:00
dependabot[bot]
1d385ad5b4 chore(deps): bump vite from 5.4.19 to 5.4.21 in /website (#2841)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-17 11:59:42 +02:00
Andrey Nering
2eff3545d9 ci(goreleaser): add missing env to workflow 2026-05-16 18:09:51 -03:00
117 changed files with 3927 additions and 1729 deletions

17
.github/renovate.json vendored
View File

@@ -6,20 +6,31 @@
"schedule:weekly", "schedule:weekly",
":semanticCommitTypeAll(chore)" ":semanticCommitTypeAll(chore)"
], ],
"mode": "full",
"addLabels":["area: dependencies"], "addLabels":["area: dependencies"],
"osvVulnerabilityAlerts": true,
"postUpdateOptions": ["gomodTidy"],
"customManagers": [ "customManagers": [
{ {
"customType": "regex", "customType": "regex",
"fileMatch": ["^\\.github/workflows/.*\\.ya?ml$"], "managerFilePatterns": ["/^\\.github/workflows/.*\\.ya?ml$/"],
"matchStrings": [ "matchStrings": [
"uses:\\s*golangci/golangci-lint-action@\\S+\\s+with:\\s+version:\\s*(?<currentValue>v[\\d.]+)" "uses:\\s*golangci/golangci-lint-action@\\S+(?:\\s*#[^\\n]*)?\\s+with:\\s+version:\\s*(?<currentValue>v[\\d.]+)"
], ],
"datasourceTemplate": "github-releases", "datasourceTemplate": "github-releases",
"depNameTemplate": "golangci/golangci-lint" "depNameTemplate": "golangci/golangci-lint"
} }
], ],
"packageRules": [ "packageRules": [
{
"matchManagers": ["gomod"],
"matchDepTypes": ["indirect"],
"enabled": true
},
{
"matchUpdateTypes": ["digest", "pinDigest"],
"groupName": "all non-major dependencies",
"groupSlug": "all-minor-patch"
},
{ {
"matchManagers": ["github-actions"], "matchManagers": ["github-actions"],
"addLabels": ["area: github actions"] "addLabels": ["area: github actions"]

View File

@@ -19,25 +19,25 @@ jobs:
go-version: [1.25.10, 1.26.x] go-version: [1.25.10, 1.26.x]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with: with:
go-version: ${{matrix.go-version}} go-version: ${{matrix.go-version}}
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with: with:
version: v2.11.4 version: v2.12.2
lint-jsonschema: lint-jsonschema:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with: with:
python-version: 3.14 python-version: 3.14
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: install check-jsonschema - name: install check-jsonschema
run: python -m pip install 'check-jsonschema==0.27.3' run: python -m pip install 'check-jsonschema==0.27.3'

View File

@@ -1,69 +0,0 @@
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: "1.26.x"
cache: true
- uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7
with:
version: "~> v2"
args: release --snapshot --clean --config .goreleaser-pr.yml
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: task_linux_amd64
path: dist/task_linux_amd64.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: task_linux_arm64
path: dist/task_linux_arm64.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: task_darwin_amd64
path: dist/task_darwin_amd64.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: task_darwin_arm64
path: dist/task_darwin_arm64.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: task_windows_amd64
path: dist/task_windows_amd64.zip
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: checksums
path: dist/task_checksums.txt
- uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
id: find-comment
with:
token: ${{secrets.GITHUB_TOKEN}}
issue-number: ${{ github.event.pull_request.number }}
body-includes: "📦 Build artifacts ready!"
- uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
with:
token: ${{secrets.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

View File

@@ -13,17 +13,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with: with:
go-version: 1.26.x go-version: 1.26.x
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7 uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: latest version: latest

View File

@@ -14,12 +14,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with: with:
go-version: 1.26.x go-version: 1.26.x
@@ -32,22 +32,23 @@ jobs:
run: npm install -g npm@latest run: npm install -g npm@latest
- name: Install Task - name: Install Task
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2 uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@739bfe42ca9233c5e6aca07c1a25a9d34aca49b0 # v6 uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6
with: with:
package_json_file: "website/package.json" package_json_file: "website/package.json"
run_install: "true" run_install: "true"
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7 uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: latest version: latest
args: release --clean --draft args: release --clean --draft
env: env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
GH_GORELEASER_TOKEN: ${{secrets.GH_GORELEASER_TOKEN}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}} GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}} CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}

View File

@@ -22,10 +22,10 @@ jobs:
runs-on: ${{matrix.platform}} runs-on: ${{matrix.platform}}
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Go ${{matrix.go-version}} - name: Set up Go ${{matrix.go-version}}
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with: with:
go-version: ${{matrix.go-version}} go-version: ${{matrix.go-version}}

View File

@@ -1,31 +0,0 @@
# 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'

View File

@@ -76,7 +76,7 @@ nfpms:
- src: completion/bash/task.bash - src: completion/bash/task.bash
dst: /etc/bash_completion.d/task dst: /etc/bash_completion.d/task
- src: completion/fish/task.fish - src: completion/fish/task.fish
dst: /usr/share/fish/completions/task.fish dst: /usr/share/fish/vendor_completions.d/task.fish
- src: completion/zsh/_task - src: completion/zsh/_task
dst: /usr/local/share/zsh/site-functions/_task dst: /usr/local/share/zsh/site-functions/_task

View File

@@ -5,6 +5,12 @@
"Taskfile.yaml", "Taskfile.yaml",
"taskfile.yml", "taskfile.yml",
"taskfile.yaml" "taskfile.yaml"
],
"./website/src/public/schema-taskrc.json": [
".taskrc.yml",
".taskrc.yaml",
"taskrc.yml",
"taskrc.yaml"
] ]
}, },
"gopls": { "gopls": {

View File

@@ -1,5 +1,43 @@
# Changelog # Changelog
## Unreleased
- Fixed --interactive prompts for required vars sometimes appearing in a random
order. Prompts now follow the order the vars are declared in the Taskfile.
(#2871 by @caproven)
- Fixed Fish completions not being picked up correctly by installing them to
Fish's `vendor_completions.d` directory instead of `completions` (#2850, #2859
by @Legimity).
- PowerShell completions now work with aliases of the `task` command, not just
the `task` binary itself (#2852 by @kojiishi).
- Fixed task and namespace aliases not being completed by the Zsh completion. A
`show-aliases` zstyle can turn this off (#2865, #2864 by @vmaerten).
- Fixed task names containing certain characters (e.g. `\`, `_`, `^`) leaking
into checksum/timestamp filenames, breaking `sources:`/`generates:` up-to-date
detection (#2886 by @s3onghyun).
- Fixed `for: matrix:` loops using `ref:` rows producing wrong values when the
same task was run concurrently (e.g. by parallel `deps`) with different vars
(#2890, #2894 by @amitmishra11).
- Added a `secret: true` flag for variables that masks their value in logs,
`task --summary`, and command output (#2514 by @vmaerten).
- Added the `use_gitignore` setting (global or per-task) to skip files matched
by your `.gitignore` when fingerprinting `sources`/`generates` and when
watching (#2773 by @vmaerten).
- Added support for configuring output flags (`--output`,
`--output-group-begin`, `--output-group-end`, `--output-group-error-only`) via
the `TASK_OUTPUT*` environment variables (#2873 by @liiight).
- Added a `--temp-dir` flag (with `TASK_TEMP_DIR` env var and `temp-dir` taskrc
config) to customise the directory where Task stores temporary files such as
checksums. Relative paths are resolved against the root Taskfile (#2891 by
@kjasn).
- Defined environment variable behavior for remote taskfiles (#2267, #2847 by
@vmaerten).
- Added support for remote Taskfiles hosted on Azure DevOps, whose git URLs use
a `/_git/` path segment rather than a `.git` suffix (#2904 by @pd93).
- Re-added the example remote taskfile at
[taskfile.dev/Taskfile.yml](https://taskfile.dev/Taskfile.yml) (#2905 by
@pd93).
## v3.51.1 - 2026-05-16 ## v3.51.1 - 2026-05-16
- A significant performance boost was achieved for large Taskfiles (monorepos) - A significant performance boost was achieved for large Taskfiles (monorepos)
@@ -8,9 +46,9 @@
cleaning `..` and `.` components (#2681, #2788 by @mateenanjum). cleaning `..` and `.` components (#2681, #2788 by @mateenanjum).
- Added `joinEnv` function to join paths based on your oprating system: `;` for - Added `joinEnv` function to join paths based on your oprating system: `;` for
Windows and `:` elsewhere, and `joinUrl` to join URL paths. Also, added two Windows and `:` elsewhere, and `joinUrl` to join URL paths. Also, added two
new special variables: `FILE_PATH_SEPARATOR` which returns `\` on Windows new special variables: `FILE_PATH_SEPARATOR` which returns `\` on Windows and
and `/` elsewhere, and `PATH_LIST_SEPARATOR` which returns `;` on Windows and `/` elsewhere, and `PATH_LIST_SEPARATOR` which returns `;` on Windows and `:`
`:` elsewhere (#2406, #2408 by @solvingj). elsewhere (#2406, #2408 by @solvingj).
- Update the shell interpreter with a regression fix (#2812, #2832 by - Update the shell interpreter with a regression fix (#2812, #2832 by
@andreynering). @andreynering).
- Fix potential panic with the shell interpreter (#2810 by @trulede). - Fix potential panic with the shell interpreter (#2810 by @trulede).
@@ -26,13 +64,13 @@
- Fixed watch mode ignoring SIGHUP signal, causing the watcher to exit instead - Fixed watch mode ignoring SIGHUP signal, causing the watcher to exit instead
of restarting (#2764, #2642). of restarting (#2764, #2642).
- Fixed a long time bug where the task wouldn't re-run as it should when using - Fixed a long time bug where the task wouldn't re-run as it should when using
`method: timestamp` and the files listed on `generates:` were deleted. `method: timestamp` and the files listed on `generates:` were deleted. This
This makes `method: timestamp` behaves the same as `method: checksum` makes `method: timestamp` behaves the same as `method: checksum` (#1230, #2716
(#1230, #2716 by @drichardson). by @drichardson).
## v3.49.1 - 2026-03-08 ## v3.49.1 - 2026-03-08
* Reverted #2632 for now, which caused some regressions. That change will be - Reverted #2632 for now, which caused some regressions. That change will be
reworked (#2720, #2722, #2723). reworked (#2720, #2722, #2723).
## v3.49.0 - 2026-03-07 ## v3.49.0 - 2026-03-07

View File

@@ -15,6 +15,7 @@ import (
"github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater" "github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/internal/version" "github.com/go-task/task/v3/internal/version"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
@@ -51,7 +52,7 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
return nil, err return nil, err
} }
for k, v := range specialVars { for k, v := range specialVars {
result.Set(k, ast.Var{Value: v}) result.Set(k, ast.Var{Value: v, Secret: false})
} }
getRangeFunc := func(dir string) func(k string, v ast.Var) error { getRangeFunc := func(dir string) func(k string, v ast.Var) error {
@@ -63,12 +64,12 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
// This stops empty interface errors when using the templater to replace values later // This stops empty interface errors when using the templater to replace values later
// Preserve the Sh field so it can be displayed in summary // Preserve the Sh field so it can be displayed in summary
if !evaluateShVars && newVar.Value == nil { if !evaluateShVars && newVar.Value == nil {
result.Set(k, ast.Var{Value: "", Sh: newVar.Sh}) result.Set(k, ast.Var{Value: "", Sh: newVar.Sh, Secret: v.Secret})
return nil return nil
} }
// If the variable should not be evaluated and it is set, we can set it and return // If the variable should not be evaluated and it is set, we can set it and return
if !evaluateShVars { if !evaluateShVars {
result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh}) result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh, Secret: v.Secret})
return nil return nil
} }
// Now we can check for errors since we've handled all the cases when we don't want to evaluate // Now we can check for errors since we've handled all the cases when we don't want to evaluate
@@ -77,7 +78,7 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
} }
// If the variable is already set, we can set it and return // If the variable is already set, we can set it and return
if newVar.Value != nil || newVar.Sh == nil { if newVar.Value != nil || newVar.Sh == nil {
result.Set(k, ast.Var{Value: newVar.Value}) result.Set(k, ast.Var{Value: newVar.Value, Secret: v.Secret})
return nil return nil
} }
// If the variable is dynamic, we need to resolve it first // If the variable is dynamic, we need to resolve it first
@@ -85,7 +86,7 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
if err != nil { if err != nil {
return err return err
} }
result.Set(k, ast.Var{Value: static}) result.Set(k, ast.Var{Value: static, Secret: v.Secret})
return nil return nil
} }
} }
@@ -184,7 +185,12 @@ func (c *Compiler) HandleDynamicVar(v ast.Var, dir string, e []string) (string,
result = strings.TrimSuffix(result, "\n") result = strings.TrimSuffix(result, "\n")
c.dynamicCache[*v.Sh] = result c.dynamicCache[*v.Sh] = result
c.Logger.VerboseErrf(logger.Magenta, "task: dynamic variable: %q result: %q\n", *v.Sh, result) // Never print the resolved value of a secret variable, even in verbose mode
logResult := result
if v.Secret {
logResult = "*****"
}
c.Logger.VerboseErrf(logger.Magenta, "task: dynamic variable: %q result: %q\n", *v.Sh, logResult)
return result, nil return result, nil
} }
@@ -201,10 +207,19 @@ func (c *Compiler) getSpecialVars(t *ast.Task, call *Call) (map[string]string, e
// Use filepath.ToSlash for all paths to ensure consistent forward slashes // Use filepath.ToSlash for all paths to ensure consistent forward slashes
// across platforms. This prevents issues with backslashes being interpreted // across platforms. This prevents issues with backslashes being interpreted
// as escape sequences when paths are used in shell commands on Windows. // as escape sequences when paths are used in shell commands on Windows.
var rootTaskfile, rootDir string
if taskfile.IsRemoteEntrypoint(c.Entrypoint) {
rootTaskfile = c.Entrypoint
rootDir = ""
} else {
rootTaskfile = filepath.ToSlash(filepathext.SmartJoin(c.Dir, c.Entrypoint))
rootDir = filepath.ToSlash(c.Dir)
}
allVars := map[string]string{ allVars := map[string]string{
"TASK_EXE": filepath.ToSlash(os.Args[0]), "TASK_EXE": filepath.ToSlash(os.Args[0]),
"ROOT_TASKFILE": filepath.ToSlash(filepathext.SmartJoin(c.Dir, c.Entrypoint)), "ROOT_TASKFILE": rootTaskfile,
"ROOT_DIR": filepath.ToSlash(c.Dir), "ROOT_DIR": rootDir,
"USER_WORKING_DIR": filepath.ToSlash(c.UserWorkingDir), "USER_WORKING_DIR": filepath.ToSlash(c.UserWorkingDir),
"TASK_VERSION": version.GetVersion(), "TASK_VERSION": version.GetVersion(),
"PATH_LIST_SEPARATOR": string(os.PathListSeparator), "PATH_LIST_SEPARATOR": string(os.PathListSeparator),
@@ -212,9 +227,22 @@ func (c *Compiler) getSpecialVars(t *ast.Task, call *Call) (map[string]string, e
} }
if t != nil { if t != nil {
allVars["TASK"] = t.Task allVars["TASK"] = t.Task
if taskfile.IsRemoteEntrypoint(t.Location.Taskfile) {
allVars["TASKFILE"] = t.Location.Taskfile
allVars["TASKFILE_DIR"] = ""
switch {
case t.Dir == "":
allVars["TASK_DIR"] = filepath.ToSlash(c.UserWorkingDir)
case filepath.IsAbs(t.Dir):
allVars["TASK_DIR"] = filepath.ToSlash(t.Dir)
default:
allVars["TASK_DIR"] = filepath.ToSlash(filepathext.SmartJoin(c.UserWorkingDir, t.Dir))
}
} else {
allVars["TASK_DIR"] = filepath.ToSlash(filepathext.SmartJoin(c.Dir, t.Dir)) allVars["TASK_DIR"] = filepath.ToSlash(filepathext.SmartJoin(c.Dir, t.Dir))
allVars["TASKFILE"] = filepath.ToSlash(t.Location.Taskfile) allVars["TASKFILE"] = filepath.ToSlash(t.Location.Taskfile)
allVars["TASKFILE_DIR"] = filepath.ToSlash(filepath.Dir(t.Location.Taskfile)) allVars["TASKFILE_DIR"] = filepath.ToSlash(filepath.Dir(t.Location.Taskfile))
}
} else { } else {
allVars["TASK"] = "" allVars["TASK"] = ""
allVars["TASK_DIR"] = "" allVars["TASK_DIR"] = ""

135
compiler_internal_test.go Normal file
View File

@@ -0,0 +1,135 @@
package task
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/taskfile/ast"
)
func TestGetSpecialVarsRemote(t *testing.T) {
t.Parallel()
uwd := t.TempDir()
uwdSlash := filepath.ToSlash(uwd)
localProj := filepath.Join(uwd, "proj")
localProjSlash := filepath.ToSlash(localProj)
localTaskfile := filepath.Join(localProj, "Taskfile.yml")
localTaskfileSlash := filepath.ToSlash(localTaskfile)
absTaskDir := filepath.Join(uwd, "opt", "work")
absTaskDirSlash := filepath.ToSlash(absTaskDir)
tests := []struct {
name string
entrypoint string
compilerDir string
taskDir string
taskfileLocation string
wantRootTaskfile string
wantRootDir string
wantTaskfile string
wantTaskfileDir string
wantTaskDir string
}{
{
name: "local entrypoint, local task",
entrypoint: localTaskfile,
compilerDir: localProj,
taskDir: "",
taskfileLocation: localTaskfile,
wantRootTaskfile: localTaskfileSlash,
wantRootDir: localProjSlash,
wantTaskfile: localTaskfileSlash,
wantTaskfileDir: localProjSlash,
wantTaskDir: localProjSlash,
},
{
name: "https entrypoint, empty task.dir",
entrypoint: "https://taskfile.dev/Taskfile.yml",
compilerDir: "",
taskDir: "",
taskfileLocation: "https://taskfile.dev/Taskfile.yml",
wantRootTaskfile: "https://taskfile.dev/Taskfile.yml",
wantRootDir: "",
wantTaskfile: "https://taskfile.dev/Taskfile.yml",
wantTaskfileDir: "",
wantTaskDir: uwdSlash,
},
{
name: "https entrypoint, relative task.dir",
entrypoint: "https://taskfile.dev/Taskfile.yml",
compilerDir: "",
taskDir: "subdir",
taskfileLocation: "https://taskfile.dev/Taskfile.yml",
wantRootTaskfile: "https://taskfile.dev/Taskfile.yml",
wantRootDir: "",
wantTaskfile: "https://taskfile.dev/Taskfile.yml",
wantTaskfileDir: "",
wantTaskDir: filepath.ToSlash(filepathext.SmartJoin(uwd, "subdir")),
},
{
name: "https entrypoint, absolute task.dir",
entrypoint: "https://taskfile.dev/Taskfile.yml",
compilerDir: "",
taskDir: absTaskDir,
taskfileLocation: "https://taskfile.dev/Taskfile.yml",
wantRootTaskfile: "https://taskfile.dev/Taskfile.yml",
wantRootDir: "",
wantTaskfile: "https://taskfile.dev/Taskfile.yml",
wantTaskfileDir: "",
wantTaskDir: absTaskDirSlash,
},
{
name: "git entrypoint",
entrypoint: "https://github.com/foo/bar.git//Taskfile.yml?ref=main",
compilerDir: "",
taskDir: "",
taskfileLocation: "https://github.com/foo/bar.git//Taskfile.yml?ref=main",
wantRootTaskfile: "https://github.com/foo/bar.git//Taskfile.yml?ref=main",
wantRootDir: "",
wantTaskfile: "https://github.com/foo/bar.git//Taskfile.yml?ref=main",
wantTaskfileDir: "",
wantTaskDir: uwdSlash,
},
{
name: "local root, remote included task",
entrypoint: localTaskfile,
compilerDir: localProj,
taskDir: "",
taskfileLocation: "https://taskfile.dev/included.yml",
wantRootTaskfile: localTaskfileSlash,
wantRootDir: localProjSlash,
wantTaskfile: "https://taskfile.dev/included.yml",
wantTaskfileDir: "",
wantTaskDir: uwdSlash,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
c := &Compiler{
Dir: tt.compilerDir,
Entrypoint: tt.entrypoint,
UserWorkingDir: uwd,
}
task := &ast.Task{
Task: "mytask",
Dir: tt.taskDir,
Location: &ast.Location{Taskfile: tt.taskfileLocation},
}
vars, err := c.getSpecialVars(task, nil)
assert.NoError(t, err)
assert.Equal(t, tt.wantRootTaskfile, vars["ROOT_TASKFILE"], "ROOT_TASKFILE")
assert.Equal(t, tt.wantRootDir, vars["ROOT_DIR"], "ROOT_DIR")
assert.Equal(t, tt.wantTaskfile, vars["TASKFILE"], "TASKFILE")
assert.Equal(t, tt.wantTaskfileDir, vars["TASKFILE_DIR"], "TASKFILE_DIR")
assert.Equal(t, tt.wantTaskDir, vars["TASK_DIR"], "TASK_DIR")
})
}
}

View File

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

View File

@@ -13,9 +13,9 @@ function __task_is_experiment_enabled() {
# Listing commands from Taskfile.yml # Listing commands from Taskfile.yml
function __task_list() { function __task_list() {
local -a scripts cmd local -a scripts cmd task_aliases match mbegin mend
local -i enabled=0 local -i enabled=0
local taskfile item task desc local taskfile item task desc task_alias
cmd=($TASK_CMD) cmd=($TASK_CMD)
taskfile=${(Qv)opt_args[(i)-t|--taskfile]} taskfile=${(Qv)opt_args[(i)-t|--taskfile]}
@@ -50,14 +50,31 @@ function __task_list() {
local show_desc local show_desc
zstyle -T ":completion:${curcontext}:" verbose && show_desc=true || show_desc=false zstyle -T ":completion:${curcontext}:" verbose && show_desc=true || show_desc=false
# Read zstyle show-aliases option (default = true via -T)
local show_aliases
zstyle -T ":completion:${curcontext}:" show-aliases && show_aliases=true || show_aliases=false
for item in "${(@)${(f)output}[2,-1]#\* }"; do for item in "${(@)${(f)output}[2,-1]#\* }"; do
task="${item%%:[[:space:]]*}" task="${item%%:[[:space:]]*}"
# Extract the aliases listed in the trailing "(aliases: a, b)" column.
# NB: `aliases` is a reserved zsh parameter, so use a different name.
task_aliases=()
if [[ "$show_aliases" == "true" && "$item" == (#b)*'(aliases: '(*)')' ]]; then
task_aliases=( "${(@s:, :)match[1]}" )
fi
if [[ "$show_desc" == "true" ]]; then if [[ "$show_desc" == "true" ]]; then
local desc="${item##[^[:space:]]##[[:space:]]##}" local desc="${item##[^[:space:]]##[[:space:]]##}"
scripts+=( "${task//:/\\:}:$desc" ) scripts+=( "${task//:/\\:}:$desc" )
for task_alias in $task_aliases; do
scripts+=( "${task_alias//:/\\:}:$desc (alias of $task)" )
done
else else
scripts+=( "$task" ) scripts+=( "$task" )
for task_alias in $task_aliases; do
scripts+=( "$task_alias" )
done
fi fi
done done

View File

@@ -29,6 +29,7 @@ type (
Dir string Dir string
Entrypoint string Entrypoint string
TempDir TempDir TempDir TempDir
TempDirPath string
Force bool Force bool
ForceAll bool ForceAll bool
Insecure bool Insecure bool
@@ -165,6 +166,22 @@ func (o *tempDirOption) ApplyToExecutor(e *Executor) {
e.TempDir = o.tempDir e.TempDir = o.tempDir
} }
// WithTempDirPath sets an unresolved path used to build [Executor.TempDir]
// during [Executor.Setup]. Relative paths are resolved from the root Taskfile
// directory. Use [WithTempDir] when the remote and fingerprint directories have
// already been resolved.
func WithTempDirPath(path string) ExecutorOption {
return &tempDirPathOption{path: path}
}
type tempDirPathOption struct {
path string
}
func (o *tempDirPathOption) ApplyToExecutor(e *Executor) {
e.TempDirPath = o.path
}
// WithForce ensures that the [Executor] always runs a task, even when // WithForce ensures that the [Executor] always runs a task, even when
// fingerprinting or prompts would normally stop it. // fingerprinting or prompts would normally stop it.
func WithForce(force bool) ExecutorOption { func WithForce(force bool) ExecutorOption {

View File

@@ -283,6 +283,68 @@ func TestVars(t *testing.T) {
) )
} }
func TestSecretVars(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithName("secret vars are masked in logs"),
WithExecutorOptions(
task.WithDir("testdata/secrets"),
),
WithTask("test-secret-masking"),
)
NewExecutorTest(t,
WithName("multiple secrets masked"),
WithExecutorOptions(
task.WithDir("testdata/secrets"),
),
WithTask("test-multiple-secrets"),
)
NewExecutorTest(t,
WithName("mixed secret and public vars"),
WithExecutorOptions(
task.WithDir("testdata/secrets"),
),
WithTask("test-mixed"),
)
NewExecutorTest(t,
WithName("deferred command with secrets"),
WithExecutorOptions(
task.WithDir("testdata/secrets"),
),
WithTask("test-deferred-secret"),
)
NewExecutorTest(t,
WithName("env secret limitation"),
WithExecutorOptions(
task.WithDir("testdata/secrets"),
),
WithTask("test-env-secret-limitation"),
)
NewExecutorTest(t,
WithName("secret vars are masked in summary"),
WithExecutorOptions(
task.WithDir("testdata/secrets"),
task.WithSummary(true),
),
WithTask("test-secret-masking"),
)
NewExecutorTest(t,
WithName("dynamic secret masked in verbose"),
WithExecutorOptions(
task.WithDir("testdata/secrets"),
task.WithVerbose(true),
),
WithTask("test-dynamic-secret-verbose"),
)
NewExecutorTest(t,
WithName("secret key order independent"),
WithExecutorOptions(
task.WithDir("testdata/secrets"),
),
WithTask("test-secret-key-order"),
)
}
func TestRequires(t *testing.T) { func TestRequires(t *testing.T) {
t.Parallel() t.Parallel()
NewExecutorTest(t, NewExecutorTest(t,

146
go.mod
View File

@@ -4,17 +4,17 @@ go 1.25.10
require ( require (
charm.land/bubbles/v2 v2.1.0 charm.land/bubbles/v2 v2.1.0
charm.land/bubbletea/v2 v2.0.6 charm.land/bubbletea/v2 v2.0.7
charm.land/lipgloss/v2 v2.0.3 charm.land/lipgloss/v2 v2.0.4
github.com/Ladicle/tabwriter v1.0.0 github.com/Ladicle/tabwriter v1.0.0
github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/semver/v3 v3.5.0
github.com/alecthomas/chroma/v2 v2.23.1 github.com/alecthomas/chroma/v2 v2.27.0
github.com/chainguard-dev/git-urls v1.0.2 github.com/chainguard-dev/git-urls v1.0.2
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/dominikbraun/graph v0.23.0 github.com/dominikbraun/graph v0.23.0
github.com/elliotchance/orderedmap/v3 v3.1.0 github.com/elliotchance/orderedmap/v3 v3.1.0
github.com/fatih/color v1.19.0 github.com/fatih/color v1.19.0
github.com/fsnotify/fsnotify v1.9.0 github.com/fsnotify/fsnotify v1.10.1
github.com/go-task/slim-sprig/v3 v3.0.0 github.com/go-task/slim-sprig/v3 v3.0.0
github.com/go-task/template v0.2.0 github.com/go-task/template v0.2.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
@@ -28,109 +28,109 @@ require (
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/zeebo/xxh3 v1.1.0 github.com/zeebo/xxh3 v1.1.0
go.yaml.in/yaml/v3 v3.0.4 go.yaml.in/yaml/v3 v3.0.4
golang.org/x/sync v0.20.0 golang.org/x/sync v0.21.0
golang.org/x/term v0.43.0 golang.org/x/term v0.44.0
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997 mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117 mvdan.cc/sh/v3 v3.13.2-0.20260613075524-2255122b577b
) )
require ( require (
cel.dev/expr v0.25.1 // indirect cel.dev/expr v0.25.2 // indirect
cloud.google.com/go v0.123.0 // indirect cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.18.2 // indirect cloud.google.com/go/auth v0.20.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // 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/compute/metadata v0.9.0 // indirect
cloud.google.com/go/iam v1.5.3 // indirect cloud.google.com/go/iam v1.11.0 // indirect
cloud.google.com/go/monitoring v1.24.3 // indirect cloud.google.com/go/monitoring v1.29.0 // indirect
cloud.google.com/go/storage v1.61.3 // indirect cloud.google.com/go/storage v1.63.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect github.com/aws/aws-sdk-go-v2 v1.42.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect github.com/aws/aws-sdk-go-v2/config v1.32.26 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.25 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.30 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect github.com/aws/aws-sdk-go-v2/service/s3 v1.104.1 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.2.1 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.31.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.43.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect github.com/aws/smithy-go v1.27.3 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 // indirect github.com/charmbracelet/ultraviolet v0.0.0-20260622092850-f39628c8a989 // indirect
github.com/charmbracelet/x/ansi v0.11.7 // indirect github.com/charmbracelet/x/ansi v0.11.7 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2/v2 v2.2.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/s2a-go v0.1.9 // indirect github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.17 // indirect
github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/googleapis/gax-go/v2 v2.22.0 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.73 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect github.com/hashicorp/go-version v1.9.0 // indirect
github.com/klauspost/compress v1.18.5 // indirect github.com/klauspost/compress v1.18.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.22 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect github.com/mattn/go-runewidth v0.0.24 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pierrec/lz4/v4 v4.1.27 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sergi/go-diff v1.4.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect github.com/spiffe/go-spiffe/v2 v2.8.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.3 // indirect
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8 // indirect github.com/u-root/u-root v0.16.0 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect github.com/ulikunitz/xz v0.5.15 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/sdk v1.44.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect
golang.org/x/crypto v0.51.0 // indirect golang.org/x/crypto v0.53.0 // indirect
golang.org/x/net v0.54.0 // indirect golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.44.0 // indirect golang.org/x/sys v0.46.0 // indirect
golang.org/x/text v0.37.0 // indirect golang.org/x/text v0.38.0 // indirect
golang.org/x/time v0.15.0 // indirect golang.org/x/time v0.15.0 // indirect
google.golang.org/api v0.271.0 // indirect google.golang.org/api v0.287.0 // indirect
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto v0.0.0-20260630182238-925bb5da69e7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7 // indirect
google.golang.org/grpc v1.79.3 // indirect google.golang.org/grpc v1.82.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

323
go.sum
View File

@@ -1,89 +1,87 @@
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g= charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY= charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
charm.land/bubbletea/v2 v2.0.6 h1:UHN/91OyuhaOFGSrBXQ/hMZD8IO1Uc4BvHlgHXL2WJo= charm.land/bubbletea/v2 v2.0.7 h1:7qw2tTAVar7m7klOPBYfTB0mniv/RuexsYwMRNxSeL0=
charm.land/bubbletea/v2 v2.0.6/go.mod h1:MH/D8ZLlN3op37vQvijKuU29g3rqTp+aQapURFonF9g= charm.land/bubbletea/v2 v2.0.7/go.mod h1:DGW2q8gvzHnOpMpZTORs0aySVHCox5C+2Svk0fci1qs=
charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= charm.land/lipgloss/v2 v2.0.4 h1:lcPeVtcp23SNra7lHy8iYE4UC2aIipVQ47sbGyyxR5Q=
charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= charm.land/lipgloss/v2 v2.0.4/go.mod h1:0653x8epbZSzdDfO/XPS1a/uYPOBeSsCssOpJOqDzik=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= 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 v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= 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/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 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= 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.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4=
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= cloud.google.com/go/logging v1.18.0 h1:KhzZq+1cSkPH9YUaKLLhLtQxIHitVayBmk0sGfoM9+k=
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= cloud.google.com/go/logging v1.18.0/go.mod h1:ZGKnpBaURITh+g/uom2VhbiFoFWvejcrHPDhxFtU/gI=
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= cloud.google.com/go/longrunning v1.1.0 h1:qJ0R0IA8ONaRCNWTRPAS0iAmt1bj3TVgJ40z7ZGRslE=
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= cloud.google.com/go/longrunning v1.1.0/go.mod h1:tH+A/6UvNypiPJWAQaKCsh+xiGbB23wUO8egwUXlD2E=
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= cloud.google.com/go/monitoring v1.29.0 h1:AHhDsFaSax1/4k+qlIDX/SDGe6hggnfXJ9dkgD9qBPY=
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/monitoring v1.29.0/go.mod h1:72NOVjJXHY/HBfoLT0+qlCZBT059+9VXLeAnL2PeeVM=
cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg= cloud.google.com/go/storage v1.63.0 h1:hvXF2xfg9I32bjujggxgkEZn/Ej6sJ9pieFgeueBLrQ=
cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk= cloud.google.com/go/storage v1.63.0/go.mod h1:tirWVptrFNo5GEX2DQ47JooF7yaweJdAJ1hYAVMvKzE=
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E=
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= cloud.google.com/go/trace v1.16.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 h1:jLdiS1vO+XJFyDSWRHBx56r4s/NNtcl5J6KyCcWUX/w=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0/go.mod h1:8lmpHY+1VRoteiOwyrQMDt1YGXOrFKCz+1wJW7n3ODY=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0 h1:cSjUzZ7KU8hicTgzaSv9NmSyM9fTVK3y5lsBUl3wOis=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0/go.mod h1:dzcEjy1WJ0Q4u9twNR3LcLhNoYMRCrMCMafpxa0TjPQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 h1:RoO5+d7uCmDqovLrHCr2/BuViUXvdcrNxyNM1pN9dDQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0/go.mod h1:YqwkQPrWSC7+byyc1VlKbWLBF5JsW5IoL6xUkemYSXk=
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg= 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/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.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= 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/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 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 h1:p1BBrg/Hhp6uK7zpejeI8QFXHJeC/mynzi04Sl03k9g=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13/go.mod h1:8cIfkE9MDhkRZGpQ22aV6/lkYeYSozpz16Smrs5x4Ls=
github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= github.com/aws/aws-sdk-go-v2/config v1.32.26 h1:JI+W5B3jUA8UBz2ggbICGd9UCR6/+SB21G8EFl0SFTQ=
github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= github.com/aws/aws-sdk-go-v2/config v1.32.26/go.mod h1:RLE2Ls/wRstvdSz1GPrIWNnXcKZ/znDdWyMuiQxdBoY=
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= github.com/aws/aws-sdk-go-v2/credentials v1.19.25 h1:TzPVjfUZ1hsKafvYE+DIzKXIik2KufQxsPHanlkttbo=
github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= github.com/aws/aws-sdk-go-v2/credentials v1.19.25/go.mod h1:K4hw0buguVvtC74HnVfTRr0LzQQHAWPqJbBU9QGk2Pg=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 h1:r6qZHbT+wxgWO/e9vYNUEtg7lv5+UN3pRqKhLXvnArg=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29/go.mod h1:QRnaRcTVGKPGRy8w78HMQtKUGRYcnMZAANATkeVA6Mo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 h1:VTGy885W5DKBxWRUJbym9hytNaYzsyaPkCHGRRMAOhU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30/go.mod h1:AS0HycUvJRFvTt613AYDOgO2jzw+00cVSMny8XB3yMY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 h1:ZD2+BSw9vFsNlKYIasSNt3uDbjqqXIBcM13UJv/Lx2k=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12/go.mod h1:Ms4zlcVBbXbiP7EVLhl+lgjvA/a7YphqQ3Ih3174EmI=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 h1:V51LGlOq/1VsDsHUdoklAQi7rMmx4qQubvFYAlP2254=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22/go.mod h1:4Pzhyz8hJOm2bepgl+NjvRx8vlUFAIIvJnZ/MkcNPpU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 h1:DRebniUGZ2MqiiIVmQJ04vIXr918hubdHMnarSLEWyU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29/go.mod h1:LfRkPCD8YHDM2E5eTkos2UpwYeZnBcVarTa8L59bJHA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.30 h1:4HbXxyipSYxexU0juMIpdS05dilL6dbB2VQHxxN2vGU=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.30/go.mod h1:G7RP+uhagpKtKhd1BM9N6JQqjCcGEU47K5lBVZQyRQw=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= github.com/aws/aws-sdk-go-v2/service/s3 v1.104.1 h1:yb03KevaOAG5e8suo79Af74vjIQvoeKmjl79WQchLrs=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= github.com/aws/aws-sdk-go-v2/service/s3 v1.104.1/go.mod h1:mreYODw0Y4yv7xeczvqC6vciwFao8lPE9k1l1ulfY6E=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo= github.com/aws/aws-sdk-go-v2/service/signin v1.2.1 h1:BeJmkm5YOZs6lGRGcNoIuLSoTTtGLLCEqlSiRKYodfM=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= github.com/aws/aws-sdk-go-v2/service/signin v1.2.1/go.mod h1:LxYujSTLPRlp2vTtcUO/+1ilrew8ytt6SvQyOgejzFQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= github.com/aws/aws-sdk-go-v2/service/sso v1.31.4 h1:i465b/3c7xJd++pobNIDOggouekCuiWOnB0goQJy+94=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= github.com/aws/aws-sdk-go-v2/service/sso v1.31.4/go.mod h1:Lk7PlmoTYryQmyBG0EXqj5BcUbj3whXdU2s3yGI3EAc=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.7 h1:xbmJAnBbyYPkTzoCNCF/bpJ6ymQHRdXX1vquYfDIGYk=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.7/go.mod h1:Q5N6icH+KJZDLh+ESNwzdv6cZ6vLFF/egy3IOxWhmz4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= github.com/aws/aws-sdk-go-v2/service/sts v1.43.4 h1:Np0vmL7op0Zs5xGacYMMX3v5O5pvZ46xhb5LwDgPj8M=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= github.com/aws/aws-sdk-go-v2/service/sts v1.43.4/go.mod h1:r8wkDOuLaaMFqFiYAb8dGY2A3gJCOujMc6CFOVC4Zhc=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= github.com/aws/smithy-go v1.27.3 h1:F3Zb497UhhskkfpJmfkXswyo+t0sh9OTBnIHjogWbVY=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
@@ -94,8 +92,8 @@ github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiw
github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o= github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 h1:Q9fO0y1Zo5KB/5Vu8JZoLGm1N3RzF9bNj3Ao3xoR+Ac= github.com/charmbracelet/ultraviolet v0.0.0-20260622092850-f39628c8a989 h1:aLA9AmFNKnFr86XM3/Jm9g4xLOVjEgRuttBWUFujdVw=
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468/go.mod h1:bAAz7dh/FTYfC+oiHavL4mX1tOIBZ0ZwYjSi3qE6ivM= github.com/charmbracelet/ultraviolet v0.0.0-20260622092850-f39628c8a989/go.mod h1:f/jRa757WUmaOZrbPspXymbg/GnbF+rwe4OLsG7aXYo=
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
@@ -110,16 +108,16 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/v2 v2.2.2 h1:MYWvNYw8okuqNhwTYO587EZMiDruVa2vhV6fsGpfya0=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2/v2 v2.2.2/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -128,18 +126,18 @@ github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo= github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= 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/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -163,26 +161,26 @@ 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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= github.com/googleapis/enterprise-certificate-proxy v0.3.17 h1:73NfMHdiqo9JFU9+7a5ExpVa10/R29pXfZIaW559nrg=
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/enterprise-certificate-proxy v0.3.17/go.mod h1:rSEsBUemEBZEexP2y6jPp16LUmUbjmSbcPMQizR0o4k=
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 h1:vTCWu1wbdYo7PEZFem/rlr01+Un+wwVmI7wiegFdRLk= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.73 h1:LXhjywNxHsex3qFY2p2iOaHK4nFvdqVp9T9QLdZfpjQ=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72/go.mod h1:Vn+BBgKQHVQYdVQ4NZDICE1Brb+JfaONyDHr3q07oQc= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.73/go.mod h1:AsbUhwFfdK9ipM8G0i8WVHS0IesKck6M0M9NcuMQTJ8=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 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-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-getter v1.8.6 h1:9sQboWULaydVphxc4S64oAI4YqpuCk7nPmvbk131ebY= github.com/hashicorp/go-getter v1.8.6 h1:9sQboWULaydVphxc4S64oAI4YqpuCk7nPmvbk131ebY=
github.com/hashicorp/go-getter v1.8.6/go.mod h1:nVH12eOV2P58dIiL3rsU6Fh3wLeJEKBOJzhMmzlSWoo= github.com/hashicorp/go-getter v1.8.6/go.mod h1:nVH12eOV2P58dIiL3rsU6Fh3wLeJEKBOJzhMmzlSWoo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 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/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 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -194,23 +192,23 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.15/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.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.24/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 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 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 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 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.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25 h1:S1hI5JiKP7883xBzZAr1ydcxrKNSVNm7+3+JwjxZEsg=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/planetscale/vtprotobuf v0.6.1-0.20250313105119-ba97887b0a25/go.mod h1:ZQntvDG8TkPgljxtA0R9frDoND4QORU1VXz015N5Ks4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -225,21 +223,21 @@ github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0
github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc= github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc=
github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.8.1 h1:eXZMLsu+3MLEPJyGJkolqtVrteZfQdUpOWj6LTiDl/E=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/spiffe/go-spiffe/v2 v2.8.1/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 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/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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.16.0 h1:wY40O83MBVks97+Is0WlFlOPSwKQMIrWP9R1IsrExg8=
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8/go.mod h1:JNauIV2zopCBv/6o+umxcT3bKe8YUqYJaTZQYSYpKss= github.com/u-root/u-root v0.16.0/go.mod h1:yL/XdSSW27PdGLgUh4MNRBy54mKM+TBLzpwiB4nwj90=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= 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/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 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
@@ -252,57 +250,58 @@ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s=
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.69.0 h1:2yEATaop1/a1I4psnSLgWVPLWwCzkqWakgJy7xTDVy0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0/go.mod h1:D7J12YRapIekYyPWgGPlA/23pRmpSEZC5xJC/TTLI9U=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 h1:hqxVTu/GtBF+vJ8d1fzW7fRxZFvgoDjWcxwwCaFDYpU=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0/go.mod h1:z5fVEF4X5v0ESvlJqBrrFlBVoj5EQuefZpzsu7R+x5Q=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY= google.golang.org/api v0.287.0 h1:CQDMqUiqZZ0U/Yge3zyjAhNQ0OSYEH0PaA7l4xtEen4=
google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q= google.golang.org/api v0.287.0/go.mod h1:pPW85yt3Iuc3unkpaMhFtMmOqnTdCwCqEOaUlnuxRlQ=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= google.golang.org/genproto v0.0.0-20260630182238-925bb5da69e7 h1:lQG76ePMKmtujel4VIVMiFoHVWVNtJdawbCZJtWlVXU=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= google.golang.org/genproto v0.0.0-20260630182238-925bb5da69e7/go.mod h1:LwlOWYBU335L+sR55UuR5fbbU8KmEX+3tUHf3SwMmhM=
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 h1:jQ9p21COKWjP3VwuFrNRiiOTMh3mPpN45R7SLrH/HUU=
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7/go.mod h1:KqHwBx2upmfa1XSi1WuRvC+2VGCLtooKkfmyvRbUmqA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7 h1:eM/YSd5bBFagF51o1E745Ta7RwzpW0h+z+QDNZOgmQ8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -315,7 +314,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997 h1:3bbJwtPFh98dJ6lxRdR3eLHTH1CmR3BcU6TriIMiXjE= 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/moreinterp v0.0.0-20260120230322-19def062a997/go.mod h1:Qy/zdaMDxq9sT72Gi43K3gsV+TtTohyDO3f1cyBVwuo=
mvdan.cc/sh/v3 v3.13.2-0.20260503214111-9e7dd28c81cf h1:3mGRe/xSr7fd9m+c5FSab/CSCtADsbdMcyhYGdVR6DY= mvdan.cc/sh/v3 v3.13.2-0.20260613075524-2255122b577b h1:NREoadYF42Gu7127VIccx/SRia+Bz8wpKBaqmXKiGXE=
mvdan.cc/sh/v3 v3.13.2-0.20260503214111-9e7dd28c81cf/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0= mvdan.cc/sh/v3 v3.13.2-0.20260613075524-2255122b577b/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117 h1:BfzdGSjcnJBb8sPNLudpzTml8zFUxS1N0N/v9IIS6tQ=
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=

View File

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

View File

@@ -0,0 +1,211 @@
package fingerprint
import (
"bufio"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
"github.com/go-task/task/v3/internal/gitignore"
)
type linesCacheEntry struct {
mtime time.Time
lines []string
}
var (
gitignoreLinesCache sync.Map // dir -> linesCacheEntry, invalidated by mtime
repoRootCache sync.Map // dir -> repo root (or "" when not in a repo)
)
// findRepoRoot returns the first ancestor of dir containing a .git entry, or
// ("", false) when dir is not inside a git repository.
func findRepoRoot(dir string) (string, bool) {
if v, ok := repoRootCache.Load(dir); ok {
root := v.(string)
return root, root != ""
}
current := dir
for {
if _, err := os.Stat(filepath.Join(current, ".git")); err == nil {
repoRootCache.Store(dir, current)
return current, true
}
parent := filepath.Dir(current)
if parent == current {
break
}
current = parent
}
repoRootCache.Store(dir, "")
return "", false
}
// filterGitignored marks entries matching gitignore rules as excluded (false).
// All .gitignore files from the repo root down to each candidate file's
// directory feed a single matcher so that precedence and cross-file negations
// (`!pattern`) resolve correctly.
func filterGitignored(files map[string]bool, dir string) map[string]bool {
if len(files) == 0 {
return files
}
absDir, err := filepath.Abs(dir)
if err != nil {
return files
}
repoRoot, ok := findRepoRoot(absDir)
if !ok {
return files
}
// Every directory from the repo root down to each candidate file's dir, so
// nested .gitignore files reached by deep globs are included too.
dirSet := make(map[string]struct{})
for path, included := range files {
if !included {
continue
}
absPath, err := filepath.Abs(path)
if err != nil {
continue
}
d := filepath.Dir(absPath)
if !withinRepo(repoRoot, d) {
continue
}
for {
dirSet[d] = struct{}{}
if d == repoRoot {
break
}
parent := filepath.Dir(d)
if parent == d {
break
}
d = parent
}
}
// Shallow dirs first (lower priority): the matcher scans patterns last to
// first, so deeper rules win and can negate shallower ones.
dirs := make([]string, 0, len(dirSet))
for d := range dirSet {
dirs = append(dirs, d)
}
sort.Slice(dirs, func(i, j int) bool {
di := strings.Count(dirs[i], string(filepath.Separator))
dj := strings.Count(dirs[j], string(filepath.Separator))
if di != dj {
return di < dj
}
return dirs[i] < dirs[j]
})
var patterns []gitignore.Pattern
for _, d := range dirs {
lines := readGitignoreLines(d)
if len(lines) == 0 {
continue
}
// domain scopes each pattern to its .gitignore subtree (go-git semantics).
var domain []string
if rel, err := filepath.Rel(repoRoot, d); err == nil && rel != "." {
domain = strings.Split(filepath.ToSlash(rel), "/")
}
for _, line := range lines {
patterns = append(patterns, gitignore.ParsePattern(line, domain))
}
}
if len(patterns) == 0 {
return files
}
matcher := gitignore.NewMatcher(patterns)
for path, included := range files {
if !included {
continue
}
absPath, err := filepath.Abs(path)
if err != nil {
continue
}
relPath, err := filepath.Rel(repoRoot, absPath)
if err != nil || relPath == ".." || strings.HasPrefix(relPath, ".."+string(filepath.Separator)) {
continue
}
if ignored(matcher, strings.Split(filepath.ToSlash(relPath), "/")) {
files[path] = false
}
}
return files
}
// ignored honors Git's rule that a file under an ignored directory cannot be
// re-included by a deeper negation: if any ancestor directory is ignored, so is
// the file. Otherwise the file's own verdict applies (isDir=false).
func ignored(matcher gitignore.Matcher, segments []string) bool {
for i := 1; i < len(segments); i++ {
if matcher.Match(segments[:i], true) {
return true
}
}
return matcher.Match(segments, false)
}
func withinRepo(repoRoot, p string) bool {
rel, err := filepath.Rel(repoRoot, p)
if err != nil {
return false
}
return rel == "." || (rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)))
}
// readGitignoreLines returns the .gitignore lines in dir (nil if none), cached
// per directory and invalidated by mtime so watch mode picks up edits.
func readGitignoreLines(dir string) []string {
path := filepath.Join(dir, ".gitignore")
info, err := os.Stat(path)
if err != nil {
return nil
}
mtime := info.ModTime()
if v, ok := gitignoreLinesCache.Load(dir); ok {
entry := v.(linesCacheEntry)
if entry.mtime.Equal(mtime) {
return entry.lines
}
}
lines := parseGitignoreLines(path)
gitignoreLinesCache.Store(dir, linesCacheEntry{mtime: mtime, lines: lines})
return lines
}
func parseGitignoreLines(path string) []string {
f, err := os.Open(path)
if err != nil {
return nil
}
defer f.Close()
var lines []string
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimRight(scanner.Text(), "\r")
if line != "" && !strings.HasPrefix(line, "#") {
lines = append(lines, line)
}
}
// On a scan error (e.g. an over-long line) keep what was parsed rather than
// dropping the whole file.
return lines
}

View File

@@ -0,0 +1,254 @@
package fingerprint
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/go-task/task/v3/taskfile/ast"
)
func initGitRepo(t *testing.T, dir string) {
t.Helper()
require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755))
}
func TestGlobsWithGitignore(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
require.NoError(t, os.WriteFile(filepath.Join(dir, "included.txt"), []byte("included"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "ignored.log"), []byte("ignored"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "also-included.txt"), []byte("also included"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.log\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
filesWithout, err := Globs(dir, globs, false)
require.NoError(t, err)
filesWith, err := Globs(dir, globs, true)
require.NoError(t, err)
hasLog := false
for _, f := range filesWithout {
if filepath.Base(f) == "ignored.log" {
hasLog = true
break
}
}
assert.True(t, hasLog, "ignored.log should be present without gitignore filter")
hasLog = false
for _, f := range filesWith {
if filepath.Base(f) == "ignored.log" {
hasLog = true
break
}
}
assert.False(t, hasLog, "ignored.log should be excluded with gitignore filter")
txtCount := 0
for _, f := range filesWith {
if filepath.Ext(f) == ".txt" {
txtCount++
}
}
assert.Equal(t, 2, txtCount, "both .txt files should remain")
}
func TestGlobsWithGitignoreParentDirIgnored(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
buildDir := filepath.Join(dir, "build")
require.NoError(t, os.MkdirAll(buildDir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(buildDir, "keep.txt"), []byte("keep"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(buildDir, "other.txt"), []byte("other"), 0o644))
// Git cannot re-include a file under an ignored directory.
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("build/\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(buildDir, ".gitignore"), []byte("!keep.txt\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./**/*"},
}
files, err := Globs(dir, globs, true)
require.NoError(t, err)
for _, f := range files {
base := filepath.Base(f)
assert.NotEqual(t, "keep.txt", base, "keep.txt must stay excluded under ignored build/")
assert.NotEqual(t, "other.txt", base, "other.txt must stay excluded under ignored build/")
}
}
func TestGlobsWithGitignoreNested(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
subDir := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subDir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "keep.txt"), []byte("keep"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "build.out"), []byte("build"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.log\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, ".gitignore"), []byte("*.out\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
files, err := Globs(subDir, globs, true)
require.NoError(t, err)
for _, f := range files {
assert.NotEqual(t, "build.out", filepath.Base(f), "build.out should be excluded by nested .gitignore")
}
}
func TestGlobsWithGitignoreCrossFileNegation(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
subDir := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subDir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "debug.log"), []byte("debug"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "other.log"), []byte("other"), 0o644))
// Root ignores all *.log; a nested .gitignore re-includes debug.log.
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.log\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, ".gitignore"), []byte("!debug.log\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
files, err := Globs(subDir, globs, true)
require.NoError(t, err)
hasDebug, hasOther := false, false
for _, f := range files {
switch filepath.Base(f) {
case "debug.log":
hasDebug = true
case "other.log":
hasOther = true
}
}
assert.True(t, hasDebug, "debug.log should be re-included by the nested negation")
assert.False(t, hasOther, "other.log should remain excluded by the root *.log rule")
}
func TestGlobsWithGitignoreDeepGlob(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
subDir := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subDir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "keep.txt"), []byte("keep"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "gen.out"), []byte("gen"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, ".gitignore"), []byte("*.out\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./**/*"},
}
files, err := Globs(dir, globs, true)
require.NoError(t, err)
for _, f := range files {
assert.NotEqual(t, "gen.out", filepath.Base(f), "gen.out should be excluded by the nested .gitignore reached via deep glob")
}
}
func TestGlobsWithGitignoreDoubleDotFile(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
// A ".."-prefixed name must not be skipped by the out-of-tree guard.
require.NoError(t, os.WriteFile(filepath.Join(dir, "..keep.log"), []byte("x"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "keep.txt"), []byte("y"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.log\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
files, err := Globs(dir, globs, true)
require.NoError(t, err)
for _, f := range files {
assert.NotEqual(t, "..keep.log", filepath.Base(f), "..keep.log should be excluded by *.log")
}
}
func TestGlobsWithGitignoreLongLine(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
require.NoError(t, os.WriteFile(filepath.Join(dir, "ignored.log"), []byte("x"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "keep.txt"), []byte("y"), 0o644))
// A line over bufio.Scanner's 64KB limit triggers a scan error; patterns
// parsed before it must survive.
longLine := strings.Repeat("a", 70*1024)
content := "*.log\n" + longLine + "\n"
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(content), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
files, err := Globs(dir, globs, true)
require.NoError(t, err)
for _, f := range files {
assert.NotEqual(t, "ignored.log", filepath.Base(f), "*.log parsed before the long line should still apply")
}
}
func TestGlobsWithGitignoreNoRepo(t *testing.T) {
t.Parallel()
// Cannot use t.TempDir() here because it creates a dir inside the
// go-task repo which has a .git parent, defeating the "no repo" test.
dir, err := os.MkdirTemp("", "task-gitignore-norepo-*") //nolint:usetesting
require.NoError(t, err)
t.Cleanup(func() { os.RemoveAll(dir) })
require.NoError(t, os.WriteFile(filepath.Join(dir, "file.txt"), []byte("content"), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
files, err := Globs(dir, globs, true)
require.NoError(t, err)
assert.Len(t, files, 1)
}

View File

@@ -10,7 +10,7 @@ import (
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
func Globs(dir string, globs []*ast.Glob) ([]string, error) { func Globs(dir string, globs []*ast.Glob, useGitignore bool) ([]string, error) {
resultMap := make(map[string]bool) resultMap := make(map[string]bool)
for _, g := range globs { for _, g := range globs {
matches, err := glob(dir, g.Glob) matches, err := glob(dir, g.Glob)
@@ -21,6 +21,11 @@ func Globs(dir string, globs []*ast.Glob) ([]string, error) {
resultMap[match] = !g.Negate resultMap[match] = !g.Negate
} }
} }
if useGitignore {
resultMap = filterGitignored(resultMap, dir)
}
return collectKeys(resultMap), nil return collectKeys(resultMap), nil
} }

View File

@@ -89,7 +89,7 @@ func (*ChecksumChecker) Kind() string {
} }
func (c *ChecksumChecker) checksum(t *ast.Task) (string, error) { func (c *ChecksumChecker) checksum(t *ast.Task) (string, error) {
sources, err := Globs(t.Dir, t.Sources) sources, err := Globs(t.Dir, t.Sources, t.ShouldUseGitignore())
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -119,7 +119,7 @@ func (checker *ChecksumChecker) checksumFilePath(t *ast.Task) string {
return filepath.Join(checker.tempDir, "checksum", normalizeFilename(t.Name())) return filepath.Join(checker.tempDir, "checksum", normalizeFilename(t.Name()))
} }
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]") var checksumFilenameRegexp = regexp.MustCompile("[^[:alnum:]]")
// replaces invalid characters on filenames with "-" // replaces invalid characters on filenames with "-"
func normalizeFilename(f string) string { func normalizeFilename(f string) string {

View File

@@ -16,6 +16,10 @@ func TestNormalizeFilename(t *testing.T) {
{"foo/bar/baz", "foo-bar-baz"}, {"foo/bar/baz", "foo-bar-baz"},
{"foo@bar/baz", "foo-bar-baz"}, {"foo@bar/baz", "foo-bar-baz"},
{"foo1bar2baz3", "foo1bar2baz3"}, {"foo1bar2baz3", "foo1bar2baz3"},
{"foo\\bar", "foo-bar"},
{"foo_bar", "foo-bar"},
{"foo[bar]baz", "foo-bar-baz"},
{"foo^bar`baz", "foo-bar-baz"},
} }
for _, test := range tests { for _, test := range tests {
assert.Equal(t, test.Out, normalizeFilename(test.In)) assert.Equal(t, test.Out, normalizeFilename(test.In))

View File

@@ -28,7 +28,7 @@ func (checker *TimestampChecker) IsUpToDate(t *ast.Task) (bool, error) {
return false, nil return false, nil
} }
sources, err := Globs(t.Dir, t.Sources) sources, err := Globs(t.Dir, t.Sources, t.ShouldUseGitignore())
if err != nil { if err != nil {
return false, nil return false, nil
} }
@@ -54,7 +54,7 @@ func (checker *TimestampChecker) IsUpToDate(t *ast.Task) (bool, error) {
} }
} }
generates, err := Globs(t.Dir, t.Generates) generates, err := Globs(t.Dir, t.Generates, t.ShouldUseGitignore())
if err != nil { if err != nil {
return false, nil return false, nil
} }
@@ -112,7 +112,7 @@ func (checker *TimestampChecker) Kind() string {
// Value implements the Checker Interface // Value implements the Checker Interface
func (checker *TimestampChecker) Value(t *ast.Task) (any, error) { func (checker *TimestampChecker) Value(t *ast.Task) (any, error) {
sources, err := Globs(t.Dir, t.Sources) sources, err := Globs(t.Dir, t.Sources, t.ShouldUseGitignore())
if err != nil { if err != nil {
return time.Now(), err return time.Now(), err
} }

View File

@@ -87,6 +87,7 @@ var (
Cert string Cert string
CertKey string CertKey string
Interactive bool Interactive bool
TempDir string
) )
func init() { func init() {
@@ -143,10 +144,11 @@ func init() {
pflag.BoolVarP(&ExitCode, "exit-code", "x", false, "Pass-through the exit code of the task command.") pflag.BoolVarP(&ExitCode, "exit-code", "x", false, "Pass-through the exit code of the task command.")
pflag.StringVarP(&Dir, "dir", "d", "", "Sets the directory in which Task will execute and look for a Taskfile.") pflag.StringVarP(&Dir, "dir", "d", "", "Sets the directory in which Task will execute and look for a Taskfile.")
pflag.StringVarP(&Entrypoint, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml".`) pflag.StringVarP(&Entrypoint, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml".`)
pflag.StringVarP(&Output.Name, "output", "o", "", "Sets output style: [interleaved|group|prefixed].") pflag.StringVar(&TempDir, "temp-dir", getConfig(config, "TEMP_DIR", func() *string { return config.TempDir }, ""), "Sets the directory used to store Task temporary files, such as checksums. Relative paths are relative to the root Taskfile.")
pflag.StringVar(&Output.Group.Begin, "output-group-begin", "", "Message template to print before a task's grouped output.") pflag.StringVarP(&Output.Name, "output", "o", getConfig(config, "OUTPUT", func() *string { return nil }, ""), "Sets output style: [interleaved|group|prefixed].")
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.") pflag.StringVar(&Output.Group.Begin, "output-group-begin", getConfig(config, "OUTPUT_GROUP_BEGIN", func() *string { return nil }, ""), "Message template to print before a task's grouped output.")
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.") pflag.StringVar(&Output.Group.End, "output-group-end", getConfig(config, "OUTPUT_GROUP_END", func() *string { return nil }, ""), "Message template to print after a task's grouped output.")
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", getConfig(config, "OUTPUT_GROUP_ERROR_ONLY", func() *bool { return nil }, false), "Swallow output from successful tasks.")
pflag.BoolVarP(&Color, "color", "c", getConfig(config, "COLOR", func() *bool { return config.Color }, true), "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.") pflag.BoolVarP(&Color, "color", "c", getConfig(config, "COLOR", func() *bool { return config.Color }, true), "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, "CONCURRENCY", func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.") pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, "CONCURRENCY", func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.") pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
@@ -308,6 +310,7 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithTaskSorter(sorter), task.WithTaskSorter(sorter),
task.WithVersionCheck(true), task.WithVersionCheck(true),
task.WithFailfast(Failfast), task.WithFailfast(Failfast),
task.WithTempDirPath(TempDir),
) )
} }

201
internal/gitignore/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 Sourced Technologies, S.L.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,34 @@
// Vendored from go-git: github.com/go-git/go-git/v5 v5.19.1,
// plumbing/format/gitignore/matcher.go. Licensed under the Apache License 2.0;
// see the LICENSE file in this directory.
package gitignore
// Matcher defines a global multi-pattern matcher for gitignore patterns
type Matcher interface {
// Match matches patterns in the order of priorities. As soon as an inclusion or
// exclusion is found, not further matching is performed.
Match(path []string, isDir bool) bool
}
// NewMatcher constructs a new global matcher. Patterns must be given in the order of
// increasing priority. That is most generic settings files first, then the content of
// the repo .gitignore, then content of .gitignore down the path or the repo and then
// the content command line arguments.
func NewMatcher(ps []Pattern) Matcher {
return &matcher{ps}
}
type matcher struct {
patterns []Pattern
}
func (m *matcher) Match(path []string, isDir bool) bool {
n := len(m.patterns)
for i := n - 1; i >= 0; i-- {
if match := m.patterns[i].Match(path, isDir); match > NoMatch {
return match == Exclude
}
}
return false
}

View File

@@ -0,0 +1,23 @@
// Test cases ported from go-git: github.com/go-git/go-git/v5 v5.19.1,
// plumbing/format/gitignore/matcher_test.go. Licensed under the Apache
// License 2.0; see LICENSE.
package gitignore
import "testing"
func TestMatcher_Match(t *testing.T) {
t.Parallel()
m := NewMatcher([]Pattern{
ParsePattern("**/middle/v[uo]l?ano", nil),
ParsePattern("!volcano", nil),
})
if got := m.Match([]string{"head", "middle", "vulkano"}, false); got != true {
t.Errorf("Match(vulkano) = %t, want true", got)
}
if got := m.Match([]string{"head", "middle", "volcano"}, false); got != false {
t.Errorf("Match(volcano) = %t, want false (negated)", got)
}
}

View File

@@ -0,0 +1,165 @@
// Package gitignore implements gitignore pattern matching.
//
// This package is vendored from go-git:
//
// github.com/go-git/go-git/v5 v5.19.1, plumbing/format/gitignore
//
// Only the pattern parsing and matching logic (pattern.go and matcher.go) is
// copied; the file-walking helpers (dir.go) are omitted as they pull in
// go-billy and other go-git internals. The original code is licensed under the
// Apache License 2.0; see the LICENSE file in this directory.
package gitignore
import (
"path/filepath"
"strings"
)
// MatchResult defines outcomes of a match, no match, exclusion or inclusion.
type MatchResult int
const (
// NoMatch defines the no match outcome of a match check
NoMatch MatchResult = iota
// Exclude defines an exclusion of a file as a result of a match check
Exclude
// Include defines an explicit inclusion of a file as a result of a match check
Include
)
const (
inclusionPrefix = "!"
zeroToManyDirs = "**"
patternDirSep = "/"
)
// Pattern defines a single gitignore pattern.
type Pattern interface {
// Match matches the given path to the pattern.
Match(path []string, isDir bool) MatchResult
}
type pattern struct {
domain []string
pattern []string
inclusion bool
dirOnly bool
isGlob bool
}
// ParsePattern parses a gitignore pattern string into the Pattern structure.
func ParsePattern(p string, domain []string) Pattern {
// storing domain, copy it to ensure it isn't changed externally
domain = append([]string(nil), domain...)
res := pattern{domain: domain}
if strings.HasPrefix(p, inclusionPrefix) {
res.inclusion = true
p = p[1:]
}
if !strings.HasSuffix(p, "\\ ") {
p = strings.TrimRight(p, " ")
}
if strings.HasSuffix(p, patternDirSep) {
res.dirOnly = true
p = p[:len(p)-1]
}
if strings.Contains(p, patternDirSep) {
res.isGlob = true
}
res.pattern = strings.Split(p, patternDirSep)
return &res
}
func (p *pattern) Match(path []string, isDir bool) MatchResult {
if len(path) <= len(p.domain) {
return NoMatch
}
for i, e := range p.domain {
if path[i] != e {
return NoMatch
}
}
path = path[len(p.domain):]
if p.isGlob && !p.globMatch(path, isDir) {
return NoMatch
} else if !p.isGlob && !p.simpleNameMatch(path, isDir) {
return NoMatch
}
if p.inclusion {
return Include
} else {
return Exclude
}
}
func (p *pattern) simpleNameMatch(path []string, isDir bool) bool {
for i, name := range path {
if match, err := filepath.Match(p.pattern[0], name); err != nil {
return false
} else if !match {
continue
}
if p.dirOnly && !isDir && i == len(path)-1 {
return false
}
return true
}
return false
}
func (p *pattern) globMatch(path []string, isDir bool) bool {
matched := false
canTraverse := false
for i, pattern := range p.pattern {
if pattern == "" {
canTraverse = false
continue
}
if pattern == zeroToManyDirs {
if i == len(p.pattern)-1 {
break
}
canTraverse = true
continue
}
if strings.Contains(pattern, zeroToManyDirs) {
return false
}
if len(path) == 0 {
return false
}
if canTraverse {
canTraverse = false
for len(path) > 0 {
e := path[0]
path = path[1:]
if match, err := filepath.Match(pattern, e); err != nil {
return false
} else if match {
matched = true
break
} else if len(path) == 0 {
// if nothing left then fail
matched = false
}
}
} else {
if match, err := filepath.Match(pattern, path[0]); err != nil || !match {
return false
}
matched = true
path = path[1:]
}
}
if matched && p.dirOnly && !isDir && len(path) == 0 {
matched = false
}
return matched
}

View File

@@ -0,0 +1,80 @@
// Test cases ported from go-git: github.com/go-git/go-git/v5 v5.19.1,
// plumbing/format/gitignore/pattern_test.go (originally written against
// gopkg.in/check.v1; rewritten here as table-driven stdlib tests to avoid an
// extra test dependency). Licensed under the Apache License 2.0; see LICENSE.
package gitignore
import "testing"
func TestParsePattern_Match(t *testing.T) {
t.Parallel()
tests := []struct {
name string
pattern string
domain []string
path []string
isDir bool
want MatchResult
}{
{"inclusion", "!vul?ano", nil, []string{"value", "vulkano", "tail"}, false, Include},
{"domainLonger_mismatch", "value", []string{"head", "middle", "tail"}, []string{"head", "middle"}, false, NoMatch},
{"domainSameLength_mismatch", "value", []string{"head", "middle", "tail"}, []string{"head", "middle", "tail"}, false, NoMatch},
{"domainMismatch_mismatch", "value", []string{"head", "middle", "tail"}, []string{"head", "middle", "_tail_", "value"}, false, NoMatch},
{"withDomain", "middle/", []string{"value", "volcano"}, []string{"value", "volcano", "middle", "tail"}, false, Exclude},
{"onlyMatchInDomain_mismatch", "volcano/", []string{"value", "volcano"}, []string{"value", "volcano", "tail"}, true, NoMatch},
{"atStart", "value", nil, []string{"value", "tail"}, false, Exclude},
{"inTheMiddle", "value", nil, []string{"head", "value", "tail"}, false, Exclude},
{"atEnd", "value", nil, []string{"head", "value"}, false, Exclude},
{"atStart_dirWanted", "value/", nil, []string{"value", "tail"}, false, Exclude},
{"inTheMiddle_dirWanted", "value/", nil, []string{"head", "value", "tail"}, false, Exclude},
{"atEnd_dirWanted", "value/", nil, []string{"head", "value"}, true, Exclude},
{"atEnd_dirWanted_notADir_mismatch", "value/", nil, []string{"head", "value"}, false, NoMatch},
{"mismatch", "value", nil, []string{"head", "val", "tail"}, false, NoMatch},
{"valueLonger_mismatch", "val", nil, []string{"head", "value", "tail"}, false, NoMatch},
{"withAsterisk", "v*o", nil, []string{"value", "vulkano", "tail"}, false, Exclude},
{"withQuestionMark", "vul?ano", nil, []string{"value", "vulkano", "tail"}, false, Exclude},
{"magicChars", "v[ou]l[kc]ano", nil, []string{"value", "volcano"}, false, Exclude},
{"wrongPattern_mismatch", "v[ou]l[", nil, []string{"value", "vol["}, false, NoMatch},
{"glob_fromRootWithSlash", "/value/vul?ano", nil, []string{"value", "vulkano", "tail"}, false, Exclude},
{"glob_withDomain", "middle/tail/", []string{"value", "volcano"}, []string{"value", "volcano", "middle", "tail"}, true, Exclude},
{"glob_onlyMatchInDomain_mismatch", "volcano/tail", []string{"value", "volcano"}, []string{"value", "volcano", "tail"}, false, NoMatch},
{"glob_fromRootWithoutSlash", "value/vul?ano", nil, []string{"value", "vulkano", "tail"}, false, Exclude},
{"glob_fromRoot_mismatch", "value/vulkano", nil, []string{"value", "volcano"}, false, NoMatch},
{"glob_fromRoot_tooShort_mismatch", "value/vul?ano", nil, []string{"value"}, false, NoMatch},
{"glob_fromRoot_notAtRoot_mismatch", "/value/volcano", nil, []string{"value", "value", "volcano"}, false, NoMatch},
{"glob_leadingAsterisks_atStart", "**/*lue/vol?ano", nil, []string{"value", "volcano", "tail"}, false, Exclude},
{"glob_leadingAsterisks_notAtStart", "**/*lue/vol?ano", nil, []string{"head", "value", "volcano", "tail"}, false, Exclude},
{"glob_leadingAsterisks_mismatch", "**/*lue/vol?ano", nil, []string{"head", "value", "Volcano", "tail"}, false, NoMatch},
{"glob_leadingAsterisks_isDir", "**/*lue/vol?ano/", nil, []string{"head", "value", "volcano", "tail"}, false, Exclude},
{"glob_leadingAsterisks_isDirAtEnd", "**/*lue/vol?ano/", nil, []string{"head", "value", "volcano"}, true, Exclude},
{"glob_leadingAsterisks_isDir_mismatch", "**/*lue/vol?ano/", nil, []string{"head", "value", "Colcano"}, true, NoMatch},
{"glob_leadingAsterisks_isDirNoDirAtEnd_mismatch", "**/*lue/vol?ano/", nil, []string{"head", "value", "volcano"}, false, NoMatch},
{"glob_tailingAsterisks", "/*lue/vol?ano/**", nil, []string{"value", "volcano", "tail", "moretail"}, false, Exclude},
{"glob_tailingAsterisks_exactMatch", "/*lue/vol?ano/**", nil, []string{"value", "volcano"}, false, Exclude},
{"glob_middleAsterisks_emptyMatch", "/*lue/**/vol?ano", nil, []string{"value", "volcano"}, false, Exclude},
{"glob_middleAsterisks_oneMatch", "/*lue/**/vol?ano", nil, []string{"value", "middle", "volcano"}, false, Exclude},
{"glob_middleAsterisks_multiMatch", "/*lue/**/vol?ano", nil, []string{"value", "middle1", "middle2", "volcano"}, false, Exclude},
{"glob_middleAsterisks_isDir_trailing", "/*lue/**/vol?ano/", nil, []string{"value", "middle1", "middle2", "volcano"}, true, Exclude},
{"glob_middleAsterisks_isDir_trailing_mismatch", "/*lue/**/vol?ano/", nil, []string{"value", "middle1", "middle2", "volcano"}, false, NoMatch},
{"glob_middleAsterisks_isDir", "/*lue/**/vol?ano/", nil, []string{"value", "middle1", "middle2", "volcano", "tail"}, false, Exclude},
{"glob_wrongDoubleAsterisk_mismatch", "/*lue/**foo/vol?ano", nil, []string{"value", "foo", "volcano", "tail"}, false, NoMatch},
{"glob_magicChars", "**/head/v[ou]l[kc]ano", nil, []string{"value", "head", "volcano"}, false, Exclude},
{"glob_wrongPattern_noTraversal_mismatch", "**/head/v[ou]l[", nil, []string{"value", "head", "vol["}, false, NoMatch},
{"glob_wrongPattern_onTraversal_mismatch", "/value/**/v[ou]l[", nil, []string{"value", "head", "vol["}, false, NoMatch},
{"glob_issue_923", "**/android/**/GeneratedPluginRegistrant.java", nil, []string{"packages", "flutter_tools", "lib", "src", "android", "gradle.dart"}, false, NoMatch},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
p := ParsePattern(tt.pattern, tt.domain)
if got := p.Match(tt.path, tt.isDir); got != tt.want {
t.Errorf("ParsePattern(%q, %v).Match(%v, %t) = %v, want %v",
tt.pattern, tt.domain, tt.path, tt.isDir, got, tt.want)
}
})
}
}

View File

@@ -117,7 +117,12 @@ func printTaskCommands(l *logger.Logger, t *ast.Task) {
isCommand := c.Cmd != "" isCommand := c.Cmd != ""
l.Outf(logger.Default, " - ") l.Outf(logger.Default, " - ")
if isCommand { if isCommand {
l.Outf(logger.Yellow, "%s\n", c.Cmd) // Use the masked command so secret values are not leaked in summaries
logCmd := c.LogCmd
if logCmd == "" {
logCmd = c.Cmd
}
l.Outf(logger.Yellow, "%s\n", logCmd)
} else { } else {
l.Outf(logger.Green, "Task: %s\n", c.Task) l.Outf(logger.Green, "Task: %s\n", c.Task)
} }
@@ -196,6 +201,11 @@ func printTaskEnv(l *logger.Logger, t *ast.Task) {
// formatVarValue formats a variable value based on its type. // formatVarValue formats a variable value based on its type.
// Handles static values, shell commands (sh:), references (ref:), and maps. // Handles static values, shell commands (sh:), references (ref:), and maps.
func formatVarValue(v ast.Var) string { func formatVarValue(v ast.Var) string {
// Never expose secret variables in the summary, whatever their type
if v.Secret {
return "*****"
}
// Shell command - check this first before Value // Shell command - check this first before Value
// because dynamic vars may have both Sh and an empty Value // because dynamic vars may have both Sh and an empty Value
if v.Sh != nil { if v.Sh != nil {

View File

@@ -0,0 +1,61 @@
package templater
import (
"github.com/go-task/task/v3/taskfile/ast"
)
// MaskSecrets replaces template placeholders with their values, masking secrets.
// This function uses the Go templater to resolve all variables ({{.VAR}}) while
// masking secret ones as "*****".
func MaskSecrets(cmdTemplate string, vars *ast.Vars) string {
return MaskSecretsWithExtra(cmdTemplate, vars, nil)
}
// MaskSecretsWithExtra is like MaskSecrets but also resolves extra variables (e.g., loop vars).
func MaskSecretsWithExtra(cmdTemplate string, vars *ast.Vars, extra map[string]any) string {
if vars == nil {
vars = ast.NewVars()
}
// Fast path: if there are no secrets to mask, resolve the template directly
// without the extra DeepCopy + masking pass.
if !hasSecrets(vars) {
cache := &Cache{Vars: vars}
result := ReplaceWithExtra(cmdTemplate, cache, extra)
if cache.Err() != nil {
return cmdTemplate
}
return result
}
// Create a copy with secret values masked, leaving the originals untouched.
maskedVars := vars.DeepCopy()
for name, v := range maskedVars.All() {
if v.Secret {
maskedVars.Set(name, ast.Var{
Value: "*****",
Secret: true,
})
}
}
cache := &Cache{Vars: maskedVars}
result := ReplaceWithExtra(cmdTemplate, cache, extra)
// If there was an error, return the original template
if cache.Err() != nil {
return cmdTemplate
}
return result
}
// hasSecrets reports whether any variable is marked as secret.
func hasSecrets(vars *ast.Vars) bool {
for _, v := range vars.All() {
if v.Secret {
return true
}
}
return false
}

View File

@@ -132,7 +132,7 @@ func ReplaceVar(v ast.Var, cache *Cache) ast.Var {
func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) ast.Var { func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) ast.Var {
if v.Ref != "" { if v.Ref != "" {
return ast.Var{Value: ResolveRef(v.Ref, cache)} return ast.Var{Value: ResolveRef(v.Ref, cache), Secret: v.Secret}
} }
return ast.Var{ return ast.Var{
Value: ReplaceWithExtra(v.Value, cache, extra), Value: ReplaceWithExtra(v.Value, cache, extra),
@@ -140,6 +140,7 @@ func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) ast.Var
Live: v.Live, Live: v.Live,
Ref: v.Ref, Ref: v.Ref,
Dir: v.Dir, Dir: v.Dir,
Secret: v.Secret,
} }
} }

12
mise.toml Normal file
View File

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

View File

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

View File

@@ -1,6 +1,7 @@
package task package task
import ( import (
"cmp"
"context" "context"
"fmt" "fmt"
"os" "os"
@@ -13,7 +14,6 @@ import (
"github.com/sajari/fuzzy" "github.com/sajari/fuzzy"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/logger"
@@ -133,13 +133,9 @@ func (e *Executor) setupTempDir() error {
return nil return nil
} }
tempDir := env.GetTaskEnv("TEMP_DIR") // e.TempDirPath carries the resolved CLI precedence (flag > TASK_TEMP_DIR > taskrc).
if tempDir == "" { tempDir := cmp.Or(e.TempDirPath, ".task")
e.TempDir = TempDir{ if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") {
Remote: filepathext.SmartJoin(e.Dir, ".task"),
Fingerprint: filepathext.SmartJoin(e.Dir, ".task"),
}
} else if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") {
tempDir, err := execext.ExpandLiteral(tempDir) tempDir, err := execext.ExpandLiteral(tempDir)
if err != nil { if err != nil {
return err return err

View File

@@ -349,6 +349,8 @@ func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, d
extra["EXIT_CODE"] = fmt.Sprintf("%d", *deferredExitCode) extra["EXIT_CODE"] = fmt.Sprintf("%d", *deferredExitCode)
} }
// Resolve template with secrets masked for logging
cmd.LogCmd = templater.MaskSecretsWithExtra(cmd.Cmd, vars, extra)
cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra) cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
cmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra) cmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
cmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra) cmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra)
@@ -388,12 +390,12 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
return err return err
case cmd.Cmd != "": case cmd.Cmd != "":
if !shouldRunOnCurrentPlatform(cmd.Platforms) { if !shouldRunOnCurrentPlatform(cmd.Platforms) {
e.Logger.VerboseOutf(logger.Yellow, "task: [%s] %s not for current platform - ignored\n", t.Name(), cmd.Cmd) e.Logger.VerboseOutf(logger.Yellow, "task: [%s] %s not for current platform - ignored\n", t.Name(), cmd.LogCmd)
return nil return nil
} }
if e.Verbose || (!call.Silent && !cmd.Silent && !t.IsSilent() && !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) e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmd.LogCmd)
} }
if e.Dry { if e.Dry {

View File

@@ -653,6 +653,164 @@ func TestStatusChecksumMissingGenerated(t *testing.T) { // nolint:paralleltest /
require.NoError(t, err, "generated.txt should be recreated after third run") require.NoError(t, err, "generated.txt should be recreated after third run")
} }
func writeFile(t *testing.T, dir, name, content string) {
t.Helper()
require.NoError(t, os.WriteFile(filepathext.SmartJoin(dir, name), []byte(content), 0o644))
}
// gitignoreStep writes a set of files then runs the task once, capturing its
// output as a golden fixture named run.
type gitignoreStep struct {
write map[string]string
run string
}
// gitignoreSeq drives a checksum task through a sequence of runs against a
// fixture dir. create seeds runtime files (removed on cleanup); restore resets
// tracked files to their committed content on cleanup; artifacts are
// task-produced files to delete on cleanup.
type gitignoreSeq struct {
dir string
task string
create map[string]string
restore map[string]string
artifacts []string
steps []gitignoreStep
}
func (s gitignoreSeq) run(t *testing.T) {
t.Helper()
cleanup := func() {
_ = os.RemoveAll(filepathext.SmartJoin(s.dir, ".task"))
for name := range s.create {
_ = os.Remove(filepathext.SmartJoin(s.dir, name))
}
for _, name := range s.artifacts {
_ = os.Remove(filepathext.SmartJoin(s.dir, name))
}
for name, content := range s.restore {
writeFile(t, s.dir, name, content)
}
}
cleanup()
t.Cleanup(cleanup)
for name, content := range s.create {
writeFile(t, s.dir, name, content)
}
for _, step := range s.steps {
for name, content := range step.write {
writeFile(t, s.dir, name, content)
}
NewExecutorTest(t,
WithName(step.run),
WithExecutorOptions(task.WithDir(s.dir)),
WithTask(s.task),
)
}
}
func TestGitignoreChecksum(t *testing.T) { //nolint:paralleltest // shares testdata/gitignore and mutates fixture files
gitignoreSeq{
dir: "testdata/gitignore",
task: "build",
create: map[string]string{"ignored.txt": "ignored\n"},
restore: map[string]string{"source.txt": "source content\n"},
artifacts: []string{"generated.txt"},
steps: []gitignoreStep{
{run: "first run"},
{run: "up to date"},
{run: "ignored file modified", write: map[string]string{"ignored.txt": "ignored modified\n"}},
{run: "source file modified", write: map[string]string{"source.txt": "source modified\n"}},
},
}.run(t)
}
// TestGitignoreNegation checks that a `!pattern` in a nested .gitignore
// re-includes a file excluded by a parent .gitignore.
func TestGitignoreNegation(t *testing.T) { //nolint:paralleltest // mutates fixture files
gitignoreSeq{
dir: "testdata/gitignore_negation",
task: "build",
create: map[string]string{"sub/debug.log": "debug\n", "sub/other.log": "other\n"},
steps: []gitignoreStep{
{run: "first run"},
{run: "up to date"},
{run: "ignored file modified", write: map[string]string{"sub/other.log": "other modified\n"}},
{run: "reincluded file modified", write: map[string]string{"sub/debug.log": "debug modified\n"}},
},
}.run(t)
}
// TestGitignoreNested checks that a .gitignore in a subdirectory below the task
// dir is honored when its files are reached by a deep glob.
func TestGitignoreNested(t *testing.T) { //nolint:paralleltest // mutates fixture files
gitignoreSeq{
dir: "testdata/gitignore_nested",
task: "build",
create: map[string]string{"sub/secret.dat": "secret\n"},
restore: map[string]string{"sub/keep.txt": "keep\n"},
steps: []gitignoreStep{
{run: "first run"},
{run: "up to date"},
{run: "ignored file modified", write: map[string]string{"sub/secret.dat": "secret modified\n"}},
{run: "source file modified", write: map[string]string{"sub/keep.txt": "keep modified\n"}},
},
}.run(t)
}
// TestGitignoreIncluded checks that a top-level use_gitignore in an included
// Taskfile is propagated onto its tasks during merge.
func TestGitignoreIncluded(t *testing.T) { //nolint:paralleltest // mutates fixture files
gitignoreSeq{
dir: "testdata/gitignore_included",
task: "included:build",
create: map[string]string{"ignored.txt": "ignored\n"},
steps: []gitignoreStep{
{run: "first run"},
{run: "up to date"},
{run: "ignored file modified", write: map[string]string{"ignored.txt": "ignored modified\n"}},
},
}.run(t)
}
// TestGitignoreIncludedOverride checks that an explicit use_gitignore: false in
// an included Taskfile is preserved even when the root Taskfile sets it to true.
func TestGitignoreIncludedOverride(t *testing.T) { //nolint:paralleltest // mutates fixture files
gitignoreSeq{
dir: "testdata/gitignore_included_override",
task: "included:build",
create: map[string]string{"ignored.txt": "ignored\n"},
steps: []gitignoreStep{
{run: "first run"},
{run: "up to date"},
{run: "ignored file modified", write: map[string]string{"ignored.txt": "ignored modified\n"}},
},
}.run(t)
}
func TestGitignoreTaskListFallback(t *testing.T) { //nolint:paralleltest // shares testdata/gitignore with TestGitignoreChecksum
const dir = "testdata/gitignore"
var buff bytes.Buffer
e := task.NewExecutor(
task.WithDir(dir),
task.WithStdout(&buff),
task.WithStderr(&buff),
)
require.NoError(t, e.Setup())
listed, err := e.CompiledTaskForTaskList(&task.Call{Task: "build"})
require.NoError(t, err)
assert.True(t, listed.ShouldUseGitignore(),
"task list should reflect the global use_gitignore fallback")
// "build-no-use_gitignore" explicitly disables it.
listedOff, err := e.CompiledTaskForTaskList(&task.Call{Task: "build-no-use_gitignore"})
require.NoError(t, err)
assert.False(t, listedOff.ShouldUseGitignore(),
"explicit use_gitignore: false must be preserved in the list path")
}
func TestStatusVariables(t *testing.T) { func TestStatusVariables(t *testing.T) {
t.Parallel() t.Parallel()
@@ -1082,6 +1240,25 @@ func TestIncludesIncorrect(t *testing.T) {
assert.Contains(t, err.Error(), "Failed to parse testdata/includes_incorrect/incomplete.yml:", err.Error()) assert.Contains(t, err.Error(), "Failed to parse testdata/includes_incorrect/incomplete.yml:", err.Error())
} }
func TestIncludesMissingTaskfile(t *testing.T) {
t.Parallel()
const dir = "testdata/includes_missing_taskfile"
var buff bytes.Buffer
e := task.NewExecutor(
task.WithDir(dir),
task.WithStdout(&buff),
task.WithStderr(&buff),
task.WithSilent(true),
)
err := e.Setup()
require.Error(t, err)
assert.Contains(t, err.Error(), "include must specify taskfile or dir")
assert.NotContains(t, err.Error(), "include cycle detected")
}
func TestIncludesEmptyMain(t *testing.T) { func TestIncludesEmptyMain(t *testing.T) {
t.Parallel() t.Parallel()

View File

@@ -9,7 +9,8 @@ import (
// Cmd is a task command // Cmd is a task command
type Cmd struct { type Cmd struct {
Cmd string Cmd string // Resolved command (used for execution and fingerprinting)
LogCmd string // Command with secrets masked (used for logging)
Task string Task string
For *For For *For
If string If string
@@ -28,6 +29,7 @@ func (c *Cmd) DeepCopy() *Cmd {
} }
return &Cmd{ return &Cmd{
Cmd: c.Cmd, Cmd: c.Cmd,
LogCmd: c.LogCmd,
Task: c.Task, Task: c.Task,
For: c.For.DeepCopy(), For: c.For.DeepCopy(),
If: c.If, If: c.If,

View File

@@ -2,6 +2,7 @@ package ast
import ( import (
"iter" "iter"
"strings"
"sync" "sync"
"github.com/elliotchance/orderedmap/v3" "github.com/elliotchance/orderedmap/v3"
@@ -171,6 +172,9 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
if err := node.Decode(&includedTaskfile); err != nil { if err := node.Decode(&includedTaskfile); err != nil {
return errors.NewTaskfileDecodeError(err, node) return errors.NewTaskfileDecodeError(err, node)
} }
if strings.TrimSpace(includedTaskfile.Taskfile) == "" && strings.TrimSpace(includedTaskfile.Dir) == "" {
return errors.NewTaskfileDecodeError(nil, node).WithMessage("include must specify taskfile or dir")
}
include.Taskfile = includedTaskfile.Taskfile include.Taskfile = includedTaskfile.Taskfile
include.Dir = includedTaskfile.Dir include.Dir = includedTaskfile.Dir
include.Optional = includedTaskfile.Optional include.Optional = includedTaskfile.Optional

View File

@@ -93,6 +93,21 @@ func (matrix *Matrix) DeepCopy() *Matrix {
} }
} }
// DeepCopy returns a copy of the MatrixRow. Without this, deepcopy.OrderedMap
// falls back to copying the *MatrixRow pointer as-is, so every "copy" of a
// Matrix would still share the same underlying rows - see #2890, where
// concurrent invocations of a task with a `ref:` matrix row raced on
// resolveMatrixRefs mutating that shared row.
func (row *MatrixRow) DeepCopy() *MatrixRow {
if row == nil {
return nil
}
return &MatrixRow{
Ref: row.Ref,
Value: deepcopy.Slice(row.Value),
}
}
func (matrix *Matrix) UnmarshalYAML(node *yaml.Node) error { func (matrix *Matrix) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind { switch node.Kind {
case yaml.MappingNode: case yaml.MappingNode:

View File

@@ -38,6 +38,7 @@ type Task struct {
Method string Method string
Prefix string `hash:"ignore"` Prefix string `hash:"ignore"`
IgnoreError bool IgnoreError bool
UseGitignore *bool
Run string Run string
Platforms []*Platform Platforms []*Platform
If string If string
@@ -75,6 +76,12 @@ func (t *Task) IsSilent() bool {
return t.Silent != nil && *t.Silent return t.Silent != nil && *t.Silent
} }
// ShouldUseGitignore returns true if gitignore filtering is enabled for the task.
// Returns false if UseGitignore is nil or set to false.
func (t *Task) ShouldUseGitignore() bool {
return t.UseGitignore != nil && *t.UseGitignore
}
// WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values. // 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) { func (t *Task) WildcardMatch(name string) (bool, []string) {
names := append([]string{t.Task}, t.Aliases...) names := append([]string{t.Task}, t.Aliases...)
@@ -150,6 +157,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
Method string Method string
Prefix string Prefix string
IgnoreError bool `yaml:"ignore_error"` IgnoreError bool `yaml:"ignore_error"`
UseGitignore *bool `yaml:"use_gitignore,omitempty"`
Run string Run string
Platforms []*Platform Platforms []*Platform
If string If string
@@ -190,6 +198,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
t.Method = task.Method t.Method = task.Method
t.Prefix = task.Prefix t.Prefix = task.Prefix
t.IgnoreError = task.IgnoreError t.IgnoreError = task.IgnoreError
t.UseGitignore = deepcopy.Scalar(task.UseGitignore)
t.Run = task.Run t.Run = task.Run
t.Platforms = task.Platforms t.Platforms = task.Platforms
t.If = task.If t.If = task.If
@@ -233,6 +242,7 @@ func (t *Task) DeepCopy() *Task {
Method: t.Method, Method: t.Method,
Prefix: t.Prefix, Prefix: t.Prefix,
IgnoreError: t.IgnoreError, IgnoreError: t.IgnoreError,
UseGitignore: deepcopy.Scalar(t.UseGitignore),
Run: t.Run, Run: t.Run,
IncludeVars: t.IncludeVars.DeepCopy(), IncludeVars: t.IncludeVars.DeepCopy(),
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(), IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),

View File

@@ -34,6 +34,7 @@ type Taskfile struct {
Dotenv []string Dotenv []string
Run string Run string
Interval time.Duration Interval time.Duration
UseGitignore *bool
} }
// Merge merges the second Taskfile into the first // Merge merges the second Taskfile into the first
@@ -67,6 +68,14 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
} }
} }
} }
if t2.UseGitignore != nil {
for _, t := range t2.Tasks.All(nil) {
if t.UseGitignore == nil {
v := *t2.UseGitignore
t.UseGitignore = &v
}
}
}
t1.Vars.Merge(t2.Vars, include) t1.Vars.Merge(t2.Vars, include)
t1.Env.Merge(t2.Env, include) t1.Env.Merge(t2.Env, include)
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars) return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
@@ -89,6 +98,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
Dotenv []string Dotenv []string
Run string Run string
Interval time.Duration Interval time.Duration
UseGitignore *bool `yaml:"use_gitignore"`
} }
if err := node.Decode(&taskfile); err != nil { if err := node.Decode(&taskfile); err != nil {
return errors.NewTaskfileDecodeError(err, node) return errors.NewTaskfileDecodeError(err, node)
@@ -106,6 +116,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
tf.Dotenv = taskfile.Dotenv tf.Dotenv = taskfile.Dotenv
tf.Run = taskfile.Run tf.Run = taskfile.Run
tf.Interval = taskfile.Interval tf.Interval = taskfile.Interval
tf.UseGitignore = taskfile.UseGitignore
if tf.Includes == nil { if tf.Includes == nil {
tf.Includes = NewIncludes() tf.Includes = NewIncludes()
} }

View File

@@ -13,32 +13,49 @@ type Var struct {
Sh *string Sh *string
Ref string Ref string
Dir string Dir string
Secret bool
} }
func (v *Var) UnmarshalYAML(node *yaml.Node) error { func (v *Var) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind { switch node.Kind {
case yaml.MappingNode: case yaml.MappingNode:
key := "<none>"
if len(node.Content) > 0 {
key = node.Content[0].Value
}
switch key {
case "sh", "ref", "map":
var m struct { var m struct {
Sh *string Sh *string
Ref string Ref string
Map any Map any
Value any
Secret bool
} }
if err := node.Decode(&m); err != nil { if err := node.Decode(&m); err != nil {
return errors.NewTaskfileDecodeError(err, node) return errors.NewTaskfileDecodeError(err, node)
} }
// Validate the keys regardless of their order: every key must be known
// and at least one type-defining key must be present. "secret" is a
// modifier, not a type, so it can appear in any position.
hasType := false
for i := 0; i+1 < len(node.Content); i += 2 {
switch node.Content[i].Value {
case "sh", "ref", "map", "value":
hasType = true
case "secret":
// modifier, not a type
default:
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map", "value" or using a scalar value`, node.Content[i].Value)
}
}
if !hasType {
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`a variable must define one of "sh", "ref", "map", "value" or be a scalar value`)
}
v.Sh = m.Sh v.Sh = m.Sh
v.Ref = m.Ref v.Ref = m.Ref
v.Secret = m.Secret
// Handle both "map" and "value" keys
if m.Map != nil {
v.Value = m.Map v.Value = m.Map
return nil } else if m.Value != nil {
default: v.Value = m.Value
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map" or using a scalar value`, key)
} }
return nil
default: default:
var value any var value any
if err := node.Decode(&value); err != nil { if err := node.Decode(&value); err != nil {

View File

@@ -98,6 +98,9 @@ func (vars *Vars) Values() iter.Seq[Var] {
// ToCacheMap converts Vars to an unordered map containing only the static // ToCacheMap converts Vars to an unordered map containing only the static
// variables // variables
func (vars *Vars) ToCacheMap() (m map[string]any) { func (vars *Vars) ToCacheMap() (m map[string]any) {
if vars == nil || vars.om == nil {
return nil
}
defer vars.mutex.RUnlock() defer vars.mutex.RUnlock()
vars.mutex.RLock() vars.mutex.RLock()
m = make(map[string]any, vars.Len()) m = make(map[string]any, vars.Len())

55
taskfile/ast/vars_test.go Normal file
View File

@@ -0,0 +1,55 @@
package ast
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestVars_ToCacheMap(t *testing.T) {
t.Parallel()
t.Run("nil receiver returns nil", func(t *testing.T) {
t.Parallel()
var vars *Vars
assert.Nil(t, vars.ToCacheMap())
})
t.Run("empty vars returns empty map", func(t *testing.T) {
t.Parallel()
vars := NewVars()
m := vars.ToCacheMap()
assert.NotNil(t, m)
assert.Empty(t, m)
})
t.Run("static values are included", func(t *testing.T) {
t.Parallel()
vars := NewVars(
&VarElement{Key: "FOO", Value: Var{Value: "bar"}},
&VarElement{Key: "NUM", Value: Var{Value: 42}},
)
m := vars.ToCacheMap()
assert.Equal(t, map[string]any{"FOO": "bar", "NUM": 42}, m)
})
t.Run("live values take precedence over static values", func(t *testing.T) {
t.Parallel()
vars := NewVars(
&VarElement{Key: "FOO", Value: Var{Value: "bar", Live: "live-bar"}},
)
m := vars.ToCacheMap()
assert.Equal(t, map[string]any{"FOO": "live-bar"}, m)
})
t.Run("dynamic variables are excluded", func(t *testing.T) {
t.Parallel()
sh := "echo hello"
vars := NewVars(
&VarElement{Key: "STATIC", Value: Var{Value: "ok"}},
&VarElement{Key: "DYNAMIC", Value: Var{Sh: &sh}},
)
m := vars.ToCacheMap()
assert.Equal(t, map[string]any{"STATIC": "ok"}, m)
})
}

View File

@@ -2,6 +2,7 @@ package taskfile
import ( import (
"context" "context"
"slices"
"strings" "strings"
"time" "time"
@@ -73,7 +74,7 @@ func NewNode(
return node, err return node, err
} }
func isRemoteEntrypoint(entrypoint string) bool { func IsRemoteEntrypoint(entrypoint string) bool {
scheme, _ := getScheme(entrypoint) scheme, _ := getScheme(entrypoint)
switch scheme { switch scheme {
case "git", "http", "https": case "git", "http", "https":
@@ -89,7 +90,10 @@ func getScheme(uri string) (string, error) {
return "", err return "", err
} }
if strings.HasSuffix(strings.Split(u.Path, "//")[0], ".git") && (u.Scheme == "git" || u.Scheme == "ssh" || u.Scheme == "https" || u.Scheme == "http") { isDotGit := strings.HasSuffix(strings.Split(u.Path, "//")[0], ".git")
isUnderscoreGit := strings.Contains(strings.Split(u.Path, "//")[0], "/_git/")
schemeIsGitCompatible := slices.Contains([]string{"git", "ssh", "https", "http"}, u.Scheme)
if (isDotGit || isUnderscoreGit) && schemeIsGitCompatible {
return "git", nil return "git", nil
} }

View File

@@ -60,7 +60,7 @@ func (node *FileNode) Read() ([]byte, error) {
func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) { func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) {
// If the file is remote, we don't need to resolve the path // If the file is remote, we don't need to resolve the path
if isRemoteEntrypoint(entrypoint) { if IsRemoteEntrypoint(entrypoint) {
return entrypoint, nil return entrypoint, nil
} }

View File

@@ -188,7 +188,7 @@ func (node *GitNode) ReadContext(ctx context.Context) ([]byte, error) {
func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) { func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) {
// If the file is remote, we don't need to resolve the path // If the file is remote, we don't need to resolve the path
if isRemoteEntrypoint(entrypoint) { if IsRemoteEntrypoint(entrypoint) {
return entrypoint, nil return entrypoint, nil
} }

View File

@@ -42,7 +42,7 @@ func (node *StdinNode) Read() ([]byte, error) {
func (node *StdinNode) ResolveEntrypoint(entrypoint string) (string, error) { func (node *StdinNode) ResolveEntrypoint(entrypoint string) (string, error) {
// If the file is remote, we don't need to resolve the path // If the file is remote, we don't need to resolve the path
if isRemoteEntrypoint(entrypoint) { if IsRemoteEntrypoint(entrypoint) {
return entrypoint, nil return entrypoint, nil
} }

View File

@@ -21,4 +21,7 @@ func TestScheme(t *testing.T) {
scheme, err = getScheme("https://github.com/foo/common.yml") scheme, err = getScheme("https://github.com/foo/common.yml")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "https", scheme) assert.Equal(t, "https", scheme)
scheme, err = getScheme("https://some-azure-host.com/org/project/_git/repo")
assert.NoError(t, err)
assert.Equal(t, "git", scheme)
} }

View File

@@ -19,6 +19,7 @@ type TaskRC struct {
Interactive *bool `yaml:"interactive"` Interactive *bool `yaml:"interactive"`
Remote Remote `yaml:"remote"` Remote Remote `yaml:"remote"`
Failfast bool `yaml:"failfast"` Failfast bool `yaml:"failfast"`
TempDir *string `yaml:"temp-dir"`
Experiments map[string]int `yaml:"experiments"` Experiments map[string]int `yaml:"experiments"`
} }
@@ -70,4 +71,5 @@ func (t *TaskRC) Merge(other *TaskRC) {
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency) t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
t.Interactive = cmp.Or(other.Interactive, t.Interactive) t.Interactive = cmp.Or(other.Interactive, t.Interactive)
t.Failfast = cmp.Or(other.Failfast, t.Failfast) t.Failfast = cmp.Or(other.Failfast, t.Failfast)
t.TempDir = cmp.Or(other.TempDir, t.TempDir)
} }

View File

@@ -112,6 +112,40 @@ func TestGetConfig_OnlyLocal(t *testing.T) { //nolint:paralleltest // cannot run
}, cfg) }, cfg)
} }
func TestGetConfig_TempDir(t *testing.T) { //nolint:paralleltest // cannot run in parallel
_, _, localDir := setupDirs(t)
writeFile(t, localDir, ".taskrc.yml", `
temp-dir: .task-cache
`)
cfg, err := GetConfig(localDir)
require.NoError(t, err)
require.NotNil(t, cfg)
require.NotNil(t, cfg.TempDir)
assert.Equal(t, ".task-cache", *cfg.TempDir)
}
func TestGetConfig_TempDirMergePrecedence(t *testing.T) { //nolint:paralleltest // cannot run in parallel
xdgConfigDir, homeDir, localDir := setupDirs(t)
writeFile(t, xdgConfigDir, "taskrc.yml", `
temp-dir: xdg-cache
`)
writeFile(t, homeDir, ".taskrc.yml", `
temp-dir: home-cache
`)
writeFile(t, localDir, ".taskrc.yml", `
temp-dir: local-cache
`)
cfg, err := GetConfig(localDir)
require.NoError(t, err)
require.NotNil(t, cfg)
require.NotNil(t, cfg.TempDir)
assert.Equal(t, "local-cache", *cfg.TempDir)
}
func TestGetConfig_All(t *testing.T) { //nolint:paralleltest // cannot run in parallel func TestGetConfig_All(t *testing.T) { //nolint:paralleltest // cannot run in parallel
xdgConfigDir, homeDir, localDir := setupDirs(t) xdgConfigDir, homeDir, localDir := setupDirs(t)

1
testdata/gitignore/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
ignored.txt

25
testdata/gitignore/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
version: '3'
use_gitignore: true
tasks:
build:
cmds:
- cp ./source.txt ./generated.txt
sources:
- ./*.txt
- exclude: ./generated.txt
generates:
- ./generated.txt
method: checksum
build-no-use_gitignore:
use_gitignore: false
cmds:
- cp ./source.txt ./generated.txt
sources:
- ./*.txt
- exclude: ./generated.txt
generates:
- ./generated.txt
method: checksum

1
testdata/gitignore/source.txt vendored Normal file
View File

@@ -0,0 +1 @@
source content

View File

@@ -0,0 +1 @@
task: [build] cp ./source.txt ./generated.txt

View File

@@ -0,0 +1 @@
task: Task "build" is up to date

View File

@@ -0,0 +1 @@
task: [build] cp ./source.txt ./generated.txt

View File

@@ -0,0 +1 @@
task: Task "build" is up to date

View File

@@ -0,0 +1 @@
ignored.txt

View File

@@ -0,0 +1,9 @@
version: '3'
includes:
included: ./included/Taskfile.yml
tasks:
default:
cmds:
- echo "root"

View File

@@ -0,0 +1,11 @@
version: '3'
use_gitignore: true
tasks:
build:
cmds:
- echo "build executed"
sources:
- ./*.txt
method: checksum

View File

@@ -0,0 +1 @@
source

View File

@@ -0,0 +1,2 @@
task: [included:build] echo "build executed"
build executed

View File

@@ -0,0 +1 @@
task: Task "included:build" is up to date

View File

@@ -0,0 +1 @@
task: Task "included:build" is up to date

View File

@@ -0,0 +1 @@
ignored.txt

View File

@@ -0,0 +1,11 @@
version: '3'
use_gitignore: true
includes:
included: ./included/Taskfile.yml
tasks:
default:
cmds:
- echo "root"

View File

@@ -0,0 +1,11 @@
version: '3'
use_gitignore: false
tasks:
build:
cmds:
- echo "build executed"
sources:
- ./*.txt
method: checksum

View File

@@ -0,0 +1 @@
source

View File

@@ -0,0 +1,2 @@
task: [included:build] echo "build executed"
build executed

View File

@@ -0,0 +1,2 @@
task: [included:build] echo "build executed"
build executed

View File

@@ -0,0 +1 @@
task: Task "included:build" is up to date

View File

@@ -0,0 +1 @@
*.log

View File

@@ -0,0 +1,11 @@
version: '3'
use_gitignore: true
tasks:
build:
cmds:
- echo "build executed"
sources:
- ./sub/*.log
method: checksum

View File

@@ -0,0 +1 @@
!debug.log

View File

@@ -0,0 +1,2 @@
task: [build] echo "build executed"
build executed

View File

@@ -0,0 +1 @@
task: Task "build" is up to date

View File

@@ -0,0 +1,2 @@
task: [build] echo "build executed"
build executed

View File

@@ -0,0 +1 @@
task: Task "build" is up to date

11
testdata/gitignore_nested/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
version: '3'
use_gitignore: true
tasks:
build:
cmds:
- echo "build executed"
sources:
- ./sub/*
method: checksum

View File

@@ -0,0 +1 @@
secret.dat

View File

@@ -0,0 +1 @@
keep

View File

@@ -0,0 +1,2 @@
task: [build] echo "build executed"
build executed

View File

@@ -0,0 +1 @@
task: Task "build" is up to date

View File

@@ -0,0 +1,2 @@
task: [build] echo "build executed"
build executed

View File

@@ -0,0 +1 @@
task: Task "build" is up to date

View File

@@ -0,0 +1,5 @@
version: '3'
includes:
GOBIN:
sh: echo $(go env GOPATH)/bin

83
testdata/secrets/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,83 @@
version: '3'
vars:
# Public variable
APP_NAME: myapp
# Secret variable with value
API_KEY:
value: "secret-api-key-123"
secret: true
# Secret variable from shell command
PASSWORD:
sh: "echo 'my-super-secret-password'"
secret: true
# Non-secret variable
PUBLIC_URL: https://example.com
tasks:
test-secret-masking:
desc: Test that secret variables are masked in logs
cmds:
- echo "Deploying {{.APP_NAME}} to {{.PUBLIC_URL}}"
- echo "Using API key {{.API_KEY}}"
- echo "Password is {{.PASSWORD}}"
- echo "Public app name is {{.APP_NAME}}"
test-multiple-secrets:
desc: Test multiple secrets in one command
cmds:
- echo "API={{.API_KEY}} PWD={{.PASSWORD}}"
test-mixed:
desc: Test mix of secret and public vars
vars:
LOCAL_SECRET:
value: "task-level-secret"
secret: true
cmds:
- echo "App={{.APP_NAME}} Secret={{.LOCAL_SECRET}} URL={{.PUBLIC_URL}}"
test-deferred-secret:
desc: Test that deferred commands mask secrets
vars:
DEFERRED_SECRET:
value: "deferred-secret-value"
secret: true
cmds:
- echo "Starting task"
- defer: echo "Cleanup with secret={{.DEFERRED_SECRET}} and app={{.APP_NAME}}"
- echo "Main command executed"
test-dynamic-secret-verbose:
desc: Test that dynamic (sh) secrets are masked even in verbose logs
cmds:
- echo "Password is {{.PASSWORD}}"
test-secret-key-order:
desc: Test that "secret" may be declared before the value/sh key
vars:
SECRET_FIRST:
secret: true
value: "order-independent-secret"
SH_SECRET_FIRST:
secret: true
sh: "echo 'sh-order-independent-secret'"
cmds:
- echo "Value={{.SECRET_FIRST}} Sh={{.SH_SECRET_FIRST}}"
test-env-secret-limitation:
desc: Test showing that env vars with secret flag are NOT masked (limitation)
env:
SECRET_TOKEN:
value: "env-secret-token-123"
secret: true
PUBLIC_ENV: "public-value"
cmds:
# Templates {{.VAR}} don't work with env - they're empty
- echo "Token via template is {{.SECRET_TOKEN}}"
# Shell $VAR works but is NOT masked (env vars not in template system)
- echo "Token via shell is $SECRET_TOKEN"
- echo "Public env is {{.PUBLIC_ENV}}"

View File

@@ -0,0 +1,6 @@
task: [test-deferred-secret] echo "Starting task"
Starting task
task: [test-deferred-secret] echo "Main command executed"
Main command executed
task: [test-deferred-secret] echo "Cleanup with secret=***** and app=myapp"
Cleanup with secret=deferred-secret-value and app=myapp

View File

@@ -0,0 +1,5 @@
task: dynamic variable: "echo 'my-super-secret-password'" result: "*****"
task: "test-dynamic-secret-verbose" started
task: [test-dynamic-secret-verbose] echo "Password is *****"
Password is my-super-secret-password
task: "test-dynamic-secret-verbose" finished

View File

@@ -0,0 +1,6 @@
task: [test-env-secret-limitation] echo "Token via template is "
Token via template is
task: [test-env-secret-limitation] echo "Token via shell is $SECRET_TOKEN"
Token via shell is env-secret-token-123
task: [test-env-secret-limitation] echo "Public env is "
Public env is

View File

@@ -0,0 +1,2 @@
task: [test-mixed] echo "App=myapp Secret=***** URL=https://example.com"
App=myapp Secret=task-level-secret URL=https://example.com

View File

@@ -0,0 +1,2 @@
task: [test-multiple-secrets] echo "API=***** PWD=*****"
API=secret-api-key-123 PWD=my-super-secret-password

View File

@@ -0,0 +1,2 @@
task: [test-secret-key-order] echo "Value=***** Sh=*****"
Value=order-independent-secret Sh=sh-order-independent-secret

View File

@@ -0,0 +1,8 @@
task: [test-secret-masking] echo "Deploying myapp to https://example.com"
Deploying myapp to https://example.com
task: [test-secret-masking] echo "Using API key *****"
Using API key secret-api-key-123
task: [test-secret-masking] echo "Password is *****"
Password is my-super-secret-password
task: [test-secret-masking] echo "Public app name is myapp"
Public app name is myapp

View File

@@ -0,0 +1,15 @@
task: test-secret-masking
Test that secret variables are masked in logs
vars:
APP_NAME: "myapp"
API_KEY: *****
PASSWORD: *****
PUBLIC_URL: "https://example.com"
commands:
- echo "Deploying myapp to https://example.com"
- echo "Using API key *****"
- echo "Password is *****"
- echo "Public app name is myapp"

View File

@@ -19,6 +19,16 @@ import (
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
// shouldTaskUseGitignore resolves whether gitignore filtering applies to a
// task: the task-level value takes precedence, falling back to the Taskfile's
// global use_gitignore when the task does not set it.
func (e *Executor) shouldTaskUseGitignore(t *ast.Task) bool {
if t.UseGitignore != nil {
return *t.UseGitignore
}
return e.Taskfile.UseGitignore != nil && *e.Taskfile.UseGitignore
}
// CompiledTask returns a copy of a task, but replacing variables in almost all // CompiledTask returns a copy of a task, but replacing variables in almost all
// properties using the Go template package. // properties using the Go template package.
func (e *Executor) CompiledTask(call *Call) (*ast.Task, error) { func (e *Executor) CompiledTask(call *Call) (*ast.Task, error) {
@@ -43,6 +53,8 @@ func (e *Executor) CompiledTaskForTaskList(call *Call) (*ast.Task, error) {
cache := &templater.Cache{Vars: vars} cache := &templater.Cache{Vars: vars}
gitignore := e.shouldTaskUseGitignore(origTask)
return &ast.Task{ return &ast.Task{
Task: origTask.Task, Task: origTask.Task,
Label: templater.Replace(origTask.Label, cache), Label: templater.Replace(origTask.Label, cache),
@@ -59,6 +71,7 @@ func (e *Executor) CompiledTaskForTaskList(call *Call) (*ast.Task, error) {
Env: nil, Env: nil,
Dotenv: origTask.Dotenv, Dotenv: origTask.Dotenv,
Silent: deepcopy.Scalar(origTask.Silent), Silent: deepcopy.Scalar(origTask.Silent),
UseGitignore: &gitignore,
Interactive: origTask.Interactive, Interactive: origTask.Interactive,
Internal: origTask.Internal, Internal: origTask.Internal,
Method: origTask.Method, Method: origTask.Method,
@@ -110,6 +123,8 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
} }
} }
gitignore := e.shouldTaskUseGitignore(origTask)
new := ast.Task{ new := ast.Task{
Task: origTask.Task, Task: origTask.Task,
Label: templater.Replace(origTask.Label, cache), Label: templater.Replace(origTask.Label, cache),
@@ -126,6 +141,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
Env: nil, Env: nil,
Dotenv: templater.Replace(origTask.Dotenv, cache), Dotenv: templater.Replace(origTask.Dotenv, cache),
Silent: deepcopy.Scalar(origTask.Silent), Silent: deepcopy.Scalar(origTask.Silent),
UseGitignore: &gitignore,
Interactive: origTask.Interactive, Interactive: origTask.Interactive,
Internal: origTask.Internal, Internal: origTask.Internal,
Method: templater.Replace(origTask.Method, cache), Method: templater.Replace(origTask.Method, cache),
@@ -219,7 +235,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
continue continue
} }
if cmd.For != nil { if cmd.For != nil {
list, keys, err := itemsFromFor(cmd.For, new.Dir, new.Sources, new.Generates, vars, origTask.Location, cache) list, keys, err := itemsFromFor(cmd.For, new.Dir, new.Sources, new.Generates, gitignore, vars, origTask.Location, cache)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -239,6 +255,8 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
extra["KEY"] = keys[i] extra["KEY"] = keys[i]
} }
newCmd := cmd.DeepCopy() newCmd := cmd.DeepCopy()
// Resolve template with secrets masked + loop vars for logging
newCmd.LogCmd = templater.MaskSecretsWithExtra(cmd.Cmd, cache.Vars, extra)
newCmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra) newCmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
newCmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra) newCmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
newCmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra) newCmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra)
@@ -254,6 +272,8 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
continue continue
} }
newCmd := cmd.DeepCopy() newCmd := cmd.DeepCopy()
// Resolve template with secrets masked for logging
newCmd.LogCmd = templater.MaskSecrets(cmd.Cmd, cache.Vars)
newCmd.Cmd = templater.Replace(cmd.Cmd, cache) newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
newCmd.Task = templater.Replace(cmd.Task, cache) newCmd.Task = templater.Replace(cmd.Task, cache)
newCmd.If = templater.Replace(cmd.If, cache) newCmd.If = templater.Replace(cmd.If, cache)
@@ -268,7 +288,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
continue continue
} }
if dep.For != nil { if dep.For != nil {
list, keys, err := itemsFromFor(dep.For, new.Dir, new.Sources, new.Generates, vars, origTask.Location, cache) list, keys, err := itemsFromFor(dep.For, new.Dir, new.Sources, new.Generates, gitignore, vars, origTask.Location, cache)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -339,6 +359,7 @@ func itemsFromFor(
dir string, dir string,
sources []*ast.Glob, sources []*ast.Glob,
generates []*ast.Glob, generates []*ast.Glob,
gitignore bool,
vars *ast.Vars, vars *ast.Vars,
location *ast.Location, location *ast.Location,
cache *templater.Cache, cache *templater.Cache,
@@ -347,13 +368,14 @@ func itemsFromFor(
var values []any // The list of values to loop over var values []any // The list of values to loop over
// Get the list from a matrix // Get the list from a matrix
if f.Matrix.Len() != 0 { if f.Matrix.Len() != 0 {
if err := resolveMatrixRefs(f.Matrix, cache); err != nil { resolvedMatrix, err := resolveMatrixRefs(f.Matrix, cache)
if err != nil {
return nil, nil, errors.TaskfileInvalidError{ return nil, nil, errors.TaskfileInvalidError{
URI: location.Taskfile, URI: location.Taskfile,
Err: err, Err: err,
} }
} }
return asAnySlice(product(f.Matrix)), nil, nil return asAnySlice(product(resolvedMatrix)), nil, nil
} }
// Get the list from the explicit for list // Get the list from the explicit for list
if len(f.List) > 0 { if len(f.List) > 0 {
@@ -361,7 +383,7 @@ func itemsFromFor(
} }
// Get the list from the task sources // Get the list from the task sources
if f.From == "sources" { if f.From == "sources" {
glist, err := fingerprint.Globs(dir, sources) glist, err := fingerprint.Globs(dir, sources, gitignore)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -375,7 +397,7 @@ func itemsFromFor(
} }
// Get the list from the task generates // Get the list from the task generates
if f.From == "generates" { if f.From == "generates" {
glist, err := fingerprint.Globs(dir, generates) glist, err := fingerprint.Globs(dir, generates, gitignore)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -425,22 +447,43 @@ func itemsFromFor(
return values, keys, nil return values, keys, nil
} }
func resolveMatrixRefs(matrix *ast.Matrix, cache *templater.Cache) error { // resolveMatrixRefs resolves any `ref:` rows in matrix and returns a new
// Matrix with those rows populated. It must not mutate the matrix passed in:
// that matrix is part of the shared, cached Task AST, and concurrent
// invocations of the same task (e.g. via parallel deps) call this with the
// same *ast.Matrix and would otherwise race on the row.Value assignment
// below, intermittently leaking a value resolved for one caller into another
// caller's expansion. See #2890.
func resolveMatrixRefs(matrix *ast.Matrix, cache *templater.Cache) (*ast.Matrix, error) {
if matrix.Len() == 0 { if matrix.Len() == 0 {
return nil return matrix, nil
} }
hasRef := false
for _, row := range matrix.All() { for _, row := range matrix.All() {
if row.Ref != "" {
hasRef = true
break
}
}
if !hasRef {
return matrix, nil
}
resolved := matrix.DeepCopy()
for _, row := range resolved.All() {
if row.Ref != "" { if row.Ref != "" {
v := templater.ResolveRef(row.Ref, cache) v := templater.ResolveRef(row.Ref, cache)
if cache.Err() != nil {
return nil, cache.Err()
}
switch value := v.(type) { switch value := v.(type) {
case []any: case []any:
row.Value = value row.Value = value
default: default:
return fmt.Errorf("matrix reference %q must resolve to a list", row.Ref) return nil, fmt.Errorf("matrix reference %q must resolve to a list", row.Ref)
} }
} }
} }
return nil return resolved, nil
} }
func resolveEnumRefs(requires *ast.Requires, cache *templater.Cache) error { func resolveEnumRefs(requires *ast.Requires, cache *templater.Cache) error {

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