mirror of
https://github.com/go-task/task.git
synced 2026-06-11 18:01:40 +00:00
Compare commits
142 Commits
feat/comma
...
git-ignore
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d92de8e44 | ||
|
|
70fe29314f | ||
|
|
de99487b65 | ||
|
|
7705f922c1 | ||
|
|
7cea8e3364 | ||
|
|
13ef1b2dda | ||
|
|
ae3627c596 | ||
|
|
fe542d5418 | ||
|
|
81d621d8aa | ||
|
|
f50327f2a2 | ||
|
|
dd810d42e1 | ||
|
|
de1717b05f | ||
|
|
7766e8add5 | ||
|
|
e61d0f0f7c | ||
|
|
9c8799480d | ||
|
|
7f04f7057c | ||
|
|
48dd62cdc1 | ||
|
|
b27a6653f6 | ||
|
|
76269b03c4 | ||
|
|
ed492ac862 | ||
|
|
1d385ad5b4 | ||
|
|
2eff3545d9 | ||
|
|
bd79c29a39 | ||
|
|
136e0dae89 | ||
|
|
41a2137044 | ||
|
|
aea7f7a713 | ||
|
|
c3e16b0a68 | ||
|
|
19699a1f0e | ||
|
|
9e3ff27ba1 | ||
|
|
de05ebc503 | ||
|
|
6e154f2b71 | ||
|
|
9ba1e566a9 | ||
|
|
1b53d425ee | ||
|
|
7b3559ce4c | ||
|
|
43dbeb4260 | ||
|
|
d9e0e04725 | ||
|
|
629a10813f | ||
|
|
dc64864643 | ||
|
|
5f78da2d0a | ||
|
|
7915b78ac2 | ||
|
|
3441bf522e | ||
|
|
46f5db22c8 | ||
|
|
ecffcc720f | ||
|
|
be35b3af75 | ||
|
|
3aaedc790d | ||
|
|
0b19d973ac | ||
|
|
70b6cd8ee0 | ||
|
|
1eb5720e7e | ||
|
|
1b06da16f6 | ||
|
|
6e37e3d7a7 | ||
|
|
4bea638b05 | ||
|
|
8f2d17a387 | ||
|
|
f7d17fffad | ||
|
|
697ef35303 | ||
|
|
8fe3d048fa | ||
|
|
d61d92dfdf | ||
|
|
114ac1eedc | ||
|
|
a016b7b72b | ||
|
|
219bf3e5a5 | ||
|
|
b36fcfd8bb | ||
|
|
ef8fb84c8f | ||
|
|
ddecd51715 | ||
|
|
88fc6d4f24 | ||
|
|
07fbd9887e | ||
|
|
f2e32beabd | ||
|
|
8fa9dc04ac | ||
|
|
0c98f1ad13 | ||
|
|
a12cc6e843 | ||
|
|
44e1350d0c | ||
|
|
2973dd75f9 | ||
|
|
a10f1d2ee7 | ||
|
|
f727b55fbc | ||
|
|
363153cbf3 | ||
|
|
6b436eda48 | ||
|
|
2ddebca4e1 | ||
|
|
b6ab6153a2 | ||
|
|
bca99520bf | ||
|
|
1312ee8a81 | ||
|
|
b7cb204f10 | ||
|
|
921f84157a | ||
|
|
470ef30f8f | ||
|
|
87b12e663e | ||
|
|
e61700f36d | ||
|
|
8b6aca5722 | ||
|
|
19d8fae5f9 | ||
|
|
c387048f8f | ||
|
|
f2b3ba1263 | ||
|
|
2139e32426 | ||
|
|
d4e168128b | ||
|
|
54bdcba369 | ||
|
|
c55c969474 | ||
|
|
73f9879421 | ||
|
|
aa83651da2 | ||
|
|
4ddad9f9f7 | ||
|
|
080ee8869f | ||
|
|
af943b064b | ||
|
|
962eada344 | ||
|
|
a0d9750edf | ||
|
|
a1b8985df0 | ||
|
|
21daf6160a | ||
|
|
c70d28f7b8 | ||
|
|
90e6ef88dc | ||
|
|
a788034148 | ||
|
|
90bcbe9ba5 | ||
|
|
60a808ca23 | ||
|
|
edc80aed81 | ||
|
|
68bea7f273 | ||
|
|
c62f9c7147 | ||
|
|
c4ecff753d | ||
|
|
2ed77716be | ||
|
|
a2f8e144ca | ||
|
|
2cdd7d3e43 | ||
|
|
56b316a124 | ||
|
|
39ce6a21ac | ||
|
|
fc5f6fa3aa | ||
|
|
44a2f2e5f5 | ||
|
|
d356c649aa | ||
|
|
ca24d32f37 | ||
|
|
f63a63fa6c | ||
|
|
c0ff7105e7 | ||
|
|
8b063d6b92 | ||
|
|
dc8ac5e79f | ||
|
|
df7810ab63 | ||
|
|
82783417ea | ||
|
|
d5bed6b716 | ||
|
|
c8f722c0d5 | ||
|
|
b7743eda88 | ||
|
|
c0796e9701 | ||
|
|
cf54be3266 | ||
|
|
e129ae2fac | ||
|
|
ed69256512 | ||
|
|
40ad9719d4 | ||
|
|
48f75f0913 | ||
|
|
f000ea2b22 | ||
|
|
e8be687a40 | ||
|
|
788605a3a9 | ||
|
|
697cf442a2 | ||
|
|
e957edf783 | ||
|
|
09e7247d05 | ||
|
|
502f24a2ad | ||
|
|
f09f31c6d5 | ||
|
|
5a78808caa |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,2 +1,5 @@
|
||||
* text=auto
|
||||
*.mdx -linguist-detectable
|
||||
|
||||
# Keep LF line endings in testdata for consistent checksums across platforms
|
||||
testdata/** text eol=lf
|
||||
|
||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -7,3 +7,5 @@ Please understand that it may take some time to be reviewed.
|
||||
Also, make sure to follow the [Contribution Guide](https://taskfile.dev/contributing/).
|
||||
|
||||
-->
|
||||
|
||||
- [ ] I have read and followed the [Contribution Guide](https://taskfile.dev/contributing/)
|
||||
|
||||
@@ -4,13 +4,16 @@ on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-awaiting-response:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const issue = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
|
||||
7
.github/workflows/issue-closed.yml
vendored
7
.github/workflows/issue-closed.yml
vendored
@@ -4,13 +4,16 @@ on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-closed:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const labels = await github.paginate(
|
||||
github.rest.issues.listLabelsOnIssue, {
|
||||
|
||||
49
.github/workflows/issue-experiment.yml
vendored
49
.github/workflows/issue-experiment.yml
vendored
@@ -2,16 +2,19 @@ name: issue experiment
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
types: [field_added]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-experiment-proposed:
|
||||
if: github.event.label.name == format('status{0} proposed', ':')
|
||||
issue-experiment-proposal:
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'proposal'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -20,12 +23,12 @@ jobs:
|
||||
body: 'This issue has been marked as an experiment proposal! :test_tube: It will now enter a period of consultation during which we encourage the community to provide feedback on the proposed design. Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
issue-experiment-draft:
|
||||
if: github.event.label.name == format('status{0} draft', ':')
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'draft'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -34,12 +37,12 @@ jobs:
|
||||
body: 'This experiment has been marked as a draft! :sparkles: This means that an initial implementation has been added to the latest release of Task! You can find information about this experiment and how to enable it in our [experiments documentation](https://taskfile.dev/experiments). Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
issue-experiment-candidate:
|
||||
if: github.event.label.name == format('status{0} candidate', ':')
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'candidate'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -48,12 +51,12 @@ jobs:
|
||||
body: 'This experiment has been marked as a candidate! :fire: This means that the implementation is nearing completion and we are entering a period for final comments and feedback! You can find information about this experiment and how to enable it in our [experiments documentation](https://taskfile.dev/experiments). Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
issue-experiment-stable:
|
||||
if: github.event.label.name == format('status{0} stable', ':')
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'stable'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -62,12 +65,12 @@ jobs:
|
||||
body: 'This experiment has been marked as stable! :metal: This means that the implementation is now final and ready to be released. No more changes will be made and the experiment is safe to use in production! You can find information about this experiment and how to enable it in our [experiments documentation](https://taskfile.dev/experiments). Please see the [experiment workflow documentation](https://taskfile.dev/experiments#workflow) for more information on how we release experiments.'
|
||||
})
|
||||
issue-experiment-released:
|
||||
if: github.event.label.name == format('status{0} released', ':')
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'released'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -82,12 +85,12 @@ jobs:
|
||||
state: 'closed'
|
||||
})
|
||||
issue-experiment-abandoned:
|
||||
if: github.event.label.name == format('status{0} abandoned', ':')
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'abandoned'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -102,12 +105,12 @@ jobs:
|
||||
state: 'closed'
|
||||
})
|
||||
issue-experiment-superseded:
|
||||
if: github.event.label.name == format('status{0} superseded', ':')
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'superseded'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
|
||||
7
.github/workflows/issue-needs-triage.yml
vendored
7
.github/workflows/issue-needs-triage.yml
vendored
@@ -4,13 +4,16 @@ on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-needs-triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v8
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const labels = await github.paginate(
|
||||
github.rest.issues.listLabelsOnIssue, {
|
||||
|
||||
17
.github/workflows/lint.yml
vendored
17
.github/workflows/lint.yml
vendored
@@ -8,33 +8,36 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.24.x, 1.25.x]
|
||||
go-version: [1.25.10, 1.26.x]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1
|
||||
with:
|
||||
version: v2.8.0
|
||||
version: v2.12.2
|
||||
|
||||
lint-jsonschema:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: install check-jsonschema
|
||||
run: python -m pip install 'check-jsonschema==0.27.3'
|
||||
|
||||
32
.github/workflows/pr-build.yml
vendored
32
.github/workflows/pr-build.yml
vendored
@@ -13,51 +13,51 @@ jobs:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'needs-build')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: '1.25.x'
|
||||
go-version: "1.26.x"
|
||||
cache: true
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
- uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7
|
||||
with:
|
||||
version: '~> v2'
|
||||
version: "~> v2"
|
||||
args: release --snapshot --clean --config .goreleaser-pr.yml
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_linux_amd64
|
||||
path: dist/task_linux_amd64.tar.gz
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_linux_arm64
|
||||
path: dist/task_linux_arm64.tar.gz
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_darwin_amd64
|
||||
path: dist/task_darwin_amd64.tar.gz
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_darwin_arm64
|
||||
path: dist/task_darwin_arm64.tar.gz
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_windows_amd64
|
||||
path: dist/task_windows_amd64.zip
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: checksums
|
||||
path: dist/task_checksums.txt
|
||||
- uses: peter-evans/find-comment@v3
|
||||
- uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
id: find-comment
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT || github.token }}
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-includes: '📦 Build artifacts ready!'
|
||||
- uses: peter-evans/create-or-update-comment@v4
|
||||
body-includes: "📦 Build artifacts ready!"
|
||||
- uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT || github.token }}
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
|
||||
14
.github/workflows/release-nightly.yml
vendored
14
.github/workflows/release-nightly.yml
vendored
@@ -4,27 +4,31 @@ on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
go-version: 1.26.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
args: release --clean --nightly -f .goreleaser-nightly.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
|
||||
|
||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -3,51 +3,52 @@ name: goreleaser
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: read
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
go-version: 1.26.x
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: '24'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version: "24"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
|
||||
- name: Install Task
|
||||
uses: go-task/setup-task@v1
|
||||
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6
|
||||
with:
|
||||
package_json_file: 'website/package.json'
|
||||
run_install: 'true'
|
||||
package_json_file: "website/package.json"
|
||||
run_install: "true"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
args: release --clean --draft
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
GH_GORELEASER_TOKEN: ${{secrets.GH_GORELEASER_TOKEN}}
|
||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
|
||||
|
||||
@@ -55,3 +56,5 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
task website:deploy:prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
|
||||
19
.github/workflows/security.yml
vendored
Normal file
19
.github/workflows/security.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Security
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
govulncheck:
|
||||
name: govulncheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
|
||||
25
.github/workflows/test.yml
vendored
25
.github/workflows/test.yml
vendored
@@ -8,31 +8,26 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: [1.24.x, 1.25.x]
|
||||
go-version: [1.25.10, 1.26.x]
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{matrix.platform}}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Go ${{matrix.go-version}}
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
env:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
|
||||
- name: Build
|
||||
run: go build -o ./bin/task -v ./cmd/task
|
||||
|
||||
- name: Test
|
||||
run: ./bin/task test --output=group --output-group-begin='::group::{{.TASK}}' --output-group-end='::endgroup::'
|
||||
run: go run ./cmd/task test
|
||||
|
||||
@@ -2,45 +2,33 @@ version: "2"
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gci
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
rewrite-rules:
|
||||
- pattern: interface{}
|
||||
replacement: any
|
||||
gofumpt:
|
||||
module-path: github.com/go-task/task/v3
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/go-task
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/go-task)
|
||||
- localmodule
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- depguard
|
||||
- gosec
|
||||
- mirror
|
||||
- misspell
|
||||
- modernize
|
||||
- noctx
|
||||
- paralleltest
|
||||
- thelper
|
||||
- tparallel
|
||||
- usetesting
|
||||
settings:
|
||||
gosec:
|
||||
excludes:
|
||||
- G306
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
@@ -52,13 +40,8 @@ linters:
|
||||
- pkg: errors
|
||||
desc: Use github.com/go-task/task/v3/errors instead
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
@@ -22,6 +22,8 @@ builds:
|
||||
goarch: '386'
|
||||
- goos: darwin
|
||||
goarch: riscv64
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
- goos: windows
|
||||
goarch: riscv64
|
||||
env:
|
||||
@@ -60,7 +62,7 @@ nfpms:
|
||||
- vendor: Task
|
||||
homepage: https://taskfile.dev
|
||||
maintainer: The Task authors <task@taskfile.dev>
|
||||
description: Simple task runner written in Go
|
||||
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
section: golang
|
||||
license: MIT
|
||||
conflicts:
|
||||
@@ -74,19 +76,20 @@ nfpms:
|
||||
- src: completion/bash/task.bash
|
||||
dst: /etc/bash_completion.d/task
|
||||
- src: completion/fish/task.fish
|
||||
dst: /usr/share/fish/completions/task.fish
|
||||
dst: /usr/share/fish/vendor_completions.d/task.fish
|
||||
- src: completion/zsh/_task
|
||||
dst: /usr/local/share/zsh/site-functions/_task
|
||||
|
||||
brews:
|
||||
- name: go-task
|
||||
description: Task runner / simpler Make alternative written in Go
|
||||
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
license: MIT
|
||||
homepage: https://taskfile.dev
|
||||
directory: Formula
|
||||
repository:
|
||||
owner: go-task
|
||||
name: homebrew-tap
|
||||
token: "{{.Env.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
|
||||
test: system "#{bin}/task", "--help"
|
||||
install: |-
|
||||
bin.install "task"
|
||||
@@ -100,8 +103,8 @@ brews:
|
||||
winget:
|
||||
- name: Task
|
||||
publisher: Task
|
||||
short_description: A task runner / simpler Make alternative written in Go
|
||||
description: Task is a task runner / build tool that aims to be simpler and easier to use than, for example, GNU Make.
|
||||
short_description: The modern task runner.
|
||||
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
license: MIT
|
||||
homepage: https://taskfile.dev/
|
||||
publisher_url: https://taskfile.dev/
|
||||
@@ -128,6 +131,7 @@ winget:
|
||||
owner: go-task
|
||||
name: winget-pkgs
|
||||
branch: 'task-{{.Version}}'
|
||||
token: "{{.Env.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
|
||||
pull_request:
|
||||
enabled: true
|
||||
draft: false
|
||||
@@ -139,12 +143,11 @@ winget:
|
||||
body: |
|
||||
/cc @andreynering @pd93 @vmaerten
|
||||
|
||||
|
||||
npms:
|
||||
- name: "@go-task/cli"
|
||||
repository: "git+https://github.com/go-task/task.git"
|
||||
bugs: https://github.com/go-task/task/issues
|
||||
description: A task runner / simpler Make alternative written in Go
|
||||
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
homepage: https://taskfile.dev
|
||||
license: MIT
|
||||
author: "The Task authors"
|
||||
@@ -155,7 +158,6 @@ npms:
|
||||
- "build-tool"
|
||||
- "task-runner"
|
||||
|
||||
|
||||
cloudsmiths:
|
||||
- organization: "task"
|
||||
repository: "{{if not .IsNightly}}task{{end}}"
|
||||
|
||||
61
CHANGELOG.md
61
CHANGELOG.md
@@ -2,6 +2,67 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Fixed Fish completions not being picked up correctly by installing them to
|
||||
Fish's `vendor_completions.d` directory instead of `completions` (#2850, #2859
|
||||
by @Legimity).
|
||||
- PowerShell completions now work with aliases of the `task` command, not just
|
||||
the `task` binary itself (#2852 by @kojiishi).
|
||||
|
||||
## v3.51.1 - 2026-05-16
|
||||
|
||||
- A significant performance boost was achieved for large Taskfiles (monorepos)
|
||||
by skipping templating altogether when the string is static (#2820 by @romnn).
|
||||
- Added `absPath` template function that resolves a path to its absolute form,
|
||||
cleaning `..` and `.` components (#2681, #2788 by @mateenanjum).
|
||||
- Added `joinEnv` function to join paths based on your oprating system: `;` for
|
||||
Windows and `:` elsewhere, and `joinUrl` to join URL paths. Also, added two
|
||||
new special variables: `FILE_PATH_SEPARATOR` which returns `\` on Windows
|
||||
and `/` elsewhere, and `PATH_LIST_SEPARATOR` which returns `;` on Windows and
|
||||
`:` elsewhere (#2406, #2408 by @solvingj).
|
||||
- Update the shell interpreter with a regression fix (#2812, #2832 by
|
||||
@andreynering).
|
||||
- Fix potential panic with the shell interpreter (#2810 by @trulede).
|
||||
|
||||
## v3.50.0 - 2026-04-13
|
||||
|
||||
- Added `enum.ref` support in `requires`: enum constraints can now reference
|
||||
variables or template pipelines (e.g., `ref: .ALLOWED_ENVS`) instead of
|
||||
duplicating static lists. Combined with `sh:` variables, this enables fully
|
||||
dynamic enum validation (#2678 by @vmaerten).
|
||||
- Fixed Fish completion using hardcoded `task` binary name instead of
|
||||
`$GO_TASK_PROGNAME` for experiments cache (#2730, #2727 by @SergioChan).
|
||||
- Fixed watch mode ignoring SIGHUP signal, causing the watcher to exit instead
|
||||
of restarting (#2764, #2642).
|
||||
- Fixed a long time bug where the task wouldn't re-run as it should when using
|
||||
`method: timestamp` and the files listed on `generates:` were deleted.
|
||||
This makes `method: timestamp` behaves the same as `method: checksum`
|
||||
(#1230, #2716 by @drichardson).
|
||||
|
||||
## v3.49.1 - 2026-03-08
|
||||
|
||||
* Reverted #2632 for now, which caused some regressions. That change will be
|
||||
reworked (#2720, #2722, #2723).
|
||||
|
||||
## v3.49.0 - 2026-03-07
|
||||
|
||||
- Fixed included Taskfiles with `watch: true` not triggering watch mode when
|
||||
called from the root Taskfile (#2686, #1763 by @trulede).
|
||||
- Fixed Remote Git Taskfiles failing on Windows due to backslashes in URL paths
|
||||
(#2656 by @Trim21).
|
||||
- Fixed remote Git Taskfiles timing out when resolving includes after accepting
|
||||
the trust prompt (#2669, #2668 by @vmaerten).
|
||||
- Fixed unclear error message when Taskfile search stops at a directory
|
||||
ownership boundary (#2682, #1683 by @trulede).
|
||||
- Fixed global variables from imported Taskfiles not resolving `ref:` values
|
||||
correctly (#2632 by @trulede).
|
||||
- Every `.taskrc.yml` option can now be overridden with a `TASK_`-prefixed
|
||||
environment variable, making CI and container configuration easier (#2607,
|
||||
#1066 by @vmaerten).
|
||||
|
||||
## v3.48.0 - 2026-01-26
|
||||
|
||||
- Fixed `if:` conditions when using to check dynamic variables. Also, skip
|
||||
variable prompt if task would be skipped by `if:` (#2658, #2660 by @vmaerten).
|
||||
- Fixed `ROOT_TASKFILE` variable pointing to directory instead of the actual
|
||||
Taskfile path when no explicit `-t` flag is provided (#2635, #1706 by
|
||||
@trulede).
|
||||
|
||||
23
README.md
23
README.md
@@ -3,14 +3,14 @@
|
||||
<img src="website/src/public/img/logo.svg" width="200px" height="200px" />
|
||||
</a>
|
||||
|
||||
<h1>Task</h1>
|
||||
<h1>Task: The Modern Task Runner</h1>
|
||||
|
||||
<p>
|
||||
Task is a task runner / build tool that aims to be simpler and easier to use than, for example, <a href="https://www.gnu.org/software/make/">GNU Make<a>.
|
||||
A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="https://taskfile.dev/docs/installation">Installation</a> | <a href="https://taskfile.dev/docs/getting-started">Getting Started</a> | <a href="https://taskfile.dev/docs/guide">Docs</a> | <a href="https://twitter.com/taskfiledev">Twitter</a> | <a href="https://bsky.app/profile/taskfile.dev">Bluesky</a> | <a href="https://fosstodon.org/@task">Mastodon</a> | <a href="https://discord.gg/6TY36E39UK">Discord</a>
|
||||
<a href="https://taskfile.dev/docs/installation">Installation</a> • <a href="https://taskfile.dev/docs/getting-started">Getting Started</a> • <a href="https://taskfile.dev/docs/guide">Docs</a> • <a href="https://twitter.com/taskfiledev">Twitter</a> • <a href="https://bsky.app/profile/taskfile.dev">Bluesky</a> • <a href="https://fosstodon.org/@task">Mastodon</a> • <a href="https://discord.gg/6TY36E39UK">Discord</a>
|
||||
</p>
|
||||
|
||||
<h1>Gold Sponsors</h1>
|
||||
@@ -22,6 +22,11 @@
|
||||
<img src="website/src/public/img/devowl.io.svg" height="100px" width="200px" title="devowl.io" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://goodx.international/">
|
||||
<img src="website/src/public/img/goodx.svg" height="80px" width="200px" title="GoodX" />
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://magic.dev/">
|
||||
<img src="website/src/public/img/magic.png" height="100px" width="200px" title="Magic" />
|
||||
@@ -29,4 +34,16 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Community Sponsors</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://cloudsmith.com/">
|
||||
<img src="website/src/public/img/cloudsmith.svg" height="100px" width="200px" title="Cloudsmith" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -117,7 +117,7 @@ func changelog(version *semver.Version) error {
|
||||
changelog = changelogReleaseRegex.ReplaceAllString(changelog, fmt.Sprintf("## v%s - %s", version, date))
|
||||
|
||||
// Write the changelog to the source file
|
||||
if err := os.WriteFile(changelogSource, []byte(changelog), 0o644); err != nil {
|
||||
if err := os.WriteFile(changelogSource, []byte(changelog), 0o644); err != nil { //nolint:gosec
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ func changelog(version *semver.Version) error {
|
||||
changelogWithFrontmatter := fmt.Sprintf("---\n%s\n---\n\n%s", frontmatter, changelogWithVPre)
|
||||
|
||||
// Write the changelog to the target file
|
||||
return os.WriteFile(changelogTarget, []byte(changelogWithFrontmatter), 0o644)
|
||||
return os.WriteFile(changelogTarget, []byte(changelogWithFrontmatter), 0o644) //nolint:gosec
|
||||
}
|
||||
|
||||
func setVersionFile(fileName string, version *semver.Version) error {
|
||||
|
||||
21
compiler.go
21
compiler.go
@@ -198,18 +198,23 @@ func (c *Compiler) ResetCache() {
|
||||
}
|
||||
|
||||
func (c *Compiler) getSpecialVars(t *ast.Task, call *Call) (map[string]string, error) {
|
||||
// Use filepath.ToSlash for all paths to ensure consistent forward slashes
|
||||
// across platforms. This prevents issues with backslashes being interpreted
|
||||
// as escape sequences when paths are used in shell commands on Windows.
|
||||
allVars := map[string]string{
|
||||
"TASK_EXE": filepath.ToSlash(os.Args[0]),
|
||||
"ROOT_TASKFILE": filepathext.SmartJoin(c.Dir, c.Entrypoint),
|
||||
"ROOT_DIR": c.Dir,
|
||||
"USER_WORKING_DIR": c.UserWorkingDir,
|
||||
"TASK_VERSION": version.GetVersion(),
|
||||
"TASK_EXE": filepath.ToSlash(os.Args[0]),
|
||||
"ROOT_TASKFILE": filepath.ToSlash(filepathext.SmartJoin(c.Dir, c.Entrypoint)),
|
||||
"ROOT_DIR": filepath.ToSlash(c.Dir),
|
||||
"USER_WORKING_DIR": filepath.ToSlash(c.UserWorkingDir),
|
||||
"TASK_VERSION": version.GetVersion(),
|
||||
"PATH_LIST_SEPARATOR": string(os.PathListSeparator),
|
||||
"FILE_PATH_SEPARATOR": string(os.PathSeparator),
|
||||
}
|
||||
if t != nil {
|
||||
allVars["TASK"] = t.Task
|
||||
allVars["TASK_DIR"] = filepathext.SmartJoin(c.Dir, t.Dir)
|
||||
allVars["TASKFILE"] = t.Location.Taskfile
|
||||
allVars["TASKFILE_DIR"] = filepath.Dir(t.Location.Taskfile)
|
||||
allVars["TASK_DIR"] = filepath.ToSlash(filepathext.SmartJoin(c.Dir, t.Dir))
|
||||
allVars["TASKFILE"] = filepath.ToSlash(t.Location.Taskfile)
|
||||
allVars["TASKFILE_DIR"] = filepath.ToSlash(filepath.Dir(t.Location.Taskfile))
|
||||
} else {
|
||||
allVars["TASK"] = ""
|
||||
allVars["TASK_DIR"] = ""
|
||||
|
||||
@@ -5,7 +5,7 @@ set -g __task_experiments_cache ""
|
||||
set -g __task_experiments_cache_time 0
|
||||
|
||||
# Helper function to get experiments with 1-second cache
|
||||
function __task_get_experiments
|
||||
function __task_get_experiments --inherit-variable GO_TASK_PROGNAME
|
||||
set -l now (date +%s)
|
||||
set -l ttl 1 # Cache for 1 second only
|
||||
|
||||
@@ -16,7 +16,7 @@ function __task_get_experiments
|
||||
end
|
||||
|
||||
# Refresh cache
|
||||
set -g __task_experiments_cache (task --experiments 2>/dev/null)
|
||||
set -g __task_experiments_cache ($GO_TASK_PROGNAME --experiments 2>/dev/null)
|
||||
set -g __task_experiments_cache_time $now
|
||||
printf '%s\n' $__task_experiments_cache
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using namespace System.Management.Automation
|
||||
|
||||
Register-ArgumentCompleter -CommandName task -ScriptBlock {
|
||||
$cmdNames = @('task') + (Get-Alias -Definition task,task.exe,*\task,*\task.exe -ErrorAction SilentlyContinue).Name | Select-Object -Unique
|
||||
|
||||
Register-ArgumentCompleter -CommandName $cmdNames -ScriptBlock {
|
||||
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
||||
|
||||
if ($commandName.StartsWith('-')) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -50,10 +50,10 @@ func (err *TaskfileDecodeError) Error() string {
|
||||
if len(te.Errors) > 1 {
|
||||
fmt.Fprintln(buf, color.RedString("errs:"))
|
||||
for _, message := range te.Errors {
|
||||
fmt.Fprintln(buf, color.RedString("- %s", message.Err.Error()))
|
||||
fmt.Fprintln(buf, color.RedString("- %s", message))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", te.Errors[0].Err.Error()))
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", te.Errors[0]))
|
||||
}
|
||||
} else {
|
||||
// Otherwise print the error message normally
|
||||
|
||||
@@ -195,9 +195,9 @@ type TaskNotAllowedVarsError struct {
|
||||
func (err *TaskNotAllowedVarsError) Error() string {
|
||||
var builder strings.Builder
|
||||
|
||||
builder.WriteString(fmt.Sprintf("task: Task %q cancelled because it is missing required variables:\n", err.TaskName))
|
||||
builder.WriteString(fmt.Sprintf("task: Task %q cancelled because it is missing required variables:\n", err.TaskName)) //nolint:staticcheck
|
||||
for _, s := range err.NotAllowedVars {
|
||||
builder.WriteString(fmt.Sprintf(" - %s has an invalid value : '%s' (allowed values : %v)\n", s.Name, s.Value, s.Enum))
|
||||
builder.WriteString(fmt.Sprintf(" - %s has an invalid value : '%s' (allowed values : %v)\n", s.Name, s.Value, s.Enum)) //nolint:staticcheck
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
|
||||
@@ -3,6 +3,7 @@ package errors
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
@@ -11,20 +12,23 @@ import (
|
||||
// TaskfileNotFoundError is returned when no appropriate Taskfile is found when
|
||||
// searching the filesystem.
|
||||
type TaskfileNotFoundError struct {
|
||||
URI string
|
||||
Walk bool
|
||||
AskInit bool
|
||||
URI string
|
||||
Walk bool
|
||||
AskInit bool
|
||||
OwnerChange bool
|
||||
}
|
||||
|
||||
func (err TaskfileNotFoundError) Error() string {
|
||||
var walkText string
|
||||
if err.Walk {
|
||||
if err.OwnerChange {
|
||||
walkText = " (or any of the parent directories until ownership changed)."
|
||||
} else if err.Walk {
|
||||
walkText = " (or any of the parent directories)."
|
||||
}
|
||||
if err.AskInit {
|
||||
walkText += " Run `task --init` to create a new Taskfile."
|
||||
}
|
||||
return fmt.Sprintf(`task: No Taskfile found at %q%s`, err.URI, walkText)
|
||||
return fmt.Sprintf(`task: No Taskfile found at %q%s`, filepath.ToSlash(err.URI), walkText)
|
||||
}
|
||||
|
||||
func (err TaskfileNotFoundError) Code() int {
|
||||
@@ -51,7 +55,7 @@ type TaskfileInvalidError struct {
|
||||
}
|
||||
|
||||
func (err TaskfileInvalidError) Error() string {
|
||||
return fmt.Sprintf("task: Failed to parse %s:\n%v", err.URI, err.Err)
|
||||
return fmt.Sprintf("task: Failed to parse %s:\n%v", filepath.ToSlash(err.URI), err.Err)
|
||||
}
|
||||
|
||||
func (err TaskfileInvalidError) Code() int {
|
||||
@@ -70,7 +74,7 @@ func (err TaskfileFetchFailedError) Error() string {
|
||||
if err.HTTPStatusCode != 0 {
|
||||
statusText = fmt.Sprintf(" with status code %d (%s)", err.HTTPStatusCode, http.StatusText(err.HTTPStatusCode))
|
||||
}
|
||||
return fmt.Sprintf(`task: Download of %q failed%s`, err.URI, statusText)
|
||||
return fmt.Sprintf(`task: Download of %q failed%s`, filepath.ToSlash(err.URI), statusText)
|
||||
}
|
||||
|
||||
func (err TaskfileFetchFailedError) Code() int {
|
||||
@@ -86,7 +90,7 @@ type TaskfileNotTrustedError struct {
|
||||
func (err *TaskfileNotTrustedError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: Taskfile %q not trusted by user`,
|
||||
err.URI,
|
||||
filepath.ToSlash(err.URI),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -103,7 +107,7 @@ type TaskfileNotSecureError struct {
|
||||
func (err *TaskfileNotSecureError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: Taskfile %q cannot be downloaded over an insecure connection. You can override this by using the --insecure flag`,
|
||||
err.URI,
|
||||
filepath.ToSlash(err.URI),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -120,7 +124,7 @@ type TaskfileCacheNotFoundError struct {
|
||||
func (err *TaskfileCacheNotFoundError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: Taskfile %q was not found in the cache. Remove the --offline flag to use a remote copy or download it using the --download flag`,
|
||||
err.URI,
|
||||
filepath.ToSlash(err.URI),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -141,12 +145,12 @@ func (err *TaskfileVersionCheckError) Error() string {
|
||||
if err.SchemaVersion == nil {
|
||||
return fmt.Sprintf(
|
||||
`task: Missing schema version in Taskfile %q`,
|
||||
err.URI,
|
||||
filepath.ToSlash(err.URI),
|
||||
)
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"task: Invalid schema version in Taskfile %q:\nSchema version (%s) %s",
|
||||
err.URI,
|
||||
filepath.ToSlash(err.URI),
|
||||
err.SchemaVersion.String(),
|
||||
err.Message,
|
||||
)
|
||||
@@ -166,7 +170,7 @@ type TaskfileNetworkTimeoutError struct {
|
||||
func (err *TaskfileNetworkTimeoutError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: Network connection timed out after %s while attempting to download Taskfile %q`,
|
||||
err.Timeout, err.URI,
|
||||
err.Timeout, filepath.ToSlash(err.URI),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -183,8 +187,8 @@ type TaskfileCycleError struct {
|
||||
|
||||
func (err TaskfileCycleError) Error() string {
|
||||
return fmt.Sprintf("task: include cycle detected between %s <--> %s",
|
||||
err.Source,
|
||||
err.Destination,
|
||||
filepath.ToSlash(err.Source),
|
||||
filepath.ToSlash(err.Destination),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -203,7 +207,7 @@ type TaskfileDoesNotMatchChecksum struct {
|
||||
func (err *TaskfileDoesNotMatchChecksum) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"task: The checksum of the Taskfile at %q does not match!\ngot: %q\nwant: %q",
|
||||
err.URI,
|
||||
filepath.ToSlash(err.URI),
|
||||
err.ActualChecksum,
|
||||
err.ExpectedChecksum,
|
||||
)
|
||||
|
||||
@@ -165,6 +165,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
// Create a golden fixture file for the output
|
||||
g := goldie.New(t,
|
||||
goldie.WithFixtureDir(filepath.Join(e.Dir, "testdata")),
|
||||
goldie.WithEqualFn(NormalizedEqual),
|
||||
)
|
||||
|
||||
// Call setup and check for errors
|
||||
@@ -351,6 +352,41 @@ func TestRequires(t *testing.T) {
|
||||
),
|
||||
WithTask("var-defined-in-task"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("enum ref - passes validation"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("validation-var-ref"),
|
||||
WithVar("ENV", "dev"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("enum ref - fails validation"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("validation-var-ref"),
|
||||
WithVar("ENV", "invalid"),
|
||||
WithRunError(),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("enum ref - ref to non-list"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("validation-var-ref-invalid"),
|
||||
WithVar("VALUE", "test"),
|
||||
WithRunError(),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("enum ref - ref to nonexistent var"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("validation-var-ref-nonexistent"),
|
||||
WithVar("ENV", "dev"),
|
||||
WithRunError(),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: mock fs
|
||||
@@ -1160,6 +1196,10 @@ func TestIf(t *testing.T) {
|
||||
|
||||
// For loop with if
|
||||
{name: "if-in-for-loop", task: "if-in-for-loop", verbose: true},
|
||||
|
||||
// Task-level if with dynamic variable
|
||||
{name: "task-if-dynamic-true", task: "task-if-dynamic-true"},
|
||||
{name: "task-if-dynamic-false", task: "task-if-dynamic-false", verbose: true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -127,6 +127,7 @@ func (tt *FormatterTest) run(t *testing.T) {
|
||||
// Create a golden fixture file for the output
|
||||
g := goldie.New(t,
|
||||
goldie.WithFixtureDir(filepath.Join(e.Dir, "testdata")),
|
||||
goldie.WithEqualFn(NormalizedEqual),
|
||||
)
|
||||
|
||||
// Call setup and check for errors
|
||||
|
||||
159
go.mod
159
go.mod
@@ -1,106 +1,103 @@
|
||||
module github.com/go-task/task/v3
|
||||
|
||||
go 1.24.6
|
||||
|
||||
toolchain go1.25.6
|
||||
go 1.25.10
|
||||
|
||||
require (
|
||||
charm.land/bubbles/v2 v2.0.0-rc.1
|
||||
charm.land/bubbletea/v2 v2.0.0-rc.2
|
||||
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7
|
||||
charm.land/bubbles/v2 v2.1.0
|
||||
charm.land/bubbletea/v2 v2.0.6
|
||||
charm.land/lipgloss/v2 v2.0.3
|
||||
github.com/Ladicle/tabwriter v1.0.0
|
||||
github.com/Masterminds/semver/v3 v3.4.0
|
||||
github.com/alecthomas/chroma/v2 v2.23.0
|
||||
github.com/Masterminds/semver/v3 v3.5.0
|
||||
github.com/alecthomas/chroma/v2 v2.26.1
|
||||
github.com/chainguard-dev/git-urls v1.0.2
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/dominikbraun/graph v0.23.0
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/fatih/color v1.19.0
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0
|
||||
github.com/go-task/template v0.2.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/go-getter v1.8.4
|
||||
github.com/hashicorp/go-getter v1.8.6
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0
|
||||
github.com/sajari/fuzzy v1.0.0
|
||||
github.com/sebdah/goldie/v2 v2.8.0
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/term v0.39.0
|
||||
github.com/zeebo/xxh3 v1.1.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/term v0.43.0
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997
|
||||
mvdan.cc/sh/v3 v3.12.1-0.20260124232039-e74afc18e65b
|
||||
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.17.0 // indirect
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.3 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
cloud.google.com/go/storage v1.58.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||
cloud.google.com/go/storage v1.61.3 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||
github.com/aws/smithy-go v1.24.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // 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.22 // 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.13 // 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.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.3.3 // indirect
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.7 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.5.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/dlclark/regexp2/v2 v2.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 // 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/stdr v1.2.2 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
@@ -108,33 +105,33 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8 // indirect
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/oauth2 v0.33.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
google.golang.org/api v0.256.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/grpc v1.76.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/api v0.271.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
341
go.sum
341
go.sum
@@ -1,103 +1,103 @@
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
charm.land/bubbles/v2 v2.0.0-rc.1 h1:EiIFVAc3Zi/yY86td+79mPhHR7AqZ1OxF+6ztpOCRaM=
|
||||
charm.land/bubbles/v2 v2.0.0-rc.1/go.mod h1:5AbN6cEd/47gkEf8TgiQ2O3RZ5QxMS14l9W+7F9fPC4=
|
||||
charm.land/bubbletea/v2 v2.0.0-rc.2 h1:TdTbUOFzbufDJmSz/3gomL6q+fR6HwfY+P13hXQzD7k=
|
||||
charm.land/bubbletea/v2 v2.0.0-rc.2/go.mod h1:IXFmnCnMLTWw/KQ9rEatSYqbAPAYi8kA3Yqwa1SFnLk=
|
||||
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7 h1:059k1h5vvZ4ASinki9nmBguxu9Rq0UDDSa6q8LOUphk=
|
||||
charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7/go.mod h1:1qZyvvVCenJO2M1ac2mX0yyiIZJoZmDM4DG4s0udJkU=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
|
||||
charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
|
||||
charm.land/bubbletea/v2 v2.0.6 h1:UHN/91OyuhaOFGSrBXQ/hMZD8IO1Uc4BvHlgHXL2WJo=
|
||||
charm.land/bubbletea/v2 v2.0.6/go.mod h1:MH/D8ZLlN3op37vQvijKuU29g3rqTp+aQapURFonF9g=
|
||||
charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU=
|
||||
charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
|
||||
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
|
||||
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
||||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
||||
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
|
||||
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
|
||||
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
|
||||
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
|
||||
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
|
||||
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
|
||||
cloud.google.com/go/storage v1.58.0 h1:PflFXlmFJjG/nBeR9B7pKddLQWaFaRWx4uUi/LyNxxo=
|
||||
cloud.google.com/go/storage v1.58.0/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=
|
||||
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
|
||||
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
|
||||
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
|
||||
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
|
||||
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
|
||||
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
|
||||
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
|
||||
cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=
|
||||
cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk=
|
||||
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
|
||||
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
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.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
|
||||
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.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
|
||||
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
|
||||
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.23.0 h1:u/Orux1J0eLuZDeQ44froV8smumheieI0EofhbyKhhk=
|
||||
github.com/alecthomas/chroma/v2 v2.23.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
||||
github.com/alecthomas/chroma/v2 v2.26.1 h1:2X21EdxGZNv5GF9mG5u+uzc02GCFyGxbcBm3Grd9A78=
|
||||
github.com/alecthomas/chroma/v2 v2.26.1/go.mod h1:lxhRRa9H4hPmRLOOdYga4zkQIQjq3dtrrdwQeCfu78Y=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
|
||||
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
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.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g=
|
||||
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.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE=
|
||||
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.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE=
|
||||
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.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
|
||||
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.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
|
||||
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.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
|
||||
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.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
|
||||
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.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
|
||||
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.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
|
||||
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.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
|
||||
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.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
|
||||
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.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow=
|
||||
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.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o=
|
||||
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.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU=
|
||||
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.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk=
|
||||
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/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/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ=
|
||||
github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
|
||||
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
|
||||
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 h1:7Rs87fbKJoIIxsQS8YKJYGYa0tlsDwwb0twQjV1KB+g=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38/go.mod h1:6lfcr3MNP+kZR25sF1nQwJFuQnNYBlFy3PGX5rvslXc=
|
||||
github.com/charmbracelet/x/ansi v0.11.1 h1:iXAC8SyMQDJgtcz9Jnw+HU8WMEctHzoTAETIeA3JXMk=
|
||||
github.com/charmbracelet/x/ansi v0.11.1/go.mod h1:M49wjzpIujwPceJ+t5w3qh2i87+HRtHohgb5iTyepL0=
|
||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 h1:Q9fO0y1Zo5KB/5Vu8JZoLGm1N3RzF9bNj3Ao3xoR+Ac=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468/go.mod h1:bAAz7dh/FTYfC+oiHavL4mX1tOIBZ0ZwYjSi3qE6ivM=
|
||||
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
|
||||
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
@@ -106,44 +106,42 @@ github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8
|
||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
|
||||
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
||||
github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
|
||||
github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2/v2 v2.1.1 h1:LCUGyd9Wf+r+VVOl8Ny38JTpWJcAsdVnCIuhhtthmKw=
|
||||
github.com/dlclark/regexp2/v2 v2.1.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
||||
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
|
||||
github.com/envoyproxy/go-control-plane 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/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -165,26 +163,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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 h1:0HADrxxqaQkGycO1JoUUA+B4FnIkuo8d2bz/hSaTFFQ=
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70/go.mod h1:fm2FdDCzJdtbXF7WKAMvBb5NEPouXPHFbGNYs9ShFns=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||
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.72/go.mod h1:Vn+BBgKQHVQYdVQ4NZDICE1Brb+JfaONyDHr3q07oQc=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-getter v1.8.4 h1:hGEd2xsuVKgwkMtPVufq73fAmZU/x65PPcqH3cb0D9A=
|
||||
github.com/hashicorp/go-getter v1.8.4/go.mod h1:x27pPGSg9kzoB147QXI8d/nDvp2IgYGcwuRjpaXE9Yg=
|
||||
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-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
|
||||
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -194,14 +192,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
@@ -216,8 +214,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0 h1:w/bWkEJdYuRNYhHn5eXnIT8LzDM1O629X1I9MJSkD7Q=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
@@ -231,8 +229,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
@@ -250,66 +248,63 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
|
||||
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
|
||||
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
|
||||
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc=
|
||||
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/api v0.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY=
|
||||
google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
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 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -320,5 +315,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997 h1:3bbJwtPFh98dJ6lxRdR3eLHTH1CmR3BcU6TriIMiXjE=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997/go.mod h1:Qy/zdaMDxq9sT72Gi43K3gsV+TtTohyDO3f1cyBVwuo=
|
||||
mvdan.cc/sh/v3 v3.12.1-0.20260124232039-e74afc18e65b h1:PUPnLxbDzRO9kg/03l7TZk7+ywTv7FxmOhDHOtOdOtk=
|
||||
mvdan.cc/sh/v3 v3.12.1-0.20260124232039-e74afc18e65b/go.mod h1:mencVHx2sy9XZG5wJbCA9nRUOE3zvMtoRXOmXMxH7sc=
|
||||
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=
|
||||
|
||||
@@ -81,7 +81,7 @@ func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, erro
|
||||
traverseFunc = func(copy, v reflect.Value) error {
|
||||
switch v.Kind() {
|
||||
|
||||
case reflect.Ptr:
|
||||
case reflect.Pointer:
|
||||
// Unwrap the pointer
|
||||
originalValue := v.Elem()
|
||||
// If the pointer is nil, do nothing
|
||||
|
||||
62
internal/env/env.go
vendored
62
internal/env/env.go
vendored
@@ -3,7 +3,9 @@ package env
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
@@ -61,3 +63,63 @@ func isTypeAllowed(v any) bool {
|
||||
func GetTaskEnv(key string) string {
|
||||
return os.Getenv(taskVarPrefix + key)
|
||||
}
|
||||
|
||||
// GetTaskEnvBool returns the boolean value of a TASK_ prefixed env var.
|
||||
// Returns the value and true if set and valid, or false and false if not set or invalid.
|
||||
func GetTaskEnvBool(key string) (bool, bool) {
|
||||
v := GetTaskEnv(key)
|
||||
if v == "" {
|
||||
return false, false
|
||||
}
|
||||
b, err := strconv.ParseBool(v)
|
||||
return b, err == nil
|
||||
}
|
||||
|
||||
// GetTaskEnvInt returns the integer value of a TASK_ prefixed env var.
|
||||
// Returns the value and true if set and valid, or 0 and false if not set or invalid.
|
||||
func GetTaskEnvInt(key string) (int, bool) {
|
||||
v := GetTaskEnv(key)
|
||||
if v == "" {
|
||||
return 0, false
|
||||
}
|
||||
i, err := strconv.Atoi(v)
|
||||
return i, err == nil
|
||||
}
|
||||
|
||||
// GetTaskEnvDuration returns the duration value of a TASK_ prefixed env var.
|
||||
// Returns the value and true if set and valid, or 0 and false if not set or invalid.
|
||||
func GetTaskEnvDuration(key string) (time.Duration, bool) {
|
||||
v := GetTaskEnv(key)
|
||||
if v == "" {
|
||||
return 0, false
|
||||
}
|
||||
d, err := time.ParseDuration(v)
|
||||
return d, err == nil
|
||||
}
|
||||
|
||||
// GetTaskEnvString returns the string value of a TASK_ prefixed env var.
|
||||
// Returns the value and true if set (non-empty), or empty string and false if not set.
|
||||
func GetTaskEnvString(key string) (string, bool) {
|
||||
v := GetTaskEnv(key)
|
||||
return v, v != ""
|
||||
}
|
||||
|
||||
// GetTaskEnvStringSlice returns a comma-separated list from a TASK_ prefixed env var.
|
||||
// Returns the slice and true if set (non-empty), or nil and false if not set.
|
||||
func GetTaskEnvStringSlice(key string) ([]string, bool) {
|
||||
v := GetTaskEnv(key)
|
||||
if v == "" {
|
||||
return nil, false
|
||||
}
|
||||
parts := strings.Split(v, ",")
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
if trimmed := strings.TrimSpace(p); trimmed != "" {
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
@@ -127,7 +127,10 @@ func ExpandFields(s string) ([]string, error) {
|
||||
s = escape(s)
|
||||
p := syntax.NewParser()
|
||||
var words []*syntax.Word
|
||||
for w := range p.WordsSeq(strings.NewReader(s)) {
|
||||
for w, err := range p.WordsSeq(strings.NewReader(s)) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
words = append(words, w)
|
||||
}
|
||||
cfg := &expand.Config{
|
||||
|
||||
102
internal/fingerprint/gitignore.go
Normal file
102
internal/fingerprint/gitignore.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/internal/gitignore"
|
||||
)
|
||||
|
||||
type gitignoreRule struct {
|
||||
dir string
|
||||
matcher gitignore.Matcher
|
||||
}
|
||||
|
||||
// loadGitignoreRules walks up from dir collecting .gitignore files.
|
||||
// Stops at the first .git (file or directory) found.
|
||||
// Returns nil if no .git is found (not in a git repo).
|
||||
func loadGitignoreRules(dir string) []gitignoreRule {
|
||||
dir, _ = filepath.Abs(dir)
|
||||
|
||||
var rules []gitignoreRule
|
||||
foundGit := false
|
||||
current := dir
|
||||
|
||||
for {
|
||||
lines := readGitignoreLines(filepath.Join(current, ".gitignore"))
|
||||
if len(lines) > 0 {
|
||||
patterns := make([]gitignore.Pattern, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
patterns = append(patterns, gitignore.ParsePattern(line, nil))
|
||||
}
|
||||
rules = append(rules, gitignoreRule{
|
||||
dir: current,
|
||||
matcher: gitignore.NewMatcher(patterns),
|
||||
})
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(current, ".git")); err == nil {
|
||||
foundGit = true
|
||||
break
|
||||
}
|
||||
parent := filepath.Dir(current)
|
||||
if parent == current {
|
||||
break
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
|
||||
if !foundGit {
|
||||
return nil
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
func readGitignoreLines(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)
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
// filterGitignored removes entries from the file map that match gitignore rules.
|
||||
func filterGitignored(files map[string]bool, dir string) map[string]bool {
|
||||
rules := loadGitignoreRules(dir)
|
||||
if len(rules) == 0 {
|
||||
return files
|
||||
}
|
||||
|
||||
for path := range files {
|
||||
for _, rule := range rules {
|
||||
relPath, err := filepath.Rel(rule.dir, path)
|
||||
if err != nil || strings.HasPrefix(relPath, "..") {
|
||||
continue
|
||||
}
|
||||
// Sources are files, not directories; pass isDir=false. Per the
|
||||
// gitignore spec this still matches files under an ignored dir
|
||||
// (e.g. "build/" matches build/out.txt).
|
||||
if rule.matcher.Match(strings.Split(filepath.ToSlash(relPath), "/"), false) {
|
||||
files[path] = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
112
internal/fingerprint/gitignore_test.go
Normal file
112
internal/fingerprint/gitignore_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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 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 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)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package fingerprint
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
@@ -9,7 +10,7 @@ import (
|
||||
"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)
|
||||
for _, g := range globs {
|
||||
matches, err := glob(dir, g.Glob)
|
||||
@@ -20,6 +21,11 @@ func Globs(dir string, globs []*ast.Glob) ([]string, error) {
|
||||
resultMap[match] = !g.Negate
|
||||
}
|
||||
}
|
||||
|
||||
if useGitignore {
|
||||
resultMap = filterGitignored(resultMap, dir)
|
||||
}
|
||||
|
||||
return collectKeys(resultMap), nil
|
||||
}
|
||||
|
||||
@@ -50,7 +56,8 @@ func collectKeys(m map[string]bool) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k, v := range m {
|
||||
if v {
|
||||
keys = append(keys, k)
|
||||
// Normalize path separators for consistent sorting across platforms
|
||||
keys = append(keys, filepath.ToSlash(k))
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
@@ -53,6 +53,7 @@ func (checker *ChecksumChecker) IsUpToDate(t *ast.Task) (bool, error) {
|
||||
if len(t.Generates) > 0 {
|
||||
// For each specified 'generates' field, check whether the files actually exist
|
||||
for _, g := range t.Generates {
|
||||
// Exclusion patterns don't represent output files; skip them.
|
||||
if g.Negate {
|
||||
continue
|
||||
}
|
||||
@@ -88,7 +89,7 @@ func (*ChecksumChecker) Kind() string {
|
||||
}
|
||||
|
||||
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 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -28,11 +28,33 @@ func (checker *TimestampChecker) IsUpToDate(t *ast.Task) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
sources, err := Globs(t.Dir, t.Sources)
|
||||
sources, err := Globs(t.Dir, t.Sources, t.ShouldUseGitignore())
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
generates, err := Globs(t.Dir, t.Generates)
|
||||
|
||||
// If generates are declared, ensure they all exist. A missing generated
|
||||
// file means the task must run regardless of timestamps.
|
||||
if len(t.Generates) > 0 {
|
||||
for _, g := range t.Generates {
|
||||
// Exclusion patterns don't represent output files; skip them.
|
||||
if g.Negate {
|
||||
continue
|
||||
}
|
||||
files, err := glob(t.Dir, g.Glob)
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generates, err := Globs(t.Dir, t.Generates, t.ShouldUseGitignore())
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
@@ -90,7 +112,7 @@ func (checker *TimestampChecker) Kind() string {
|
||||
|
||||
// Value implements the Checker Interface
|
||||
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 {
|
||||
return time.Now(), err
|
||||
}
|
||||
|
||||
@@ -130,15 +130,15 @@ func init() {
|
||||
pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.")
|
||||
pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON")
|
||||
pflag.BoolVar(&Nested, "nested", false, "Nest namespaces when listing tasks as JSON")
|
||||
pflag.BoolVar(&Insecure, "insecure", getConfig(config, func() *bool { return config.Remote.Insecure }, false), "Forces Task to download Taskfiles over insecure connections.")
|
||||
pflag.BoolVar(&Insecure, "insecure", getConfig(config, "REMOTE_INSECURE", func() *bool { return config.Remote.Insecure }, false), "Forces Task to download Taskfiles over insecure connections.")
|
||||
pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.")
|
||||
pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, func() *bool { return config.Verbose }, false), "Enables verbose mode.")
|
||||
pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.")
|
||||
pflag.BoolVar(&DisableFuzzy, "disable-fuzzy", getConfig(config, func() *bool { return config.DisableFuzzy }, false), "Disables fuzzy matching for task names.")
|
||||
pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.")
|
||||
pflag.BoolVar(&Interactive, "interactive", getConfig(config, func() *bool { return config.Interactive }, false), "Prompt for missing required variables.")
|
||||
pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, "VERBOSE", func() *bool { return config.Verbose }, false), "Enables verbose mode.")
|
||||
pflag.BoolVarP(&Silent, "silent", "s", getConfig(config, "SILENT", func() *bool { return config.Silent }, false), "Disables echoing.")
|
||||
pflag.BoolVar(&DisableFuzzy, "disable-fuzzy", getConfig(config, "DISABLE_FUZZY", func() *bool { return config.DisableFuzzy }, false), "Disables fuzzy matching for task names.")
|
||||
pflag.BoolVarP(&AssumeYes, "yes", "y", getConfig(config, "ASSUME_YES", func() *bool { return nil }, false), "Assume \"yes\" as answer to all prompts.")
|
||||
pflag.BoolVar(&Interactive, "interactive", getConfig(config, "INTERACTIVE", func() *bool { return config.Interactive }, false), "Prompt for missing required variables.")
|
||||
pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.")
|
||||
pflag.BoolVarP(&Dry, "dry", "n", false, "Compiles and prints tasks in the order that they would be run, without executing them.")
|
||||
pflag.BoolVarP(&Dry, "dry", "n", getConfig(config, "DRY", func() *bool { return nil }, false), "Compiles and prints tasks in the order that they would be run, without executing them.")
|
||||
pflag.BoolVar(&Summary, "summary", false, "Show summary about a task.")
|
||||
pflag.BoolVarP(&ExitCode, "exit-code", "x", false, "Pass-through the exit code of the task command.")
|
||||
pflag.StringVarP(&Dir, "dir", "d", "", "Sets the directory in which Task will execute and look for a Taskfile.")
|
||||
@@ -147,10 +147,10 @@ func init() {
|
||||
pflag.StringVar(&Output.Group.Begin, "output-group-begin", "", "Message template to print before a task's grouped output.")
|
||||
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.")
|
||||
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.")
|
||||
pflag.BoolVarP(&Color, "color", "c", getConfig(config, func() *bool { return config.Color }, true), "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
|
||||
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
|
||||
pflag.BoolVarP(&Color, "color", "c", getConfig(config, "COLOR", func() *bool { return config.Color }, true), "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
|
||||
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, "CONCURRENCY", func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
|
||||
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
|
||||
pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.")
|
||||
pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, "FAILFAST", func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.")
|
||||
pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
|
||||
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
|
||||
|
||||
@@ -165,21 +165,21 @@ func init() {
|
||||
// Remote Taskfiles experiment will adds the "download" and "offline" flags
|
||||
if experiments.RemoteTaskfiles.Enabled() {
|
||||
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
|
||||
pflag.BoolVar(&Offline, "offline", getConfig(config, func() *bool { return config.Remote.Offline }, false), "Forces Task to only use local or cached Taskfiles.")
|
||||
pflag.StringSliceVar(&TrustedHosts, "trusted-hosts", getConfig(config, func() *[]string { return &config.Remote.TrustedHosts }, nil), "List of trusted hosts for remote Taskfiles (comma-separated).")
|
||||
pflag.DurationVar(&Timeout, "timeout", getConfig(config, func() *time.Duration { return config.Remote.Timeout }, time.Second*10), "Timeout for downloading remote Taskfiles.")
|
||||
pflag.BoolVar(&Offline, "offline", getConfig(config, "REMOTE_OFFLINE", func() *bool { return config.Remote.Offline }, false), "Forces Task to only use local or cached Taskfiles.")
|
||||
pflag.StringSliceVar(&TrustedHosts, "trusted-hosts", getConfig(config, "REMOTE_TRUSTED_HOSTS", func() *[]string { return &config.Remote.TrustedHosts }, nil), "List of trusted hosts for remote Taskfiles (comma-separated).")
|
||||
pflag.DurationVar(&Timeout, "timeout", getConfig(config, "REMOTE_TIMEOUT", func() *time.Duration { return config.Remote.Timeout }, time.Second*10), "Timeout for downloading remote Taskfiles.")
|
||||
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
|
||||
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
|
||||
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
|
||||
pflag.StringVar(&CACert, "cacert", getConfig(config, func() *string { return config.Remote.CACert }, ""), "Path to a custom CA certificate for HTTPS connections.")
|
||||
pflag.StringVar(&Cert, "cert", getConfig(config, func() *string { return config.Remote.Cert }, ""), "Path to a client certificate for HTTPS connections.")
|
||||
pflag.StringVar(&CertKey, "cert-key", getConfig(config, func() *string { return config.Remote.CertKey }, ""), "Path to a client certificate key for HTTPS connections.")
|
||||
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, "REMOTE_CACHE_EXPIRY", func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
|
||||
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, "REMOTE_CACHE_DIR", func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
|
||||
pflag.StringVar(&CACert, "cacert", getConfig(config, "REMOTE_CACERT", func() *string { return config.Remote.CACert }, ""), "Path to a custom CA certificate for HTTPS connections.")
|
||||
pflag.StringVar(&Cert, "cert", getConfig(config, "REMOTE_CERT", func() *string { return config.Remote.Cert }, ""), "Path to a client certificate for HTTPS connections.")
|
||||
pflag.StringVar(&CertKey, "cert-key", getConfig(config, "REMOTE_CERT_KEY", func() *string { return config.Remote.CertKey }, ""), "Path to a client certificate key for HTTPS connections.")
|
||||
}
|
||||
pflag.Parse()
|
||||
|
||||
// Auto-detect color based on environment when not explicitly configured
|
||||
// Priority: CLI flag > taskrc config > NO_COLOR > FORCE_COLOR/CI > default
|
||||
colorExplicitlySet := pflag.Lookup("color").Changed || (config != nil && config.Color != nil)
|
||||
// Priority: CLI flag > TASK_COLOR env > taskrc config > NO_COLOR > FORCE_COLOR/CI > default
|
||||
colorExplicitlySet := pflag.Lookup("color").Changed || env.GetTaskEnv("COLOR") != "" || (config != nil && config.Color != nil)
|
||||
if !colorExplicitlySet {
|
||||
if os.Getenv("NO_COLOR") != "" {
|
||||
Color = false
|
||||
@@ -311,15 +311,45 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
)
|
||||
}
|
||||
|
||||
// getConfig extracts a config value directly from a pointer field with a fallback default
|
||||
func getConfig[T any](config *taskrcast.TaskRC, fieldFunc func() *T, fallback T) T {
|
||||
if config == nil {
|
||||
return fallback
|
||||
// getConfig extracts a config value with priority: env var > taskrc config > fallback
|
||||
func getConfig[T any](config *taskrcast.TaskRC, envKey string, fieldFunc func() *T, fallback T) T {
|
||||
if envKey != "" {
|
||||
if val, ok := getEnvAs[T](envKey); ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
field := fieldFunc()
|
||||
if field != nil {
|
||||
return *field
|
||||
if config != nil {
|
||||
if field := fieldFunc(); field != nil {
|
||||
return *field
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// getEnvAs parses a TASK_ prefixed env var as type T
|
||||
func getEnvAs[T any](envKey string) (T, bool) {
|
||||
var zero T
|
||||
switch any(zero).(type) {
|
||||
case bool:
|
||||
if val, ok := env.GetTaskEnvBool(envKey); ok {
|
||||
return any(val).(T), true
|
||||
}
|
||||
case int:
|
||||
if val, ok := env.GetTaskEnvInt(envKey); ok {
|
||||
return any(val).(T), true
|
||||
}
|
||||
case time.Duration:
|
||||
if val, ok := env.GetTaskEnvDuration(envKey); ok {
|
||||
return any(val).(T), true
|
||||
}
|
||||
case string:
|
||||
if val, ok := env.GetTaskEnvString(envKey); ok {
|
||||
return any(val).(T), true
|
||||
}
|
||||
case []string:
|
||||
if val, ok := env.GetTaskEnvStringSlice(envKey); ok {
|
||||
return any(val).(T), true
|
||||
}
|
||||
}
|
||||
return zero, false
|
||||
}
|
||||
|
||||
@@ -108,10 +108,10 @@ func SearchAll(entrypoint, dir string, possibleFilenames []string) ([]string, er
|
||||
}
|
||||
}
|
||||
paths, err := SearchNPathRecursively(dir, possibleFilenames, -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(entrypoints, paths...), nil
|
||||
// The call to SearchNPathRecursively is ambiguous and may return
|
||||
// os.ErrPermission if its search ends, however it may have still
|
||||
// returned valid paths. Caller may choose to ignore that error.
|
||||
return append(entrypoints, paths...), err
|
||||
}
|
||||
|
||||
// SearchPath will check if a file at the given path exists or not. If it does,
|
||||
@@ -153,13 +153,16 @@ func SearchPath(path string, possibleFilenames []string) (string, error) {
|
||||
// also check if the user ID of the directory changes and abort if it does.
|
||||
func SearchPathRecursively(path string, possibleFilenames []string) (string, error) {
|
||||
paths, err := SearchNPathRecursively(path, possibleFilenames, 1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if len(paths) > 0 {
|
||||
// Regardless of the error, return the first possible filename.
|
||||
return paths[0], nil
|
||||
} else {
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
}
|
||||
if len(paths) == 0 {
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
return paths[0], nil
|
||||
}
|
||||
|
||||
// SearchNPathRecursively walks up the directory tree starting at the given
|
||||
@@ -188,10 +191,13 @@ func SearchNPathRecursively(path string, possibleFilenames []string, n int) ([]s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Error if we reached the root directory and still haven't found a file
|
||||
// OR if the user id of the directory changes
|
||||
if path == parentPath || (parentOwner != owner) {
|
||||
// If the user id of the directory changes indicate a permission error, otherwise
|
||||
// the calling code will infer an error condition based on the accumulated
|
||||
// contents of paths.
|
||||
if path == parentPath {
|
||||
return paths, nil
|
||||
} else if parentOwner != owner {
|
||||
return paths, os.ErrPermission
|
||||
}
|
||||
|
||||
owner = parentOwner
|
||||
|
||||
201
internal/gitignore/LICENSE
Normal file
201
internal/gitignore/LICENSE
Normal 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.
|
||||
34
internal/gitignore/matcher.go
Normal file
34
internal/gitignore/matcher.go
Normal 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
|
||||
}
|
||||
23
internal/gitignore/matcher_test.go
Normal file
23
internal/gitignore/matcher_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
165
internal/gitignore/pattern.go
Normal file
165
internal/gitignore/pattern.go
Normal 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
|
||||
}
|
||||
80
internal/gitignore/pattern_test.go
Normal file
80
internal/gitignore/pattern_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func formatMap(m map[string]any, indent int) string {
|
||||
spaces := strings.Repeat(" ", indent)
|
||||
|
||||
for k, v := range m {
|
||||
result.WriteString(fmt.Sprintf("%s%s: %v\n", spaces, k, v))
|
||||
result.WriteString(fmt.Sprintf("%s%s: %v\n", spaces, k, v)) //nolint:staticcheck
|
||||
}
|
||||
|
||||
return result.String()
|
||||
@@ -247,15 +247,17 @@ func printTaskRequires(l *logger.Logger, t *ast.Task) {
|
||||
l.Outf(logger.Default, " vars:\n")
|
||||
|
||||
for _, v := range t.Requires.Vars {
|
||||
// If the variable has enum constraints, format accordingly
|
||||
if len(v.Enum) > 0 {
|
||||
if v.Enum != nil && len(v.Enum.Value) > 0 {
|
||||
l.Outf(logger.Yellow, " - %s:\n", v.Name)
|
||||
l.Outf(logger.Yellow, " enum:\n")
|
||||
for _, enumValue := range v.Enum {
|
||||
for _, enumValue := range v.Enum.Value {
|
||||
l.Outf(logger.Yellow, " - %s\n", enumValue)
|
||||
}
|
||||
} else if v.Enum != nil && v.Enum.Ref != "" {
|
||||
l.Outf(logger.Yellow, " - %s:\n", v.Name)
|
||||
l.Outf(logger.Yellow, " enum:\n")
|
||||
l.Outf(logger.Yellow, " ref: %s\n", v.Enum.Ref)
|
||||
} else {
|
||||
// Simple required variable
|
||||
l.Outf(logger.Yellow, " - %s\n", v.Name)
|
||||
}
|
||||
}
|
||||
@@ -283,7 +285,9 @@ func isEnvVar(key string, envVars map[string]bool) bool {
|
||||
key == "TASKFILE_DIR" ||
|
||||
key == "USER_WORKING_DIR" ||
|
||||
key == "ALIAS" ||
|
||||
key == "MATCH" {
|
||||
key == "MATCH" ||
|
||||
key == "PATH_LIST_SEPARATOR" ||
|
||||
key == "FILE_PATH_SEPARATOR" {
|
||||
return true
|
||||
}
|
||||
return envVars[key]
|
||||
|
||||
@@ -3,13 +3,15 @@ package templater
|
||||
import (
|
||||
"maps"
|
||||
"math/rand/v2"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/google/uuid"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
|
||||
@@ -21,8 +23,8 @@ var templateFuncs template.FuncMap
|
||||
|
||||
func init() {
|
||||
taskFuncs := template.FuncMap{
|
||||
"OS": os,
|
||||
"ARCH": arch,
|
||||
"OS": goos,
|
||||
"ARCH": goarch,
|
||||
"numCPU": runtime.NumCPU,
|
||||
"catLines": catLines,
|
||||
"splitLines": splitLines,
|
||||
@@ -33,7 +35,10 @@ func init() {
|
||||
"splitArgs": splitArgs,
|
||||
"IsSH": IsSH, // Deprecated
|
||||
"joinPath": filepath.Join,
|
||||
"joinEnv": joinEnv,
|
||||
"joinUrl": joinUrl,
|
||||
"relPath": filepath.Rel,
|
||||
"absPath": filepath.Abs,
|
||||
"merge": merge,
|
||||
"spew": spew.Sdump,
|
||||
"fromYaml": fromYaml,
|
||||
@@ -56,11 +61,11 @@ func init() {
|
||||
maps.Copy(templateFuncs, taskFuncs)
|
||||
}
|
||||
|
||||
func os() string {
|
||||
func goos() string {
|
||||
return runtime.GOOS
|
||||
}
|
||||
|
||||
func arch() string {
|
||||
func goarch() string {
|
||||
return runtime.GOARCH
|
||||
}
|
||||
|
||||
@@ -94,6 +99,14 @@ func IsSH() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func joinEnv(elem ...string) string {
|
||||
return strings.Join(elem, string(os.PathListSeparator))
|
||||
}
|
||||
|
||||
func joinUrl(elem ...string) string {
|
||||
return path.Join(elem...)
|
||||
}
|
||||
|
||||
func merge(base map[string]any, v ...map[string]any) map[string]any {
|
||||
cap := len(v)
|
||||
for _, m := range v {
|
||||
|
||||
@@ -68,6 +68,13 @@ func ReplaceWithExtra[T any](v T, cache *Cache, extra map[string]any) T {
|
||||
return v
|
||||
}
|
||||
|
||||
// Optimization: skip if string is not a template
|
||||
if s, ok := any(v).(string); ok {
|
||||
if !strings.Contains(s, "{{") {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the cache map if it's not already initialized
|
||||
if cache.cacheMap == nil {
|
||||
cache.cacheMap = cache.Vars.ToCacheMap()
|
||||
@@ -82,6 +89,10 @@ func ReplaceWithExtra[T any](v T, cache *Cache, extra map[string]any) T {
|
||||
|
||||
// Traverse the value and parse any template variables
|
||||
copy, err := deepcopy.TraverseStringsFunc(v, func(v string) (string, error) {
|
||||
// Optimization: skip if string is not a template
|
||||
if !strings.Contains(v, "{{") {
|
||||
return v, nil
|
||||
}
|
||||
tpl, err := template.New("").Funcs(templateFuncs).Parse(v)
|
||||
if err != nil {
|
||||
return v, err
|
||||
|
||||
@@ -7,5 +7,5 @@ import (
|
||||
)
|
||||
|
||||
func IsTerminal() bool {
|
||||
return term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd()))
|
||||
return term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd())) //nolint:gosec
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.47.0
|
||||
3.51.1
|
||||
|
||||
12
mise.toml
Normal file
12
mise.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[tools]
|
||||
# Runtimes
|
||||
go = "1.25.10"
|
||||
node = "24"
|
||||
pnpm = "11.5.0"
|
||||
|
||||
# Dev tools
|
||||
golangci-lint = "2.12.2"
|
||||
mockery = "3.2.2"
|
||||
gotestsum = "latest"
|
||||
goreleaser = "2"
|
||||
"go:golang.org/x/exp/cmd/gorelease" = "latest"
|
||||
18
requires.go
18
requires.go
@@ -81,7 +81,7 @@ func (e *Executor) promptDepsVars(calls []*Call) error {
|
||||
e.promptedVars = ast.NewVars()
|
||||
|
||||
for _, v := range varsMap {
|
||||
value, err := prompter.Prompt(v.Name, v.Enum)
|
||||
value, err := prompter.Prompt(v.Name, getEnumValues(v.Enum))
|
||||
if err != nil {
|
||||
if errors.Is(err, input.ErrCancelled) {
|
||||
return &errors.TaskCancelledByUserError{TaskName: "interactive prompt"}
|
||||
@@ -120,7 +120,7 @@ func (e *Executor) promptTaskVars(t *ast.Task, call *Call) (bool, error) {
|
||||
prompter := e.newPrompter()
|
||||
|
||||
for _, v := range missing {
|
||||
value, err := prompter.Prompt(v.Name, v.Enum)
|
||||
value, err := prompter.Prompt(v.Name, getEnumValues(v.Enum))
|
||||
if err != nil {
|
||||
if errors.Is(err, input.ErrCancelled) {
|
||||
return false, &errors.TaskCancelledByUserError{TaskName: t.Name()}
|
||||
@@ -168,7 +168,7 @@ func (e *Executor) areTaskRequiredVarsSet(t *ast.Task) error {
|
||||
for i, v := range missing {
|
||||
missingVars[i] = errors.MissingVar{
|
||||
Name: v.Name,
|
||||
AllowedValues: v.Enum,
|
||||
AllowedValues: getEnumValues(v.Enum),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,11 +187,12 @@ func (e *Executor) areTaskRequiredVarsAllowedValuesSet(t *ast.Task) error {
|
||||
for _, requiredVar := range t.Requires.Vars {
|
||||
varValue, _ := t.Vars.Get(requiredVar.Name)
|
||||
|
||||
enumValues := getEnumValues(requiredVar.Enum)
|
||||
value, isString := varValue.Value.(string)
|
||||
if isString && requiredVar.Enum != nil && !slices.Contains(requiredVar.Enum, value) {
|
||||
if isString && len(enumValues) > 0 && !slices.Contains(enumValues, value) {
|
||||
notAllowedValuesVars = append(notAllowedValuesVars, errors.NotAllowedVar{
|
||||
Value: value,
|
||||
Enum: requiredVar.Enum,
|
||||
Enum: enumValues,
|
||||
Name: requiredVar.Name,
|
||||
})
|
||||
}
|
||||
@@ -206,3 +207,10 @@ func (e *Executor) areTaskRequiredVarsAllowedValuesSet(t *ast.Task) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEnumValues(e *ast.Enum) []string {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.Value
|
||||
}
|
||||
|
||||
11
setup.go
11
setup.go
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/fsext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/internal/version"
|
||||
@@ -60,12 +59,10 @@ func (e *Executor) getRootNode() (taskfile.Node, error) {
|
||||
taskfile.WithCert(e.Cert),
|
||||
taskfile.WithCertKey(e.CertKey),
|
||||
)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.TaskfileNotFoundError{
|
||||
URI: fsext.DefaultDir(e.Entrypoint, e.Dir),
|
||||
Walk: true,
|
||||
AskInit: true,
|
||||
}
|
||||
var taskNotFoundError errors.TaskfileNotFoundError
|
||||
if errors.As(err, &taskNotFoundError) {
|
||||
taskNotFoundError.AskInit = true
|
||||
return nil, taskNotFoundError
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -15,7 +15,7 @@ const maxInterruptSignals = 3
|
||||
// time to do cleanup work.
|
||||
func (e *Executor) InterceptInterruptSignals() {
|
||||
ch := make(chan os.Signal, maxInterruptSignals)
|
||||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
||||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
|
||||
|
||||
go func() {
|
||||
for i := range maxInterruptSignals {
|
||||
|
||||
21
task.go
21
task.go
@@ -148,6 +148,20 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check required vars early (before template compilation) if we can't prompt.
|
||||
// This gives a clear "missing required variables" error instead of a template error.
|
||||
if !e.canPrompt() {
|
||||
if err := e.areTaskRequiredVarsSet(t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
t, err = e.CompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if condition after CompiledTask so dynamic variables are resolved
|
||||
if strings.TrimSpace(t.If) != "" {
|
||||
if err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: t.If,
|
||||
@@ -159,7 +173,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Prompt for missing required vars (just-in-time for sequential task calls)
|
||||
// Prompt for missing required vars after if check (avoid prompting if task won't run)
|
||||
prompted, err := e.promptTaskVars(t, call)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -176,11 +190,6 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
return err
|
||||
}
|
||||
|
||||
t, err = e.CompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.areTaskRequiredVarsAllowedValuesSet(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
278
task_test.go
278
task_test.go
@@ -88,13 +88,14 @@ func (tt *TaskTest) writeFixture(
|
||||
if tt.fixtureTemplatingEnabled {
|
||||
fixtureTemplateData := map[string]any{
|
||||
"TEST_NAME": t.Name(),
|
||||
"TEST_DIR": wd,
|
||||
"TEST_DIR": filepath.ToSlash(wd),
|
||||
}
|
||||
// If the test has additional template data, copy it into the map
|
||||
if tt.fixtureTemplateData != nil {
|
||||
maps.Copy(fixtureTemplateData, tt.fixtureTemplateData)
|
||||
}
|
||||
g.AssertWithTemplate(t, goldenFileName, fixtureTemplateData, b)
|
||||
// Normalize output before comparison (CRLF→LF, backslash→forward slash)
|
||||
g.AssertWithTemplate(t, goldenFileName, fixtureTemplateData, normalizeOutput(b))
|
||||
} else {
|
||||
g.Assert(t, goldenFileName, b)
|
||||
}
|
||||
@@ -308,6 +309,73 @@ func PPSortedLines(t *testing.T, b []byte) []byte {
|
||||
return []byte(strings.Join(lines, "\n") + "\n")
|
||||
}
|
||||
|
||||
// normalizeOutput normalizes cross-platform differences for byte slice comparison:
|
||||
// - Converts CRLF and CR to LF (line endings)
|
||||
// - Converts backslashes to forward slashes (Windows paths)
|
||||
// - Handles escaped backslashes in JSON (\\) by converting to single forward slash
|
||||
func normalizeOutput(b []byte) []byte {
|
||||
b = bytes.ReplaceAll(b, []byte("\r\n"), []byte("\n"))
|
||||
b = bytes.ReplaceAll(b, []byte("\r"), []byte("\n"))
|
||||
// First replace escaped backslashes (common in JSON), then single backslashes
|
||||
b = bytes.ReplaceAll(b, []byte("\\\\"), []byte("/"))
|
||||
b = bytes.ReplaceAll(b, []byte("\\"), []byte("/"))
|
||||
return b
|
||||
}
|
||||
|
||||
// normalizePathSeparators converts backslashes to forward slashes for cross-platform path comparison.
|
||||
func normalizePathSeparators(s string) string {
|
||||
return strings.ReplaceAll(s, "\\", "/")
|
||||
}
|
||||
|
||||
// NormalizedEqual compares two byte slices after normalizing output.
|
||||
// This is used as a custom goldie.EqualFn for cross-platform golden file tests.
|
||||
func NormalizedEqual(actual, expected []byte) bool {
|
||||
return bytes.Equal(normalizeOutput(actual), normalizeOutput(expected))
|
||||
}
|
||||
|
||||
func TestNormalizeOutput(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expected []byte
|
||||
}{
|
||||
{"CRLF to LF", []byte("line1\r\nline2\r\n"), []byte("line1\nline2\n")},
|
||||
{"CR to LF", []byte("line1\rline2\r"), []byte("line1\nline2\n")},
|
||||
{"Windows path", []byte(`D:\a\task\task`), []byte(`D:/a/task/task`)},
|
||||
{"JSON escaped backslash", []byte(`{"path":"D:\\a\\task"}`), []byte(`{"path":"D:/a/task"}`)},
|
||||
{"Mixed", []byte("D:\\a\\task\r\n"), []byte("D:/a/task\n")},
|
||||
{"Unix path unchanged", []byte("/home/user/task\n"), []byte("/home/user/task\n")},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := normalizeOutput(tt.input)
|
||||
assert.Equal(t, tt.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePathSeparators(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"Windows path", `D:\a\task\task`, `D:/a/task/task`},
|
||||
{"Unix path unchanged", `/home/user/task`, `/home/user/task`},
|
||||
{"Mixed separators", `C:\Users/name\file`, `C:/Users/name/file`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := normalizePathSeparators(tt.input)
|
||||
assert.Equal(t, tt.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SyncBuffer is a threadsafe buffer for testing.
|
||||
// Some times replace stdout/stderr with a buffer to capture output.
|
||||
// stdout and stderr are threadsafe, but a regular bytes.Buffer is not.
|
||||
@@ -487,6 +555,153 @@ func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in
|
||||
}
|
||||
}
|
||||
|
||||
// TestStatusTimestamp is a regression test for https://github.com/go-task/task/issues/1230.
|
||||
// When using method: timestamp, deleting a generated file should cause the task to re-run,
|
||||
// not be skipped because the timestamp file is still present.
|
||||
func TestStatusTimestamp(t *testing.T) { // nolint:paralleltest // cannot run in parallel
|
||||
const dir = "testdata/timestamp"
|
||||
|
||||
generatedFile := filepathext.SmartJoin(dir, "generated.txt")
|
||||
tempDir := task.TempDir{
|
||||
Remote: filepathext.SmartJoin(dir, ".task"),
|
||||
Fingerprint: filepathext.SmartJoin(dir, ".task"),
|
||||
}
|
||||
|
||||
// Clean up any state from previous runs.
|
||||
_ = os.Remove(generatedFile)
|
||||
_ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.NewExecutor(
|
||||
task.WithDir(dir),
|
||||
task.WithStdout(&buff),
|
||||
task.WithStderr(&buff),
|
||||
task.WithTempDir(tempDir),
|
||||
)
|
||||
require.NoError(t, e.Setup())
|
||||
|
||||
// First run: task should execute and create generated.txt.
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||
_, err := os.Stat(generatedFile)
|
||||
require.NoError(t, err, "generated.txt should exist after first run")
|
||||
buff.Reset()
|
||||
|
||||
// Second run: task should be up to date.
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||
buff.Reset()
|
||||
|
||||
// Delete the generated file (simulate a clean), but leave the timestamp file.
|
||||
require.NoError(t, os.Remove(generatedFile))
|
||||
_, err = os.Stat(generatedFile)
|
||||
require.Error(t, err, "generated.txt should be gone")
|
||||
|
||||
// Third run: task MUST re-run because generated.txt is missing.
|
||||
// This is the regression: previously the task was incorrectly skipped.
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||
assert.NotContains(t, buff.String(), "is up to date", "task should re-run when generated file is missing")
|
||||
_, err = os.Stat(generatedFile)
|
||||
require.NoError(t, err, "generated.txt should be recreated after third run")
|
||||
}
|
||||
|
||||
// TestStatusChecksumMissingGenerated is a regression test for https://github.com/go-task/task/issues/1230.
|
||||
// When using method: checksum, deleting a generated file should cause the task to re-run,
|
||||
// not be skipped because the checksum file still matches.
|
||||
func TestStatusChecksumMissingGenerated(t *testing.T) { // nolint:paralleltest // cannot run in parallel
|
||||
const dir = "testdata/checksum"
|
||||
|
||||
generatedFile := filepathext.SmartJoin(dir, "generated.txt")
|
||||
tempDir := task.TempDir{
|
||||
Remote: filepathext.SmartJoin(dir, ".task"),
|
||||
Fingerprint: filepathext.SmartJoin(dir, ".task"),
|
||||
}
|
||||
|
||||
// Clean up any state from previous runs.
|
||||
_ = os.Remove(generatedFile)
|
||||
_ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.NewExecutor(
|
||||
task.WithDir(dir),
|
||||
task.WithStdout(&buff),
|
||||
task.WithStderr(&buff),
|
||||
task.WithTempDir(tempDir),
|
||||
)
|
||||
require.NoError(t, e.Setup())
|
||||
|
||||
// First run: task should execute and create generated.txt.
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||
_, err := os.Stat(generatedFile)
|
||||
require.NoError(t, err, "generated.txt should exist after first run")
|
||||
buff.Reset()
|
||||
|
||||
// Second run: task should be up to date.
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||
buff.Reset()
|
||||
|
||||
// Delete the generated file (simulate a clean), but leave the checksum file.
|
||||
require.NoError(t, os.Remove(generatedFile))
|
||||
_, err = os.Stat(generatedFile)
|
||||
require.Error(t, err, "generated.txt should be gone")
|
||||
|
||||
// Third run: task MUST re-run because generated.txt is missing.
|
||||
// This is the regression: previously the task was incorrectly skipped.
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||
assert.NotContains(t, buff.String(), "is up to date", "task should re-run when generated file is missing")
|
||||
_, err = os.Stat(generatedFile)
|
||||
require.NoError(t, err, "generated.txt should be recreated after third run")
|
||||
}
|
||||
|
||||
func TestGitignoreChecksum(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||
const dir = "testdata/gitignore"
|
||||
|
||||
// Clean up
|
||||
_ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
||||
_ = os.Remove(filepathext.SmartJoin(dir, "generated.txt"))
|
||||
|
||||
var buff bytes.Buffer
|
||||
tempDir := task.TempDir{
|
||||
Remote: filepathext.SmartJoin(dir, ".task"),
|
||||
Fingerprint: filepathext.SmartJoin(dir, ".task"),
|
||||
}
|
||||
e := task.NewExecutor(
|
||||
task.WithDir(dir),
|
||||
task.WithStdout(&buff),
|
||||
task.WithStderr(&buff),
|
||||
task.WithTempDir(tempDir),
|
||||
)
|
||||
require.NoError(t, e.Setup())
|
||||
|
||||
// First run - should execute
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||
|
||||
// Second run - should be up to date
|
||||
buff.Reset()
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||
assert.Equal(t, "task: Task \"build\" is up to date\n", buff.String())
|
||||
|
||||
// Modify the ignored file - should still be up to date
|
||||
require.NoError(t, os.WriteFile(filepathext.SmartJoin(dir, "ignored.txt"), []byte("modified\n"), 0o644))
|
||||
buff.Reset()
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||
assert.Equal(t, "task: Task \"build\" is up to date\n", buff.String())
|
||||
|
||||
// Modify the source file - should re-execute
|
||||
require.NoError(t, os.WriteFile(filepathext.SmartJoin(dir, "source.txt"), []byte("modified source\n"), 0o644))
|
||||
buff.Reset()
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||
assert.NotEqual(t, "task: Task \"build\" is up to date\n", buff.String())
|
||||
|
||||
// Restore source file
|
||||
require.NoError(t, os.WriteFile(filepathext.SmartJoin(dir, "source.txt"), []byte("source content\n"), 0o644))
|
||||
require.NoError(t, os.WriteFile(filepathext.SmartJoin(dir, "ignored.txt"), []byte("ignored content\n"), 0o644))
|
||||
|
||||
// Clean up
|
||||
_ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
||||
_ = os.Remove(filepathext.SmartJoin(dir, "generated.txt"))
|
||||
}
|
||||
|
||||
func TestStatusVariables(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -856,7 +1071,7 @@ func TestIncludesRemote(t *testing.T) {
|
||||
|
||||
for k, taskCall := range taskCalls {
|
||||
t.Run(taskCall.Task, func(t *testing.T) {
|
||||
expectedContent := fmt.Sprint(rand.Int64())
|
||||
expectedContent := fmt.Sprint(rand.Int64()) //nolint:gosec
|
||||
t.Setenv("CONTENT", expectedContent)
|
||||
|
||||
outputFile := fmt.Sprintf("%d.%d.txt", i, k)
|
||||
@@ -1078,7 +1293,7 @@ func TestIncludesOptionalImplicitFalse(t *testing.T) {
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
message := "task: No Taskfile found at \"%s/%s/TaskfileOptional.yml\""
|
||||
expected := fmt.Sprintf(message, wd, dir)
|
||||
expected := fmt.Sprintf(message, filepath.ToSlash(wd), dir)
|
||||
|
||||
e := task.NewExecutor(
|
||||
task.WithDir(dir),
|
||||
@@ -1098,7 +1313,7 @@ func TestIncludesOptionalExplicitFalse(t *testing.T) {
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
message := "task: No Taskfile found at \"%s/%s/TaskfileOptional.yml\""
|
||||
expected := fmt.Sprintf(message, wd, dir)
|
||||
expected := fmt.Sprintf(message, filepath.ToSlash(wd), dir)
|
||||
|
||||
e := task.NewExecutor(
|
||||
task.WithDir(dir),
|
||||
@@ -1146,11 +1361,11 @@ func TestIncludesRelativePath(t *testing.T) {
|
||||
require.NoError(t, e.Setup())
|
||||
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "common:pwd"}))
|
||||
assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
|
||||
assert.Contains(t, filepath.ToSlash(buff.String()), "testdata/includes_rel_path/common")
|
||||
|
||||
buff.Reset()
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "included:common:pwd"}))
|
||||
assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
|
||||
assert.Contains(t, filepath.ToSlash(buff.String()), "testdata/includes_rel_path/common")
|
||||
}
|
||||
|
||||
func TestIncludesInternal(t *testing.T) {
|
||||
@@ -1328,7 +1543,7 @@ func TestIncludedTaskfileVarMerging(t *testing.T) {
|
||||
|
||||
err := e.Run(t.Context(), &task.Call{Task: test.task})
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, buff.String(), test.expectedOutput)
|
||||
assert.Contains(t, filepath.ToSlash(buff.String()), test.expectedOutput)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1475,7 +1690,9 @@ func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "whereami"}))
|
||||
|
||||
// got should be the "dir" part of "testdata/dir"
|
||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||
// Normalize path separators for cross-platform compatibility (Windows uses backslashes)
|
||||
normalized := normalizePathSeparators(out.String())
|
||||
got := strings.TrimSuffix(filepath.Base(normalized), "\n")
|
||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||
}
|
||||
|
||||
@@ -1494,7 +1711,9 @@ func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
|
||||
require.NoError(t, e.Setup())
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "whereami"}))
|
||||
|
||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||
// Normalize path separators for cross-platform compatibility (Windows uses backslashes)
|
||||
normalized := normalizePathSeparators(out.String())
|
||||
got := strings.TrimSuffix(filepath.Base(normalized), "\n")
|
||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||
}
|
||||
|
||||
@@ -1520,7 +1739,9 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
|
||||
require.NoError(t, e.Setup())
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: target}))
|
||||
|
||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||
// Normalize path separators for cross-platform compatibility (Windows uses backslashes)
|
||||
normalized := normalizePathSeparators(out.String())
|
||||
got := strings.TrimSuffix(filepath.Base(normalized), "\n")
|
||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||
|
||||
// Clean-up after ourselves only if no error.
|
||||
@@ -1549,7 +1770,11 @@ func TestDynamicVariablesRunOnTheNewCreatedDir(t *testing.T) {
|
||||
require.NoError(t, e.Setup())
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: target}))
|
||||
|
||||
got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
|
||||
// Normalize path separators for cross-platform compatibility (Windows uses backslashes)
|
||||
// Take only the first line as Windows may output additional debug info
|
||||
normalized := normalizePathSeparators(out.String())
|
||||
firstLine := strings.Split(normalized, "\n")[0]
|
||||
got := filepath.Base(firstLine)
|
||||
assert.Equal(t, expected, got, "Mismatch in the working directory")
|
||||
|
||||
// Clean-up after ourselves only if no error.
|
||||
@@ -2268,7 +2493,8 @@ func TestUserWorkingDirectory(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, e.Setup())
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"}))
|
||||
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
|
||||
// Use filepath.ToSlash because USER_WORKING_DIR uses forward slashes on all platforms
|
||||
assert.Equal(t, fmt.Sprintf("%s\n", filepath.ToSlash(wd)), buff.String())
|
||||
}
|
||||
|
||||
func TestUserWorkingDirectoryWithIncluded(t *testing.T) {
|
||||
@@ -2277,7 +2503,7 @@ func TestUserWorkingDirectoryWithIncluded(t *testing.T) {
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
wd = filepathext.SmartJoin(wd, "testdata/user_working_dir_with_includes/somedir")
|
||||
wd = filepath.ToSlash(filepathext.SmartJoin(wd, "testdata/user_working_dir_with_includes/somedir"))
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.NewExecutor(
|
||||
@@ -2290,7 +2516,8 @@ func TestUserWorkingDirectoryWithIncluded(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, e.Setup())
|
||||
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "included:echo"}))
|
||||
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
|
||||
// Normalize path separators for cross-platform compatibility (Windows uses backslashes)
|
||||
assert.Equal(t, fmt.Sprintf("%s\n", wd), normalizePathSeparators(buff.String()))
|
||||
}
|
||||
|
||||
func TestPlatforms(t *testing.T) {
|
||||
@@ -2423,6 +2650,27 @@ func TestSplitArgs(t *testing.T) {
|
||||
assert.Equal(t, "3\n", buff.String())
|
||||
}
|
||||
|
||||
func TestAbsPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.NewExecutor(
|
||||
task.WithDir("testdata/abs_path"),
|
||||
task.WithStdout(&buff),
|
||||
task.WithStderr(&buff),
|
||||
task.WithSilent(true),
|
||||
)
|
||||
require.NoError(t, e.Setup())
|
||||
|
||||
err := e.Run(t.Context(), &task.Call{Task: "default"})
|
||||
require.NoError(t, err)
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
expected := filepath.Join(cwd, "bar") + "\n"
|
||||
assert.Equal(t, expected, buff.String())
|
||||
}
|
||||
|
||||
func TestSingleCmdDep(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"iter"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/goext"
|
||||
|
||||
@@ -3,7 +3,7 @@ package ast
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
@@ -22,9 +22,56 @@ func (r *Requires) DeepCopy() *Requires {
|
||||
}
|
||||
}
|
||||
|
||||
// Enum represents an enum constraint for a required variable.
|
||||
// It can either be a static list of values or a reference to another variable.
|
||||
type Enum struct {
|
||||
Ref string
|
||||
Value []string
|
||||
}
|
||||
|
||||
func (e *Enum) DeepCopy() *Enum {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return &Enum{
|
||||
Ref: e.Ref,
|
||||
Value: deepcopy.Slice(e.Value),
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
func (e *Enum) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
case yaml.SequenceNode:
|
||||
// Static list of values: enum: ["a", "b"]
|
||||
var values []string
|
||||
if err := node.Decode(&values); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
e.Value = values
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
// Reference to another variable: enum: { ref: .VAR }
|
||||
var refStruct struct {
|
||||
Ref string
|
||||
}
|
||||
if err := node.Decode(&refStruct); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
if refStruct.Ref == "" {
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("enum")
|
||||
}
|
||||
e.Ref = refStruct.Ref
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("enum")
|
||||
}
|
||||
|
||||
type VarsWithValidation struct {
|
||||
Name string
|
||||
Enum []string
|
||||
Enum *Enum
|
||||
}
|
||||
|
||||
func (v *VarsWithValidation) DeepCopy() *VarsWithValidation {
|
||||
@@ -33,7 +80,7 @@ func (v *VarsWithValidation) DeepCopy() *VarsWithValidation {
|
||||
}
|
||||
return &VarsWithValidation{
|
||||
Name: v.Name,
|
||||
Enum: v.Enum,
|
||||
Enum: v.Enum.DeepCopy(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +100,7 @@ func (v *VarsWithValidation) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.MappingNode:
|
||||
var vv struct {
|
||||
Name string
|
||||
Enum []string
|
||||
Enum *Enum
|
||||
}
|
||||
if err := node.Decode(&vv); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
@@ -38,6 +38,7 @@ type Task struct {
|
||||
Method string
|
||||
Prefix string `hash:"ignore"`
|
||||
IgnoreError bool
|
||||
UseGitignore *bool
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
If string
|
||||
@@ -75,6 +76,12 @@ func (t *Task) IsSilent() bool {
|
||||
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.
|
||||
func (t *Task) WildcardMatch(name string) (bool, []string) {
|
||||
names := append([]string{t.Task}, t.Aliases...)
|
||||
@@ -149,7 +156,8 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
UseGitignore *bool `yaml:"use_gitignore,omitempty"`
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
If string
|
||||
@@ -190,6 +198,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
t.Method = task.Method
|
||||
t.Prefix = task.Prefix
|
||||
t.IgnoreError = task.IgnoreError
|
||||
t.UseGitignore = deepcopy.Scalar(task.UseGitignore)
|
||||
t.Run = task.Run
|
||||
t.Platforms = task.Platforms
|
||||
t.If = task.If
|
||||
@@ -233,6 +242,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
Method: t.Method,
|
||||
Prefix: t.Prefix,
|
||||
IgnoreError: t.IgnoreError,
|
||||
UseGitignore: deepcopy.Scalar(t.UseGitignore),
|
||||
Run: t.Run,
|
||||
IncludeVars: t.IncludeVars.DeepCopy(),
|
||||
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
||||
@@ -242,6 +252,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
Requires: t.Requires.DeepCopy(),
|
||||
Namespace: t.Namespace,
|
||||
FullName: t.FullName,
|
||||
Watch: t.Watch,
|
||||
Failfast: t.Failfast,
|
||||
}
|
||||
return c
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
@@ -20,20 +20,21 @@ var ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles c
|
||||
|
||||
// Taskfile is the abstract syntax tree for a Taskfile
|
||||
type Taskfile struct {
|
||||
Location string
|
||||
Version *semver.Version
|
||||
Output Output
|
||||
Method string
|
||||
Includes *Includes
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks *Tasks
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval time.Duration
|
||||
Location string
|
||||
Version *semver.Version
|
||||
Output Output
|
||||
Method string
|
||||
Includes *Includes
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks *Tasks
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval time.Duration
|
||||
UseGitignore bool `yaml:"use_gitignore"`
|
||||
}
|
||||
|
||||
// Merge merges the second Taskfile into the first
|
||||
@@ -76,19 +77,20 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
var taskfile struct {
|
||||
Version *semver.Version
|
||||
Output Output
|
||||
Method string
|
||||
Includes *Includes
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks *Tasks
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval time.Duration
|
||||
Version *semver.Version
|
||||
Output Output
|
||||
Method string
|
||||
Includes *Includes
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks *Tasks
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval time.Duration
|
||||
UseGitignore bool `yaml:"use_gitignore"`
|
||||
}
|
||||
if err := node.Decode(&taskfile); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
@@ -106,6 +108,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
tf.Dotenv = taskfile.Dotenv
|
||||
tf.Run = taskfile.Run
|
||||
tf.Interval = taskfile.Interval
|
||||
tf.UseGitignore = taskfile.UseGitignore
|
||||
if tf.Includes == nil {
|
||||
tf.Includes = NewIncludes()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
|
||||
@@ -93,8 +93,8 @@ func getScheme(uri string) (string, error) {
|
||||
return "git", nil
|
||||
}
|
||||
|
||||
if i := strings.Index(uri, "://"); i != -1 {
|
||||
return uri[:i], nil
|
||||
if before, _, ok := strings.Cut(uri, "://"); ok {
|
||||
return before, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
|
||||
@@ -22,7 +22,13 @@ func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error)
|
||||
resolvedEntrypoint, err := fsext.Search(entrypoint, dir, DefaultTaskfiles)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, errors.TaskfileNotFoundError{URI: entrypoint, Walk: false}
|
||||
if entrypoint == "" {
|
||||
return nil, errors.TaskfileNotFoundError{URI: entrypoint, Walk: true}
|
||||
} else {
|
||||
return nil, errors.TaskfileNotFoundError{URI: entrypoint, Walk: false}
|
||||
}
|
||||
} else if errors.Is(err, os.ErrPermission) {
|
||||
return nil, errors.TaskfileNotFoundError{URI: entrypoint, Walk: true, OwnerChange: true}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -127,19 +128,19 @@ func (node *GitNode) getOrCloneRepo(ctx context.Context) (string, error) {
|
||||
repoMutex.Lock()
|
||||
defer repoMutex.Unlock()
|
||||
|
||||
// Check if context was cancelled while waiting for lock
|
||||
if err := ctx.Err(); err != nil {
|
||||
return "", fmt.Errorf("context cancelled while waiting for repository lock: %w", err)
|
||||
}
|
||||
|
||||
cacheDir := filepath.Join(os.TempDir(), "task-git-repos", cacheKey)
|
||||
|
||||
// check if repo is already cached (under the lock)
|
||||
// Check cache FIRST - if already cloned, no network needed, timeout irrelevant
|
||||
gitDir := filepath.Join(cacheDir, ".git")
|
||||
if _, err := os.Stat(gitDir); err == nil {
|
||||
return cacheDir, nil
|
||||
}
|
||||
|
||||
// Only check context if we need to clone (requires network)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return "", fmt.Errorf("context cancelled while waiting for repository lock: %w", err)
|
||||
}
|
||||
|
||||
getterURL := node.buildURL()
|
||||
|
||||
client := &getter.Client{
|
||||
@@ -191,8 +192,8 @@ func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) {
|
||||
return entrypoint, nil
|
||||
}
|
||||
|
||||
dir, _ := filepath.Split(node.path)
|
||||
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.url, filepath.Join(dir, entrypoint))
|
||||
dir, _ := path.Split(node.path)
|
||||
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.url, path.Join(dir, entrypoint))
|
||||
if node.ref != "" {
|
||||
return fmt.Sprintf("%s?ref=%s", resolvedEntrypoint, node.ref), nil
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func buildHTTPClient(insecure bool, caCert, cert, certKey string) (*http.Client,
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: insecure,
|
||||
InsecureSkipVerify: insecure, //nolint:gosec
|
||||
}
|
||||
|
||||
// Load custom CA certificate if provided
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dominikbraun/graph"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
@@ -285,12 +286,7 @@ func (r *Reader) isTrusted(uri string) bool {
|
||||
host := parsedURL.Host
|
||||
|
||||
// Check against each trusted pattern (exact match including port if provided)
|
||||
for _, pattern := range r.trustedHosts {
|
||||
if host == pattern {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(r.trustedHosts, host)
|
||||
}
|
||||
|
||||
func (r *Reader) include(ctx context.Context, node Node) error {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
type TaskRC struct {
|
||||
Version *semver.Version `yaml:"version"`
|
||||
Verbose *bool `yaml:"verbose"`
|
||||
Silent *bool `yaml:"silent"`
|
||||
Color *bool `yaml:"color"`
|
||||
DisableFuzzy *bool `yaml:"disable-fuzzy"`
|
||||
Concurrency *int `yaml:"concurrency"`
|
||||
@@ -63,6 +64,7 @@ func (t *TaskRC) Merge(other *TaskRC) {
|
||||
t.Remote.CertKey = cmp.Or(other.Remote.CertKey, t.Remote.CertKey)
|
||||
|
||||
t.Verbose = cmp.Or(other.Verbose, t.Verbose)
|
||||
t.Silent = cmp.Or(other.Silent, t.Silent)
|
||||
t.Color = cmp.Or(other.Color, t.Color)
|
||||
t.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy)
|
||||
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
|
||||
|
||||
@@ -3,7 +3,7 @@ package taskrc
|
||||
import (
|
||||
"os"
|
||||
|
||||
"go.yaml.in/yaml/v4"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
@@ -65,7 +65,7 @@ func (r *Reader) Read(node *Node) (*ast.TaskRC, error) {
|
||||
}
|
||||
|
||||
// Read the file
|
||||
b, err := os.ReadFile(node.entrypoint)
|
||||
b, err := os.ReadFile(node.entrypoint) //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/fsext"
|
||||
"github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
@@ -62,6 +63,9 @@ func GetConfig(dir string) (*ast.TaskRC, error) {
|
||||
return config, err
|
||||
}
|
||||
entrypoints, err := fsext.SearchAll("", absDir, defaultTaskRCs)
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
|
||||
6
testdata/abs_path/Taskfile.yml
vendored
Normal file
6
testdata/abs_path/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- cmd: echo '{{absPath "foo/../bar"}}'
|
||||
1
testdata/gitignore/.gitignore
vendored
Normal file
1
testdata/gitignore/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ignored.txt
|
||||
25
testdata/gitignore/Taskfile.yml
vendored
Normal file
25
testdata/gitignore/Taskfile.yml
vendored
Normal 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
1
testdata/gitignore/source.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
source content
|
||||
18
testdata/if/Taskfile.yml
vendored
18
testdata/if/Taskfile.yml
vendored
@@ -158,3 +158,21 @@ tasks:
|
||||
if: '{{ eq .ENV "dev" }}'
|
||||
cmds:
|
||||
- echo "should not appear"
|
||||
|
||||
# Task-level if with dynamic variable (condition met)
|
||||
task-if-dynamic-true:
|
||||
vars:
|
||||
ENABLE_FEATURE:
|
||||
sh: 'echo "true"'
|
||||
if: '{{ eq .ENABLE_FEATURE "true" }}'
|
||||
cmds:
|
||||
- echo "dynamic feature enabled"
|
||||
|
||||
# Task-level if with dynamic variable (condition not met)
|
||||
task-if-dynamic-false:
|
||||
vars:
|
||||
ENABLE_FEATURE:
|
||||
sh: 'echo "false"'
|
||||
if: '{{ eq .ENABLE_FEATURE "true" }}'
|
||||
cmds:
|
||||
- echo "should not appear"
|
||||
|
||||
2
testdata/if/testdata/TestIf-task-if-dynamic-false.golden
vendored
Normal file
2
testdata/if/testdata/TestIf-task-if-dynamic-false.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
task: dynamic variable: "echo \"false\"" result: "false"
|
||||
task: if condition not met - skipped: "task-if-dynamic-false"
|
||||
1
testdata/if/testdata/TestIf-task-if-dynamic-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-task-if-dynamic-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
dynamic feature enabled
|
||||
28
testdata/requires/Taskfile.yml
vendored
28
testdata/requires/Taskfile.yml
vendored
@@ -1,5 +1,9 @@
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
ALLOWED_ENVS: ["dev", "staging", "prod"]
|
||||
NOT_A_LIST: "this is a string"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
- task: missing-var
|
||||
@@ -41,3 +45,27 @@ tasks:
|
||||
{{range .MY_VAR | splitList " " }}
|
||||
echo {{.}}
|
||||
{{end}}
|
||||
|
||||
validation-var-ref:
|
||||
requires:
|
||||
vars:
|
||||
- name: ENV
|
||||
enum:
|
||||
ref: .ALLOWED_ENVS
|
||||
cmd: echo "{{.ENV}}"
|
||||
|
||||
validation-var-ref-invalid:
|
||||
requires:
|
||||
vars:
|
||||
- name: VALUE
|
||||
enum:
|
||||
ref: .NOT_A_LIST
|
||||
cmd: echo "{{.VALUE}}"
|
||||
|
||||
validation-var-ref-nonexistent:
|
||||
requires:
|
||||
vars:
|
||||
- name: ENV
|
||||
enum:
|
||||
ref: .NONEXISTENT_VAR
|
||||
cmd: echo "{{.ENV}}"
|
||||
|
||||
2
testdata/requires/testdata/TestRequires-enum_ref_-_fails_validation-err-run.golden
vendored
Normal file
2
testdata/requires/testdata/TestRequires-enum_ref_-_fails_validation-err-run.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
task: Task "validation-var-ref" cancelled because it is missing required variables:
|
||||
- ENV has an invalid value : 'invalid' (allowed values : [dev staging prod])
|
||||
0
testdata/requires/testdata/TestRequires-enum_ref_-_fails_validation.golden
vendored
Normal file
0
testdata/requires/testdata/TestRequires-enum_ref_-_fails_validation.golden
vendored
Normal file
2
testdata/requires/testdata/TestRequires-enum_ref_-_passes_validation.golden
vendored
Normal file
2
testdata/requires/testdata/TestRequires-enum_ref_-_passes_validation.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
task: [validation-var-ref] echo "dev"
|
||||
dev
|
||||
1
testdata/requires/testdata/TestRequires-enum_ref_-_ref_to_non-list-err-run.golden
vendored
Normal file
1
testdata/requires/testdata/TestRequires-enum_ref_-_ref_to_non-list-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
enum reference ".NOT_A_LIST" must resolve to a list
|
||||
0
testdata/requires/testdata/TestRequires-enum_ref_-_ref_to_non-list.golden
vendored
Normal file
0
testdata/requires/testdata/TestRequires-enum_ref_-_ref_to_non-list.golden
vendored
Normal file
1
testdata/requires/testdata/TestRequires-enum_ref_-_ref_to_nonexistent_var-err-run.golden
vendored
Normal file
1
testdata/requires/testdata/TestRequires-enum_ref_-_ref_to_nonexistent_var-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
enum reference ".NONEXISTENT_VAR" must resolve to a list
|
||||
0
testdata/requires/testdata/TestRequires-enum_ref_-_ref_to_nonexistent_var.golden
vendored
Normal file
0
testdata/requires/testdata/TestRequires-enum_ref_-_ref_to_nonexistent_var.golden
vendored
Normal file
2
testdata/timestamp/.gitignore
vendored
Normal file
2
testdata/timestamp/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.task
|
||||
generated.txt
|
||||
11
testdata/timestamp/Taskfile.yml
vendored
Normal file
11
testdata/timestamp/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- cp ./source.txt ./generated.txt
|
||||
sources:
|
||||
- ./source.txt
|
||||
generates:
|
||||
- ./generated.txt
|
||||
method: timestamp
|
||||
1
testdata/timestamp/source.txt
vendored
Normal file
1
testdata/timestamp/source.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hello from source
|
||||
58
variables.go
58
variables.go
@@ -59,6 +59,7 @@ func (e *Executor) CompiledTaskForTaskList(call *Call) (*ast.Task, error) {
|
||||
Env: nil,
|
||||
Dotenv: origTask.Dotenv,
|
||||
Silent: deepcopy.Scalar(origTask.Silent),
|
||||
UseGitignore: deepcopy.Scalar(origTask.UseGitignore),
|
||||
Interactive: origTask.Interactive,
|
||||
Internal: origTask.Internal,
|
||||
Method: origTask.Method,
|
||||
@@ -99,6 +100,22 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
}
|
||||
|
||||
cache := &templater.Cache{Vars: vars}
|
||||
|
||||
// Resolve enum refs only when dynamic variables have been evaluated,
|
||||
// since enum refs may depend on shell-derived variables (e.g. fromJson)
|
||||
requires := origTask.Requires
|
||||
if evaluateShVars {
|
||||
requires = origTask.Requires.DeepCopy()
|
||||
if err := resolveEnumRefs(requires, cache); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
gitignore := origTask.ShouldUseGitignore()
|
||||
if origTask.UseGitignore == nil {
|
||||
gitignore = e.Taskfile.UseGitignore
|
||||
}
|
||||
|
||||
new := ast.Task{
|
||||
Task: origTask.Task,
|
||||
Label: templater.Replace(origTask.Label, cache),
|
||||
@@ -115,6 +132,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
Env: nil,
|
||||
Dotenv: templater.Replace(origTask.Dotenv, cache),
|
||||
Silent: deepcopy.Scalar(origTask.Silent),
|
||||
UseGitignore: &gitignore,
|
||||
Interactive: origTask.Interactive,
|
||||
Internal: origTask.Internal,
|
||||
Method: templater.Replace(origTask.Method, cache),
|
||||
@@ -126,7 +144,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
Platforms: origTask.Platforms,
|
||||
If: templater.Replace(origTask.If, cache),
|
||||
Location: origTask.Location,
|
||||
Requires: origTask.Requires,
|
||||
Requires: requires,
|
||||
Watch: origTask.Watch,
|
||||
Failfast: origTask.Failfast,
|
||||
Namespace: origTask.Namespace,
|
||||
@@ -208,7 +226,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -257,7 +275,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -328,6 +346,7 @@ func itemsFromFor(
|
||||
dir string,
|
||||
sources []*ast.Glob,
|
||||
generates []*ast.Glob,
|
||||
gitignore bool,
|
||||
vars *ast.Vars,
|
||||
location *ast.Location,
|
||||
cache *templater.Cache,
|
||||
@@ -350,7 +369,7 @@ func itemsFromFor(
|
||||
}
|
||||
// Get the list from the task sources
|
||||
if f.From == "sources" {
|
||||
glist, err := fingerprint.Globs(dir, sources)
|
||||
glist, err := fingerprint.Globs(dir, sources, gitignore)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -364,7 +383,7 @@ func itemsFromFor(
|
||||
}
|
||||
// Get the list from the task generates
|
||||
if f.From == "generates" {
|
||||
glist, err := fingerprint.Globs(dir, generates)
|
||||
glist, err := fingerprint.Globs(dir, generates, gitignore)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -432,6 +451,35 @@ func resolveMatrixRefs(matrix *ast.Matrix, cache *templater.Cache) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveEnumRefs(requires *ast.Requires, cache *templater.Cache) error {
|
||||
if requires == nil || len(requires.Vars) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, v := range requires.Vars {
|
||||
if v.Enum == nil || v.Enum.Ref == "" {
|
||||
continue
|
||||
}
|
||||
resolved := templater.ResolveRef(v.Enum.Ref, cache)
|
||||
if cache.Err() != nil {
|
||||
return cache.Err()
|
||||
}
|
||||
arr, ok := resolved.([]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("enum reference %q must resolve to a list", v.Enum.Ref)
|
||||
}
|
||||
strValues := make([]string, 0, len(arr))
|
||||
for _, item := range arr {
|
||||
s, ok := item.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enum reference %q must contain only strings", v.Enum.Ref)
|
||||
}
|
||||
strValues = append(strValues, s)
|
||||
}
|
||||
v.Enum.Value = strValues
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// product generates the cartesian product of the input map of slices.
|
||||
func product(matrix *ast.Matrix) []map[string]any {
|
||||
if matrix.Len() == 0 {
|
||||
|
||||
2
watch.go
2
watch.go
@@ -205,7 +205,7 @@ func (e *Executor) collectSources(calls []*Call) ([]string, error) {
|
||||
var sources []string
|
||||
|
||||
err := e.traverse(calls, func(task *ast.Task) error {
|
||||
files, err := fingerprint.Globs(task.Dir, task.Sources)
|
||||
files, err := fingerprint.Globs(task.Dir, task.Sources, task.ShouldUseGitignore())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user