mirror of
https://github.com/go-task/task.git
synced 2026-05-18 05:05:20 +02:00
Compare commits
431 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
026c899d90 | ||
|
|
f6720760b4 | ||
|
|
065236f076 | ||
|
|
1bd5aa6bd5 | ||
|
|
c3fd3c4b5e | ||
|
|
299232ee7d | ||
|
|
12a26fa15e | ||
|
|
4ab5dec8ae | ||
|
|
af311229fe | ||
|
|
1443e2d989 | ||
|
|
5bf4e4a29b | ||
|
|
f9052c9fdf | ||
|
|
0a82e2e053 | ||
|
|
6dedcafd7d | ||
|
|
c84cfa41f7 | ||
|
|
9bc1efbc47 | ||
|
|
da7eb0c855 | ||
|
|
edb491a4d0 | ||
|
|
2ad3d26f4a | ||
|
|
cdfcd08213 | ||
|
|
382c37bc2a | ||
|
|
618cd8956f | ||
|
|
b53e5da41a | ||
|
|
b9c1ab8eae | ||
|
|
e47f55783e | ||
|
|
d5f071c096 | ||
|
|
fb784f4e3d | ||
|
|
91f9299c98 | ||
|
|
145412a75c | ||
|
|
ba38344ca6 | ||
|
|
4e963f8714 | ||
|
|
3d4d189bcd | ||
|
|
179bde1f37 | ||
|
|
e4de687aee | ||
|
|
06538860a8 | ||
|
|
8a37bf5c1f | ||
|
|
ca99266aea | ||
|
|
8dfafe507f | ||
|
|
678fdec7d2 | ||
|
|
3626b271a7 | ||
|
|
fc378cfb92 | ||
|
|
9488a2a744 | ||
|
|
6ece2445ae | ||
|
|
9b95e758f4 | ||
|
|
28fee2c356 | ||
|
|
763e77467b | ||
|
|
f2385e625d | ||
|
|
e929cccd73 | ||
|
|
cb183349b7 | ||
|
|
2ebbb99f58 | ||
|
|
6660afc8d2 | ||
|
|
b710259bfa | ||
|
|
4ec6c453bd | ||
|
|
28408ef3f4 | ||
|
|
a2d34ffc4c | ||
|
|
1a190a118f | ||
|
|
18efa3982f | ||
|
|
655e83454e | ||
|
|
3ad4604c36 | ||
|
|
5a27d04655 | ||
|
|
ea933bcc55 | ||
|
|
e0d6b71971 | ||
|
|
d7ee855e49 | ||
|
|
511f35a456 | ||
|
|
5889ff6b65 | ||
|
|
85a98b5f90 | ||
|
|
89b6140166 | ||
|
|
8cd51af3b0 | ||
|
|
a40ddd4949 | ||
|
|
b1814277c2 | ||
|
|
500ab8b941 | ||
|
|
745633dc0e | ||
|
|
9b99866224 | ||
|
|
54e4905432 | ||
|
|
c95805e0e0 | ||
|
|
4560589652 | ||
|
|
084d6444b4 | ||
|
|
3fb7919577 | ||
|
|
69b345efc9 | ||
|
|
4af5278d73 | ||
|
|
12fbdd3ec7 | ||
|
|
72a349b0e9 | ||
|
|
896d65b21f | ||
|
|
2161f33b5c | ||
|
|
b93638b97a | ||
|
|
47b78ca879 | ||
|
|
f0b15d397b | ||
|
|
eb285fa3d2 | ||
|
|
02b13a687a | ||
|
|
a085d62727 | ||
|
|
4ab1958df1 | ||
|
|
54ca217b92 | ||
|
|
a6c0c1daba | ||
|
|
9cc1c7b40b | ||
|
|
7901cce831 | ||
|
|
c7b4f26900 | ||
|
|
3ed403b839 | ||
|
|
386dcbc1a0 | ||
|
|
799bc85498 | ||
|
|
0d9e8dd71b | ||
|
|
a927ffb31e | ||
|
|
42ad618205 | ||
|
|
2b713f564f | ||
|
|
cb8e94aa33 | ||
|
|
6bc339d714 | ||
|
|
5712c463f5 | ||
|
|
78cc6e5fd3 | ||
|
|
38e07ea812 | ||
|
|
72e25a25fd | ||
|
|
a496ee5fcb | ||
|
|
ef4292c42f | ||
|
|
dc315efc7f | ||
|
|
a3a3e7fb0b | ||
|
|
ee99849b1d | ||
|
|
bf9dc3f662 | ||
|
|
94f82cbc5a | ||
|
|
b14318ed3f | ||
|
|
17757c0c15 | ||
|
|
19f72b7eb0 | ||
|
|
0052ad2309 | ||
|
|
af1e755196 | ||
|
|
43074c20f2 | ||
|
|
39c86992bd | ||
|
|
c71241bcbd | ||
|
|
7c2bb78540 | ||
|
|
32e675895a | ||
|
|
786813d95d | ||
|
|
f7287c503a | ||
|
|
413574e3ee | ||
|
|
4b39becf65 | ||
|
|
15b7e3c69a | ||
|
|
7c93ea8b44 | ||
|
|
6a7cfa58f9 | ||
|
|
74b93f6eef | ||
|
|
88101613c8 | ||
|
|
599591ad3c | ||
|
|
348158a5f6 | ||
|
|
c3e410e95a | ||
|
|
42bcd5406a | ||
|
|
ba23aca631 | ||
|
|
5ef245a4bd | ||
|
|
036a60f517 | ||
|
|
9c969541a5 | ||
|
|
a52b483dd0 | ||
|
|
4e84c6bb76 | ||
|
|
0f9baf62a1 | ||
|
|
979ad523ef | ||
|
|
975c07688e | ||
|
|
67a02255b5 | ||
|
|
028ae1a660 | ||
|
|
68b1d2783d | ||
|
|
12793c350d | ||
|
|
8716ab81be | ||
|
|
c2a4e4470b | ||
|
|
f5a8ec8a0c | ||
|
|
048d92709a | ||
|
|
8dc9637e7a | ||
|
|
700bf00107 | ||
|
|
4836d42828 | ||
|
|
5762d5ef8e | ||
|
|
9f2fe0da61 | ||
|
|
d1a5771839 | ||
|
|
7663abdcde | ||
|
|
1e42e1f817 | ||
|
|
5f7ae5d32e | ||
|
|
17db402e4b | ||
|
|
f2242958a6 | ||
|
|
ea4b695b5a | ||
|
|
209c88c341 | ||
|
|
bd94f9f607 | ||
|
|
9f6b78ec84 | ||
|
|
fbde227167 | ||
|
|
fc06e92a87 | ||
|
|
a0cab3f5ec | ||
|
|
bb4c254211 | ||
|
|
57bf348829 | ||
|
|
092b9b6391 | ||
|
|
cd8c831204 | ||
|
|
0d03f4f266 | ||
|
|
b8bf298c84 | ||
|
|
9a91c4cb21 | ||
|
|
2921450bf7 | ||
|
|
dffa355cad | ||
|
|
48039be12c | ||
|
|
43cb64e6cc | ||
|
|
25a7b5936f | ||
|
|
4ae3071845 | ||
|
|
242523c797 | ||
|
|
0fdb5e8665 | ||
|
|
534dfa089c | ||
|
|
51a3bcaacd | ||
|
|
6289fcf34c | ||
|
|
2959737d7d | ||
|
|
a3047d3cd8 | ||
|
|
725600f220 | ||
|
|
fd83414074 | ||
|
|
6c645a33f7 | ||
|
|
9d969e5971 | ||
|
|
8b382a3bae | ||
|
|
a34892ad94 | ||
|
|
e55bb29554 | ||
|
|
1168ef32df | ||
|
|
245d7f747f | ||
|
|
b216ae885c | ||
|
|
61cb15ad01 | ||
|
|
04579c0c44 | ||
|
|
39462cbfde | ||
|
|
72dfec68b0 | ||
|
|
f89c12ddf0 | ||
|
|
c903d07332 | ||
|
|
138b9a5a4f | ||
|
|
1e2121a99f | ||
|
|
9495fb2b1c | ||
|
|
1fda55910e | ||
|
|
e6c808c02b | ||
|
|
0fc26a43a9 | ||
|
|
c0b4c19443 | ||
|
|
1a8df44e9e | ||
|
|
82ad1de8d0 | ||
|
|
d59c795502 | ||
|
|
504cb94e8b | ||
|
|
e7606635fe | ||
|
|
9a05ceaa80 | ||
|
|
083654d8c9 | ||
|
|
79c93fb42b | ||
|
|
64fc538a16 | ||
|
|
4da081e5c3 | ||
|
|
4bdfe5ce3b | ||
|
|
26ef693417 | ||
|
|
952f32d388 | ||
|
|
e72c35f79f | ||
|
|
72991d4f04 | ||
|
|
6f965e3043 | ||
|
|
1c6d686356 | ||
|
|
dac5aa1954 | ||
|
|
303bd6ccb2 | ||
|
|
f736cfaaf1 | ||
|
|
53f97889bc | ||
|
|
fe2da74ea3 | ||
|
|
64fb66895b | ||
|
|
d2bd834c81 | ||
|
|
8a43ca5d8f | ||
|
|
a10a9faabf | ||
|
|
3d3ed0e403 | ||
|
|
47dc87a2c9 | ||
|
|
3b0a746f85 | ||
|
|
281edfe5b3 | ||
|
|
7289ffce0b | ||
|
|
61e1af50ff | ||
|
|
715a143735 | ||
|
|
a0b1605634 | ||
|
|
69fc13bd13 | ||
|
|
b42a52ba77 | ||
|
|
cb812476b3 | ||
|
|
b09c6870fe | ||
|
|
86e4a3aac7 | ||
|
|
7782bc92ae | ||
|
|
9cc2d65091 | ||
|
|
b932e539d9 | ||
|
|
be45eb04d9 | ||
|
|
6b878980dc | ||
|
|
cd910abd45 | ||
|
|
6e524bb2fa | ||
|
|
b4c8f5a0fe | ||
|
|
09f85844ba | ||
|
|
d54d2ccabc | ||
|
|
cf81ab3112 | ||
|
|
aaa7b7772d | ||
|
|
71eb8cdeea | ||
|
|
68ce8b1d84 | ||
|
|
5323990c72 | ||
|
|
ec4e68d601 | ||
|
|
bb5b045293 | ||
|
|
89f29cb75b | ||
|
|
da4ce5b0a5 | ||
|
|
fb68a5f79a | ||
|
|
f40f389cb4 | ||
|
|
a459eeaabb | ||
|
|
84f02a822f | ||
|
|
55d1aa260d | ||
|
|
e7084cdf26 | ||
|
|
ca55e9b621 | ||
|
|
6528b36caa | ||
|
|
f8736c5f77 | ||
|
|
6896accf86 | ||
|
|
c12ed49acb | ||
|
|
d1bfd3e9f7 | ||
|
|
fc17343fcc | ||
|
|
d3e9be1520 | ||
|
|
d850d03c96 | ||
|
|
0058f18676 | ||
|
|
b3c4007756 | ||
|
|
9e8fd54be9 | ||
|
|
a33544101a | ||
|
|
1c35358fcc | ||
|
|
13daa6dc35 | ||
|
|
20c1ffe098 | ||
|
|
bd8ccb8d03 | ||
|
|
8162b05f59 | ||
|
|
68d5095761 | ||
|
|
6cb0a5a2f2 | ||
|
|
08056924e0 | ||
|
|
39706105e1 | ||
|
|
bf4e7960cb | ||
|
|
3d36616e9e | ||
|
|
3976e8372a |
@@ -8,6 +8,6 @@ charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
|
||||
[*.{md,mdx,yml,yaml,json,toml,htm,html,js,ts,css,svg,sh,bash,fish}]
|
||||
[*.{md,mdx,yml,yaml,json,toml,htm,html,js,ts,vue,css,svg,sh,bash,fish}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
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/)
|
||||
|
||||
11
.github/renovate.json
vendored
11
.github/renovate.json
vendored
@@ -8,6 +8,17 @@
|
||||
],
|
||||
"mode": "full",
|
||||
"addLabels":["area: dependencies"],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"fileMatch": ["^\\.github/workflows/.*\\.ya?ml$"],
|
||||
"matchStrings": [
|
||||
"uses:\\s*golangci/golangci-lint-action@\\S+\\s+with:\\s+version:\\s*(?<currentValue>v[\\d.]+)"
|
||||
],
|
||||
"datasourceTemplate": "github-releases",
|
||||
"depNameTemplate": "golangci/golangci-lint"
|
||||
}
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["github-actions"],
|
||||
|
||||
@@ -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@v7
|
||||
- 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@v7
|
||||
- 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@v7
|
||||
- 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@v7
|
||||
- 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@v7
|
||||
- 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@v7
|
||||
- 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@v7
|
||||
- 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@v7
|
||||
- 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@v7
|
||||
- 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@v7
|
||||
- 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, {
|
||||
|
||||
36
.github/workflows/lint.yml
vendored
36
.github/workflows/lint.yml
vendored
@@ -8,51 +8,39 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.23.x, 1.24.x]
|
||||
go-version: [1.25.10, 1.26.x]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v7
|
||||
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
|
||||
with:
|
||||
version: v2.0.2
|
||||
version: v2.12.2
|
||||
|
||||
lint-jsonschema:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.12
|
||||
python-version: 3.14
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: install check-jsonschema
|
||||
run: python -m pip install 'check-jsonschema==0.27.3'
|
||||
|
||||
- name: check-jsonschema (metaschema)
|
||||
run: check-jsonschema --check-metaschema website/static/schema.json
|
||||
check_doc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get changed files in the docs folder
|
||||
id: changed-files-specific
|
||||
uses: tj-actions/changed-files@v46
|
||||
with:
|
||||
files: website/versioned_docs/**
|
||||
|
||||
- uses: actions/github-script@v7
|
||||
if: steps.changed-files-specific.outputs.any_changed == 'true'
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('website/versioned_docs has changed. Instead you need to update the docs in the website/docs folder.')
|
||||
run: check-jsonschema --check-metaschema website/src/public/schema.json
|
||||
|
||||
69
.github/workflows/pr-build.yml
vendored
Normal file
69
.github/workflows/pr-build.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: PR Build
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled, synchronize]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'needs-build')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: "1.26.x"
|
||||
cache: true
|
||||
- uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7
|
||||
with:
|
||||
version: "~> v2"
|
||||
args: release --snapshot --clean --config .goreleaser-pr.yml
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_linux_amd64
|
||||
path: dist/task_linux_amd64.tar.gz
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_linux_arm64
|
||||
path: dist/task_linux_arm64.tar.gz
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_darwin_amd64
|
||||
path: dist/task_darwin_amd64.tar.gz
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_darwin_arm64
|
||||
path: dist/task_darwin_arm64.tar.gz
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: task_windows_amd64
|
||||
path: dist/task_windows_amd64.zip
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: checksums
|
||||
path: dist/task_checksums.txt
|
||||
- uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
id: find-comment
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-includes: "📦 Build artifacts ready!"
|
||||
- uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
## 📦 Build artifacts ready!
|
||||
|
||||
Download binaries from [this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
|
||||
|
||||
Available platforms: Linux, macOS, Windows (amd64, arm64)
|
||||
edit-mode: replace
|
||||
34
.github/workflows/release-nightly.yml
vendored
Normal file
34
.github/workflows/release-nightly.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Release nightly
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: 1.26.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
args: release --clean --nightly -f .goreleaser-nightly.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
|
||||
44
.github/workflows/release.yml
vendored
44
.github/workflows/release.yml
vendored
@@ -3,28 +3,58 @@ name: goreleaser
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
go-version: 1.26.x
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
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@3be4020d41929789a01026e0e427a4321ce0ad44 # v2
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6
|
||||
with:
|
||||
package_json_file: "website/package.json"
|
||||
run_install: "true"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
args: release --clean
|
||||
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}}
|
||||
|
||||
- name: Deploy Website
|
||||
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.23.x, 1.24.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@v5
|
||||
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@v4
|
||||
|
||||
- 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
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,3 +35,4 @@ tags
|
||||
/testdata/vars/v1
|
||||
/tmp
|
||||
node_modules
|
||||
website/.netlify/
|
||||
|
||||
@@ -2,37 +2,33 @@ version: "2"
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gci
|
||||
settings:
|
||||
gofmt:
|
||||
rewrite-rules:
|
||||
- pattern: interface{}
|
||||
replacement: any
|
||||
gofumpt:
|
||||
module-path: github.com/go-task/task/v3
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/go-task
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/go-task)
|
||||
- localmodule
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- depguard
|
||||
- gosec
|
||||
- mirror
|
||||
- misspell
|
||||
- modernize
|
||||
- noctx
|
||||
- paralleltest
|
||||
- thelper
|
||||
- tparallel
|
||||
- usetesting
|
||||
settings:
|
||||
gosec:
|
||||
excludes:
|
||||
- G306
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
@@ -44,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$
|
||||
|
||||
15
.goreleaser-nightly.yml
Normal file
15
.goreleaser-nightly.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
version: 2
|
||||
pro: true
|
||||
|
||||
release:
|
||||
name_template: 'v{{.Version}}'
|
||||
|
||||
nightly:
|
||||
publish_release: true
|
||||
keep_single_release: true
|
||||
version_template: "{{incminor .Version}}-nightly"
|
||||
|
||||
includes:
|
||||
- from_file:
|
||||
path: ./.goreleaser.yml
|
||||
31
.goreleaser-pr.yml
Normal file
31
.goreleaser-pr.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- binary: task
|
||||
main: ./cmd/task
|
||||
goos: [windows, darwin, linux]
|
||||
goarch: [amd64, arm64]
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- "-s -w"
|
||||
|
||||
archives:
|
||||
- name_template: '{{.Binary}}_{{.Os}}_{{.Arch}}'
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- completion/**/*
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats: [zip]
|
||||
|
||||
snapshot:
|
||||
version_template: 'pr-{{ .ShortCommit }}'
|
||||
|
||||
checksum:
|
||||
name_template: 'task_checksums.txt'
|
||||
@@ -22,6 +22,8 @@ builds:
|
||||
goarch: '386'
|
||||
- goos: darwin
|
||||
goarch: riscv64
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
- goos: windows
|
||||
goarch: riscv64
|
||||
env:
|
||||
@@ -30,13 +32,14 @@ builds:
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w # Don't set main.version.
|
||||
- "-s -w"
|
||||
- "{{if .IsNightly}}-X github.com/go-task/task/v3/internal/version.version={{.Version}}{{end}}"
|
||||
|
||||
gomod:
|
||||
proxy: true
|
||||
|
||||
archives:
|
||||
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
||||
- name_template: '{{.Binary}}_{{.Os}}_{{.Arch}}'
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
@@ -45,27 +48,30 @@ archives:
|
||||
- goos: windows
|
||||
formats: [zip]
|
||||
|
||||
release:
|
||||
draft: true
|
||||
git:
|
||||
ignore_tags:
|
||||
- "{{if not .IsNightly}}nightly{{end}}"
|
||||
|
||||
snapshot:
|
||||
version_template: "{{.Version}}"
|
||||
version_template: '{{.Version}}'
|
||||
|
||||
checksum:
|
||||
name_template: "task_checksums.txt"
|
||||
name_template: 'task_checksums.txt'
|
||||
|
||||
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:
|
||||
- taskwarrior
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
||||
- apk
|
||||
file_name_template: '{{.ProjectName}}_{{.Version}}_{{.Os}}_{{.Arch}}'
|
||||
contents:
|
||||
- src: completion/bash/task.bash
|
||||
dst: /etc/bash_completion.d/task
|
||||
@@ -76,15 +82,15 @@ nfpms:
|
||||
|
||||
brews:
|
||||
- name: go-task
|
||||
description: Task runner / simpler Make alternative written in Go
|
||||
description: A fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
license: MIT
|
||||
homepage: https://taskfile.dev
|
||||
directory: Formula
|
||||
repository:
|
||||
owner: go-task
|
||||
name: homebrew-tap
|
||||
test:
|
||||
system "#{bin}/task", "--help"
|
||||
token: "{{.Env.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
|
||||
test: system "#{bin}/task", "--help"
|
||||
install: |-
|
||||
bin.install "task"
|
||||
bash_completion.install "completion/bash/task.bash" => "task"
|
||||
@@ -97,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/
|
||||
@@ -107,7 +113,7 @@ winget:
|
||||
commit_author:
|
||||
name: task-bot
|
||||
email: 106601941+task-bot@users.noreply.github.com
|
||||
commit_msg_template: "chore: bump {{.PackageIdentifier}} to {{.Tag}}"
|
||||
commit_msg_template: 'chore: release {{.PackageIdentifier}} {{.Tag}}'
|
||||
release_notes_url: https://github.com/go-task/task/releases/tag/{{.Tag}}
|
||||
tags:
|
||||
- build
|
||||
@@ -121,13 +127,50 @@ winget:
|
||||
- task-runner
|
||||
- taskfile
|
||||
- tool
|
||||
skip_upload: true
|
||||
repository:
|
||||
owner: microsoft
|
||||
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
|
||||
check_boxes: true
|
||||
base:
|
||||
owner: go-task
|
||||
owner: microsoft
|
||||
name: winget-pkgs
|
||||
branch: "bump-task-to-{{.Tag}}"
|
||||
branch: master
|
||||
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 fast, cross-platform build tool inspired by Make, designed for modern workflows.
|
||||
homepage: https://taskfile.dev
|
||||
license: MIT
|
||||
author: "The Task authors"
|
||||
access: public
|
||||
keywords:
|
||||
- "task"
|
||||
- "taskfile"
|
||||
- "build-tool"
|
||||
- "task-runner"
|
||||
|
||||
cloudsmiths:
|
||||
- organization: "task"
|
||||
repository: "{{if not .IsNightly}}task{{end}}"
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
- apk
|
||||
distributions:
|
||||
deb:
|
||||
- "any-distro/any-version"
|
||||
rpm:
|
||||
- "any-distro/any-version"
|
||||
alpine:
|
||||
- "alpine/any-version"
|
||||
component: main
|
||||
republish: true
|
||||
|
||||
13
.vscode/settings-sample.json
vendored
13
.vscode/settings-sample.json
vendored
@@ -1,15 +1,12 @@
|
||||
{
|
||||
"yaml.schemas": {
|
||||
"./website/static/schema.json": [
|
||||
"Taskfile.yml",
|
||||
"tmp/**/*.yml"
|
||||
"./website/src/public/schema.json": [
|
||||
"Taskfile.yml",
|
||||
"Taskfile.yaml",
|
||||
"taskfile.yml",
|
||||
"taskfile.yaml"
|
||||
]
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/versioned_docs": true,
|
||||
"**/versioned_sidesbars": true,
|
||||
"**/i18n": true
|
||||
},
|
||||
"gopls": {
|
||||
"formatting.local": "github.com/go-task"
|
||||
},
|
||||
|
||||
306
CHANGELOG.md
306
CHANGELOG.md
@@ -1,6 +1,310 @@
|
||||
# Changelog
|
||||
|
||||
## v3.43.0 - 2025-04-21
|
||||
## 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).
|
||||
- Included Taskfiles with `silent: true` now properly propagate silence to their
|
||||
tasks, while still allowing individual tasks to override with `silent: false`
|
||||
(#2640, #1319 by @trulede).
|
||||
- Added TLS certificate options for Remote Taskfiles: use `--cacert` for
|
||||
self-signed certificates and `--cert`/`--cert-key` for mTLS authentication
|
||||
(#2537, #2242 by @vmaerten).
|
||||
|
||||
## v3.47.0 - 2026-01-24
|
||||
|
||||
- Fixed remote git Taskfiles: cloning now works without explicit ref, and
|
||||
directory includes are properly resolved (#2602 by @vmaerten).
|
||||
- For `output: prefixed`, print `prefix:` if set instead of task name (#1566,
|
||||
#2633 by @trulede).
|
||||
- Ensure no ANSI sequences are printed for `--color=false` (#2560, #2584 by
|
||||
@trulede).
|
||||
- Task aliases can now contain wildcards and will match accordingly (e.g., `s-*`
|
||||
as alias for `start-*`) (#1900, #2234 by @vmaerten).
|
||||
- Added conditional execution with the `if` field: skip tasks, commands, or task
|
||||
calls based on shell exit codes or template expressions like
|
||||
`{{ eq .ENV "prod" }}` (#2564, #608 by @vmaerten).
|
||||
- Task can now interactively prompt for missing required variables when running
|
||||
in a TTY, with support for enum selection menus. Enable with `--interactive`
|
||||
flag or `interactive: true` in `.taskrc.yml` (#2579, #2079 by @vmaerten).
|
||||
|
||||
## v3.46.4 - 2025-12-24
|
||||
|
||||
- Fixed regressions in completion script for Fish (#2591, #2604, #2592 by
|
||||
@WinkelCode).
|
||||
|
||||
## v3.46.3 - 2025-12-19
|
||||
|
||||
- Fixed regression in completion script for zsh (#2593, #2594 by @vmaerten).
|
||||
|
||||
## v3.46.2 - 2025-12-18
|
||||
|
||||
- Fixed a regression on previous release that affected variables passed via
|
||||
command line (#2588, #2589 by @vmaerten).
|
||||
|
||||
## v3.46.1 - 2025-12-18
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- A small behavior change was made to dependencies. Task will now wait for all
|
||||
dependencies to finish running before continuing, even if any of them fail. To
|
||||
opt for the previous behavior, set `failfast: true` either on your
|
||||
`.taskrc.yml` or per task, or use the `--failfast` flag, which will also work
|
||||
for `--parallel` (#1246, #2525 by @andreynering).
|
||||
- The `--summary` flag now displays `vars:` (both global and task-level),
|
||||
`env:`, and `requires:` sections. Dynamic variables show their shell command
|
||||
(e.g., `sh: echo "hello"`) instead of the evaluated value (#2486 ,#2524 by
|
||||
@vmaerten).
|
||||
- Improved performance of fuzzy task name matching by implementing lazy
|
||||
initialization. Added `--disable-fuzzy` flag and `disable-fuzzy` taskrc option
|
||||
to allow disabling fuzzy matching entirely (#2521, #2523 by @vmaerten).
|
||||
- Added LLM-optimized documentation via VitePress plugin, generating `llms.txt`
|
||||
and `llms-full.txt` for AI-powered development tools (#2513 by @vmaerten).
|
||||
- Added `--trusted-hosts` CLI flag and `remote.trusted-hosts` config option to
|
||||
skip confirmation prompts for specified hosts when using Remote Taskfiles
|
||||
(#2491, #2473 by @maciejlech).
|
||||
- When running in GitHub Actions, Task now automatically emits error annotations
|
||||
on failure, improving visibility in workflow summaries (#2568 by @vmaerten).
|
||||
- The `--yes` flag is now accessible in templates via the new `CLI_ASSUME_YES`
|
||||
variable (#2577, #2479 by @semihbkgr).
|
||||
- Improved shell completion scripts (Zsh, Fish, PowerShell) by adding missing
|
||||
flags and dynamic experimental feature detection (#2532 by @vmaerten).
|
||||
- Remote Taskfiles now accept `application/octet-stream` Content-Type (#2536,
|
||||
#1944 by @vmaerten).
|
||||
- Shell completion now works when Task is installed or aliased under a different
|
||||
binary name via TASK_EXE environment variable (#2495, #2468 by @vmaerten).
|
||||
- Some small fixes and improvements were made to `task --init` and to the
|
||||
default Taskfile it generates (#2433 by @andreynering).
|
||||
- Added `--remote-cache-dir` flag and `remote.cache-dir` taskrc option to
|
||||
customize the cache directory for Remote Taskfiles (#2572 by @vmaerten).
|
||||
- Zsh completion now supports zstyle verbose option to show or hide task
|
||||
descriptions (#2571 by @vmaerten).
|
||||
- Task now automatically enables colored output in CI environments (GitHub
|
||||
Actions, GitLab CI, etc.) without requiring FORCE_COLOR=1 (#2569 by
|
||||
@vmaerten).
|
||||
- Added color taskrc option to explicitly enable or disable colored output
|
||||
globally (#2569 by @vmaerten).
|
||||
- Improved Git Remote Taskfiles by switching to go-getter: SSH authentication
|
||||
now works out of the box and `applyOf` is properly supported (#2512 by
|
||||
@vmaerten).
|
||||
|
||||
### 🐛 Fixes
|
||||
|
||||
- Fix RPM upload to Cloudsmith by including the version in the filename to
|
||||
ensure unique filenames (#2507 by @vmaerten).
|
||||
- Fix `run: when_changed` to work properly for Taskfiles included multiple times
|
||||
(#2508, #2511 by @trulede).
|
||||
- Fixed Zsh and Fish completions to stop suggesting task names after `--`
|
||||
separator, allowing proper CLI_ARGS completion (#1843, #1844 by
|
||||
@boiledfroginthewell).
|
||||
- Watch mode (`--watch`) now always runs the task, regardless of `run: once` or
|
||||
`run: when_changed` settings (#2566, #1388 by @trulede).
|
||||
- Fixed global variables (CLI_ARGS, CLI_FORCE, etc.) not being accessible in
|
||||
root-level vars section (#2403, #2397 by @trulede, @vmaerten).
|
||||
- Fixed a bug where `ignore_error` was ignored when using `task:` to call
|
||||
another task (#2552, #363 by @trulede).
|
||||
- Fixed Zsh completion not suggesting global tasks when using `-g`/`--global`
|
||||
flag (#1574, #2574 by @vmaerten).
|
||||
- Fixed Fish completion failing to parse task descriptions containing colons
|
||||
(e.g., URLs or namespaced functions) (#2101, #2573 by @vmaerten).
|
||||
- Fixed false positive "property 'for' is not allowed" warnings in IntelliJ when
|
||||
using `for` loops in Taskfiles (#2576 by @vmaerten).
|
||||
|
||||
## v3.45.5 - 2025-11-11
|
||||
|
||||
- Fixed bug that made a generic message, instead of an useful one, appear when a
|
||||
Taskfile could not be found (#2431 by @andreynering).
|
||||
- Fixed a bug that caused an error when including a Remote Git Taskfile (#2438
|
||||
by @twelvelabs).
|
||||
- Fixed issue where `.taskrc.yml` was not returned if reading it failed, and
|
||||
corrected handling of remote entrypoint Taskfiles (#2460, #2461 by @vmaerten).
|
||||
- Improved performance of `--list` and `--list-all` by introducing a faster
|
||||
compilation method that skips source globbing and checksum updates (#1322,
|
||||
#2053 by @vmaerten).
|
||||
- Fixed a concurrency bug with `output: group`. This ensures that begin/end
|
||||
parts won't be mixed up from different tasks (#1208, #2349, #2350 by
|
||||
@trulede).
|
||||
- Do not re-evaluate variables for `defer:` (#2244, #2418 by @trulede).
|
||||
- Improve error message when a Taskfile is not found (#2441, #2494 by
|
||||
@vmaerten).
|
||||
- Fixed generic error message `exit status 1` when a dependency task failed
|
||||
(#2286 by @GrahamDennis).
|
||||
- Fixed YAML library from the unmaintained `gopkg.in/yaml.v3` to the new fork
|
||||
maintained by the official YAML org (#2171, #2434 by @andreynering).
|
||||
- On Windows, the built-in version of the `rm` core utils contains a fix related
|
||||
to the `-f` flag (#2426,
|
||||
[u-root/u-root#3464](https://github.com/u-root/u-root/pull/3464),
|
||||
[mvdan/sh#1199](https://github.com/mvdan/sh/pull/1199), #2506 by
|
||||
@andreynering).
|
||||
|
||||
## v3.45.4 - 2025-09-17
|
||||
|
||||
- Fixed a bug where `cache-expiry` could not be defined in `.taskrc.yml` (#2423
|
||||
by @vmaerten).
|
||||
- Fixed a bug where `.taskrc.yml` files in parent folders were not read
|
||||
correctly (#2424 by @vmaerten).
|
||||
- Fixed a bug where autocomplete in subfolders did not work with zsh (#2425 by
|
||||
@vmaerten).
|
||||
|
||||
## v3.45.3 - 2025-09-15
|
||||
|
||||
- Task now includes built-in core utilities to greatly improve compatibility on
|
||||
Windows. This means that your commands that uses `cp`, `mv`, `mkdir` or any
|
||||
other common core utility will now work by default on Windows, without extra
|
||||
setup. This is something we wanted to address for many many years, and it's
|
||||
finally being shipped!
|
||||
[Read our blog post this the topic](https://taskfile.dev/blog/windows-core-utils).
|
||||
(#197, #2360 by @andreynering).
|
||||
- :sparkles: Built and deployed a [brand new website](https://taskfile.dev)
|
||||
using [VitePress](https://vitepress.dev) (#2359, #2369, #2371, #2375, #2378 by
|
||||
@vmaerten, @andreynering, @pd93).
|
||||
- Began releasing
|
||||
[nightly builds](https://github.com/go-task/task/releases/tag/nightly). This
|
||||
will allow people to test our changes before they are fully released and
|
||||
without having to install Go to build them (#2358 by @vmaerten).
|
||||
- Added support for global config files in `$XDG_CONFIG_HOME/task/taskrc.yml` or
|
||||
`$HOME/.taskrc.yml`. Check out our new
|
||||
[configuration guide](https://taskfile.dev/docs/reference/config) for more
|
||||
details (#2247, #2380, #2390, #2391 by @vmaerten, @pd93).
|
||||
- Added experiments to the taskrc schema to clarify the expected keys and values
|
||||
(#2235 by @vmaerten).
|
||||
- Added support for new properties in `.taskrc.yml`: insecure, verbose,
|
||||
concurrency, remote offline, remote timeout, and remote expiry. :warning:
|
||||
Note: setting offline via environment variable is no longer supported. (#2389
|
||||
by @vmaerten)
|
||||
- Added a `--nested` flag when outputting tasks using `--list --json`. This will
|
||||
output tasks in a nested structure when tasks are namespaced (#2415 by @pd93).
|
||||
- Enhanced support for tasks with wildcards: they are now logged correctly, and
|
||||
wildcard parameters are fully considered during fingerprinting (#1808, #1795
|
||||
by @vmaerten).
|
||||
- Fixed panic when a variable was declared as an empty hash (`{}`) (#2416, #2417
|
||||
by @trulede).
|
||||
|
||||
#### Package API
|
||||
|
||||
- Bumped the minimum version of Go to 1.24 (#2358 by @vmaerten).
|
||||
|
||||
#### Other news
|
||||
|
||||
We recently released our
|
||||
[official GitHub Action](https://github.com/go-task/setup-task). This is based
|
||||
on the fantastic work by the Arduino team who created and maintained the
|
||||
community version. Now that this is officially adopted, fixes/updates should be
|
||||
more timely. We have already merged a couple of longstanding PRs in our
|
||||
[first release](https://github.com/go-task/setup-task/releases/tag/v1.0.0) (by
|
||||
@pd93, @shrink, @trim21 and all the previous contributors to
|
||||
[arduino/setup-task](https://github.com/arduino/setup-task/)).
|
||||
|
||||
## v3.45.0-v3.45.2 - 2025-09-15
|
||||
|
||||
Failed due to an issue with our release process.
|
||||
|
||||
## v3.44.1 - 2025-07-23
|
||||
|
||||
- Internal tasks will no longer be shown as suggestions since they cannot be
|
||||
called (#2309, #2323 by @maxmzkrcensys)
|
||||
- Fixed install script for some ARM platforms (#1516, #2291 by @trulede).
|
||||
- Fixed a regression where fingerprinting was not working correctly if the path
|
||||
to you Taskfile contained a space (#2321, #2322 by @pd93).
|
||||
- Reverted a breaking change to `randInt` (#2312, #2316 by @pd93).
|
||||
- Made new variables `TEST_NAME` and `TEST_DIR` available in fixture tests
|
||||
(#2265 by @pd93).
|
||||
|
||||
## v3.44.0 - 2025-06-08
|
||||
|
||||
- Added `uuid`, `randInt` and `randIntN` template functions (#1346, #2225 by
|
||||
@pd93).
|
||||
- Added new `CLI_ARGS_LIST` array variable which contains the arguments passed
|
||||
to Task after the `--` (the same as `CLI_ARGS`, but an array instead of a
|
||||
string). (#2138, #2139, #2140 by @pd93).
|
||||
- Added `toYaml` and `fromYaml` templating functions (#2217, #2219 by @pd93).
|
||||
- Added `task` field the `--list --json` output (#2256 by @aleksandersh).
|
||||
- Added the ability to
|
||||
[pin included taskfiles](https://taskfile.dev/next/experiments/remote-taskfiles/#manual-checksum-pinning)
|
||||
by specifying a checksum. This works with both local and remote Taskfiles
|
||||
(#2222, #2223 by @pd93).
|
||||
- When using the
|
||||
[Remote Taskfiles experiment](https://github.com/go-task/task/issues/1317),
|
||||
any credentials used in the URL will now be redacted in Task's output (#2100,
|
||||
#2220 by @pd93).
|
||||
- Fixed fuzzy suggestions not working when misspelling a task name (#2192, #2200
|
||||
by @vmaerten).
|
||||
- Fixed a bug where taskfiles in directories containing spaces created
|
||||
directories in the wrong location (#2208, #2216 by @pd93).
|
||||
- Added support for dual JSON schema files, allowing changes without affecting
|
||||
the current schema. The current schemas will only be updated during releases.
|
||||
(#2211 by @vmaerten).
|
||||
- Improved fingerprint documentation by specifying that the method can be set at
|
||||
the root level to apply to all tasks (#2233 by @vmaerten).
|
||||
- Fixed some watcher regressions after #2048 (#2199, #2202, #2241, #2196 by
|
||||
@wazazaby, #2271 by @andreynering).
|
||||
|
||||
## v3.43.3 - 2025-04-27
|
||||
|
||||
Reverted the changes made in #2113 and #2186 that affected the
|
||||
`USER_WORKING_DIR` and built-in variables. This fixes #2206, #2195, #2207 and
|
||||
#2208.
|
||||
|
||||
## v3.43.2 - 2025-04-21
|
||||
|
||||
- Fixed regresion of `CLI_ARGS` being exposed as the wrong type (#2190, #2191 by
|
||||
@vmaerten).
|
||||
|
||||
## v3.43.1 - 2025-04-21
|
||||
|
||||
- Significant improvements were made to the watcher. We migrated from
|
||||
[watcher](https://github.com/radovskyb/watcher) to
|
||||
|
||||
32
README.md
32
README.md
@@ -1,16 +1,16 @@
|
||||
<div align="center">
|
||||
<a href="https://taskfile.dev">
|
||||
<img src="website/static/img/logo.svg" width="200px" height="200px" />
|
||||
<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/installation/">Installation</a> | <a href="https://taskfile.dev/usage/">Documentation</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>
|
||||
@@ -19,7 +19,29 @@
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://devowl.io">
|
||||
<img src="/website/static/img/devowl.io.svg" height="100px" title="devowl.io" />
|
||||
<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" />
|
||||
</a>
|
||||
</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>
|
||||
|
||||
44
Taskfile.yml
44
Taskfile.yml
@@ -8,6 +8,7 @@ includes:
|
||||
|
||||
vars:
|
||||
BIN: "{{.ROOT_DIR}}/bin"
|
||||
GOTESTSUM_FORMAT: '{{if .CI}}github-actions{{else}}pkgname{{end}}'
|
||||
|
||||
env:
|
||||
CGO_ENABLED: '0'
|
||||
@@ -28,6 +29,7 @@ tasks:
|
||||
aliases: [i]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- go.mod
|
||||
cmds:
|
||||
- go install -v ./cmd/task
|
||||
|
||||
@@ -53,9 +55,12 @@ tasks:
|
||||
generate:fixtures:
|
||||
desc: Runs tests and generates golden fixture files
|
||||
aliases: [gen:fixtures, g:fixtures]
|
||||
env:
|
||||
GOLDIE_UPDATE: 'true'
|
||||
GOLDIE_TEMPLATE: 'true'
|
||||
cmds:
|
||||
- find ./testdata -name '*.golden' -delete
|
||||
- go test -update ./...
|
||||
- go test ./...
|
||||
|
||||
install:mockery:
|
||||
desc: Installs mockgen; a tool to generate mock files
|
||||
@@ -87,6 +92,7 @@ tasks:
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- .golangci.yml
|
||||
- go.mod
|
||||
cmds:
|
||||
- golangci-lint run
|
||||
|
||||
@@ -95,9 +101,19 @@ tasks:
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- .golangci.yml
|
||||
- go.mod
|
||||
cmds:
|
||||
- golangci-lint run --fix
|
||||
|
||||
format:
|
||||
desc: Runs golangci-lint and formats any Go files
|
||||
aliases: [fmt, f]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- .golangci.yml
|
||||
cmds:
|
||||
- golangci-lint fmt
|
||||
|
||||
sleepit:build:
|
||||
desc: Builds the sleepit test helper
|
||||
sources:
|
||||
@@ -117,29 +133,37 @@ tasks:
|
||||
test:
|
||||
desc: Runs test suite
|
||||
aliases: [t]
|
||||
deps: [gotestsum:install]
|
||||
sources:
|
||||
- "**/*.go"
|
||||
- "testdata/**/*"
|
||||
cmds:
|
||||
- go test ./...
|
||||
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./...
|
||||
|
||||
test:watch:
|
||||
desc: Runs test suite with watch tests included
|
||||
deps: [sleepit:build]
|
||||
deps: [sleepit:build, gotestsum:install]
|
||||
cmds:
|
||||
- go test ./... -tags 'watch'
|
||||
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./... -tags 'watch'
|
||||
|
||||
test:all:
|
||||
desc: Runs test suite with signals and watch tests included
|
||||
deps: [sleepit:build]
|
||||
deps: [sleepit:build, gotestsum:install]
|
||||
cmds:
|
||||
- go test -tags 'signals watch' ./...
|
||||
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' -tags 'signals watch' ./...
|
||||
|
||||
goreleaser:test:
|
||||
desc: Tests release process without publishing
|
||||
cmds:
|
||||
- goreleaser --snapshot --clean
|
||||
|
||||
gotestsum:install:
|
||||
desc: Installs gotestsum
|
||||
status:
|
||||
- command -v gotestsum
|
||||
cmds:
|
||||
- go install gotest.tools/gotestsum@latest
|
||||
|
||||
goreleaser:install:
|
||||
desc: Installs goreleaser
|
||||
cmds:
|
||||
@@ -189,7 +213,6 @@ tasks:
|
||||
Please wait for the CI to finish and then do the following:
|
||||
|
||||
- Copy the changelog for v{{.VERSION}} to the GitHub release
|
||||
- Publish the package to NPM with `task npm:publish`
|
||||
- Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml
|
||||
preconditions:
|
||||
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
|
||||
@@ -204,12 +227,7 @@ tasks:
|
||||
- "git add --all"
|
||||
- "git commit -m v{{.VERSION}}"
|
||||
- "git push"
|
||||
- "git tag v{{.VERSION}}"
|
||||
- "git tag -a v{{.VERSION}} -m v{{.VERSION}}"
|
||||
- "git push origin tag v{{.VERSION}}"
|
||||
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
|
||||
silent: true
|
||||
|
||||
npm:publish:
|
||||
desc: Publish release to npm
|
||||
cmds:
|
||||
- npm publish --access=public
|
||||
|
||||
24
args/args.go
24
args/args.go
@@ -20,17 +20,7 @@ func Get() ([]string, []string, error) {
|
||||
if doubleDashPos == -1 {
|
||||
return args, nil, nil
|
||||
}
|
||||
|
||||
var quotedCliArgs []string
|
||||
for _, arg := range args[doubleDashPos:] {
|
||||
quotedCliArg, err := syntax.Quote(arg, syntax.LangBash)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
quotedCliArgs = append(quotedCliArgs, quotedCliArg)
|
||||
}
|
||||
|
||||
return args[:doubleDashPos], quotedCliArgs, nil
|
||||
return args[:doubleDashPos], args[doubleDashPos:], nil
|
||||
}
|
||||
|
||||
// Parse parses command line argument: tasks and global variables
|
||||
@@ -51,6 +41,18 @@ func Parse(args ...string) ([]*task.Call, *ast.Vars) {
|
||||
return calls, globals
|
||||
}
|
||||
|
||||
func ToQuotedString(args []string) (string, error) {
|
||||
var quotedCliArgs []string
|
||||
for _, arg := range args {
|
||||
quotedCliArg, err := syntax.Quote(arg, syntax.LangBash)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
quotedCliArgs = append(quotedCliArgs, quotedCliArg)
|
||||
}
|
||||
return strings.Join(quotedCliArgs, " "), nil
|
||||
}
|
||||
|
||||
func splitVar(s string) (string, string) {
|
||||
pair := strings.SplitN(s, "=", 2)
|
||||
return pair[0], pair[1]
|
||||
|
||||
@@ -3,13 +3,11 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/otiai10/copy"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
@@ -17,15 +15,11 @@ import (
|
||||
|
||||
const (
|
||||
changelogSource = "CHANGELOG.md"
|
||||
changelogTarget = "website/docs/changelog.mdx"
|
||||
docsSource = "website/docs"
|
||||
docsTarget = "website/versioned_docs/version-latest"
|
||||
changelogTarget = "website/src/docs/changelog.md"
|
||||
versionFile = "internal/version/version.txt"
|
||||
)
|
||||
|
||||
var (
|
||||
changelogReleaseRegex = regexp.MustCompile(`## Unreleased`)
|
||||
versionRegex = regexp.MustCompile(`(?m)^ "version": "\d+\.\d+\.\d+",$`)
|
||||
)
|
||||
var changelogReleaseRegex = regexp.MustCompile(`## Unreleased`)
|
||||
|
||||
// Flags
|
||||
var (
|
||||
@@ -49,7 +43,7 @@ func release() error {
|
||||
return errors.New("error: expected version number")
|
||||
}
|
||||
|
||||
version, err := getVersion()
|
||||
version, err := getVersion(versionFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -67,32 +61,18 @@ func release() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setVersionFile("internal/version/version.txt", version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setJSONVersion("package.json", version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setJSONVersion("package-lock.json", version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := docs(); err != nil {
|
||||
if err := setVersionFile(versionFile, version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVersion() (*semver.Version, error) {
|
||||
cmd := exec.Command("git", "describe", "--tags", "--abbrev=0")
|
||||
b, err := cmd.Output()
|
||||
func getVersion(filename string) (*semver.Version, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return semver.NewVersion(strings.TrimSpace(string(b)))
|
||||
}
|
||||
|
||||
@@ -137,41 +117,21 @@ 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
|
||||
}
|
||||
|
||||
// Wrap the changelog content with v-pre directive for VitePress to prevent
|
||||
// Vue from interpreting template syntax like {{.TASK_VERSION}}
|
||||
changelogWithVPre := strings.Replace(changelog, "# Changelog\n\n", "# Changelog\n\n::: v-pre\n\n", 1) + "\n:::"
|
||||
|
||||
// Add the frontmatter to the changelog
|
||||
changelog = fmt.Sprintf("---\n%s\n---\n\n%s", frontmatter, changelog)
|
||||
changelogWithFrontmatter := fmt.Sprintf("---\n%s\n---\n\n%s", frontmatter, changelogWithVPre)
|
||||
|
||||
// Write the changelog to the target file
|
||||
return os.WriteFile(changelogTarget, []byte(changelog), 0o644)
|
||||
return os.WriteFile(changelogTarget, []byte(changelogWithFrontmatter), 0o644) //nolint:gosec
|
||||
}
|
||||
|
||||
func setVersionFile(fileName string, version *semver.Version) error {
|
||||
return os.WriteFile(fileName, []byte(version.String()+"\n"), 0o644)
|
||||
}
|
||||
|
||||
func setJSONVersion(fileName string, version *semver.Version) error {
|
||||
// Read the JSON file
|
||||
b, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Replace the version
|
||||
new := versionRegex.ReplaceAllString(string(b), fmt.Sprintf(` "version": "%s",`, version.String()))
|
||||
|
||||
// Write the JSON file
|
||||
return os.WriteFile(fileName, []byte(new), 0o644)
|
||||
}
|
||||
|
||||
func docs() error {
|
||||
if err := os.RemoveAll(docsTarget); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := copy.Copy(docsSource, docsTarget); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/args"
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/flags"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
@@ -28,19 +29,34 @@ func main() {
|
||||
Color: flags.Color,
|
||||
}
|
||||
if err, ok := err.(*errors.TaskRunError); ok && flags.ExitCode {
|
||||
emitCIErrorAnnotation(err)
|
||||
l.Errf(logger.Red, "%v\n", err)
|
||||
os.Exit(err.TaskExitCode())
|
||||
}
|
||||
if err, ok := err.(errors.TaskError); ok {
|
||||
emitCIErrorAnnotation(err)
|
||||
l.Errf(logger.Red, "%v\n", err)
|
||||
os.Exit(err.Code())
|
||||
}
|
||||
emitCIErrorAnnotation(err)
|
||||
l.Errf(logger.Red, "%v\n", err)
|
||||
os.Exit(errors.CodeUnknown)
|
||||
}
|
||||
os.Exit(errors.CodeOk)
|
||||
}
|
||||
|
||||
// emitCIErrorAnnotation emits an error annotation for supported CI providers.
|
||||
func emitCIErrorAnnotation(err error) {
|
||||
if isGA, _ := strconv.ParseBool(os.Getenv("GITHUB_ACTIONS")); !isGA {
|
||||
return
|
||||
}
|
||||
if e, ok := err.(*errors.TaskRunError); ok {
|
||||
fmt.Fprintf(os.Stdout, "::error title=Task '%s' failed::%v\n", e.TaskName, e.Err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "::error title=Task failed::%v\n", err)
|
||||
}
|
||||
|
||||
func run() error {
|
||||
log := &logger.Logger{
|
||||
Stdout: os.Stdout,
|
||||
@@ -76,7 +92,7 @@ func run() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, args, err := args.Get()
|
||||
args, _, err := args.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -128,6 +144,7 @@ func run() error {
|
||||
flags.ListAll,
|
||||
flags.ListJson,
|
||||
flags.NoStatus,
|
||||
flags.Nested,
|
||||
)
|
||||
if listOptions.ShouldListTasks() {
|
||||
if flags.Silent {
|
||||
@@ -144,24 +161,34 @@ func run() error {
|
||||
}
|
||||
|
||||
// Parse the remaining arguments
|
||||
argv, cliArgs, err := args.Get()
|
||||
cliArgsPreDash, cliArgsPostDash, err := args.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
calls, globals := args.Parse(argv...)
|
||||
calls, globals := args.Parse(cliArgsPreDash...)
|
||||
|
||||
// If there are no calls, run the default task instead
|
||||
if len(calls) == 0 {
|
||||
calls = append(calls, &task.Call{Task: "default"})
|
||||
}
|
||||
|
||||
globals.Set("CLI_ARGS", ast.Var{Value: cliArgs})
|
||||
globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
||||
// Merge CLI variables first (e.g. FOO=bar) so they take priority over Taskfile defaults
|
||||
e.Taskfile.Vars.Merge(globals, nil)
|
||||
|
||||
// Then ReverseMerge special variables so they're available for templating
|
||||
cliArgsPostDashQuoted, err := args.ToQuotedString(cliArgsPostDash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
specialVars := ast.NewVars()
|
||||
specialVars.Set("CLI_ARGS", ast.Var{Value: cliArgsPostDashQuoted})
|
||||
specialVars.Set("CLI_ARGS_LIST", ast.Var{Value: cliArgsPostDash})
|
||||
specialVars.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||
specialVars.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||
specialVars.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||
specialVars.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
||||
specialVars.Set("CLI_ASSUME_YES", ast.Var{Value: flags.AssumeYes})
|
||||
e.Taskfile.Vars.ReverseMerge(specialVars, nil)
|
||||
if !flags.Watch {
|
||||
e.InterceptInterruptSignals()
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
|
||||
defer cancel()
|
||||
if err := run(ctx); err != nil {
|
||||
fmt.Println(ctx.Err())
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context) error {
|
||||
req, err := http.NewRequest("GET", "https://taskfile.dev/schema.json", nil)
|
||||
if err != nil {
|
||||
fmt.Println(1)
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
fmt.Println(2)
|
||||
return err
|
||||
}
|
||||
fmt.Println(3)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
26
compiler.go
26
compiler.go
@@ -61,13 +61,14 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
|
||||
newVar := templater.ReplaceVar(v, cache)
|
||||
// If the variable should not be evaluated, but is nil, set it to an empty string
|
||||
// This stops empty interface errors when using the templater to replace values later
|
||||
// Preserve the Sh field so it can be displayed in summary
|
||||
if !evaluateShVars && newVar.Value == nil {
|
||||
result.Set(k, ast.Var{Value: ""})
|
||||
result.Set(k, ast.Var{Value: "", Sh: newVar.Sh})
|
||||
return nil
|
||||
}
|
||||
// If the variable should not be evaluated and it is set, we can set it and return
|
||||
if !evaluateShVars {
|
||||
result.Set(k, ast.Var{Value: newVar.Value})
|
||||
result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh})
|
||||
return nil
|
||||
}
|
||||
// Now we can check for errors since we've handled all the cases when we don't want to evaluate
|
||||
@@ -197,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"] = ""
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# vim: set tabstop=2 shiftwidth=2 expandtab:
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION='--list-all'
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
|
||||
function _task()
|
||||
{
|
||||
@@ -21,10 +22,14 @@ function _task()
|
||||
|
||||
# Handle special arguments of options.
|
||||
case "$prev" in
|
||||
-d|--dir)
|
||||
-d|--dir|--remote-cache-dir)
|
||||
_filedir -d
|
||||
return $?
|
||||
;;
|
||||
--cacert|--cert|--cert-key)
|
||||
_filedir
|
||||
return $?
|
||||
;;
|
||||
-t|--taskfile)
|
||||
_filedir yaml || return $?
|
||||
_filedir yml
|
||||
@@ -52,4 +57,4 @@ function _task()
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
complete -F _task task
|
||||
complete -F _task "$TASK_CMD"
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
set -l GO_TASK_PROGNAME task
|
||||
set -l GO_TASK_PROGNAME (if set -q GO_TASK_PROGNAME; echo $GO_TASK_PROGNAME; else if set -q TASK_EXE; echo $TASK_EXE; else; echo task; end)
|
||||
|
||||
# Cache variables for experiments (global)
|
||||
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 --inherit-variable GO_TASK_PROGNAME
|
||||
set -l now (date +%s)
|
||||
set -l ttl 1 # Cache for 1 second only
|
||||
|
||||
# Return cached value if still valid
|
||||
if test (math "$now - $__task_experiments_cache_time") -lt $ttl
|
||||
printf '%s\n' $__task_experiments_cache
|
||||
return
|
||||
end
|
||||
|
||||
# Refresh cache
|
||||
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
|
||||
|
||||
# Helper function to check if an experiment is enabled
|
||||
function __task_is_experiment_enabled
|
||||
set -l experiment $argv[1]
|
||||
__task_get_experiments | string match -qr "^\* $experiment:.*on"
|
||||
end
|
||||
|
||||
function __task_get_tasks --description "Prints all available tasks with their description" --inherit-variable GO_TASK_PROGNAME
|
||||
# Check if the global task is requested
|
||||
@@ -27,28 +54,67 @@ function __task_get_tasks --description "Prints all available tasks with their d
|
||||
end
|
||||
|
||||
# Grab names and descriptions (if any) of the tasks
|
||||
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):\s*\(.*\)\s*(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):\s*\(.*\)/\1\t\2/'| string split0)
|
||||
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):[[:space:]]\{2,\}\(.*\)[[:space:]]\{2,\}(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):[[:space:]]\{2,\}\(.*\)/\1\t\2/'| string split0)
|
||||
if test $output
|
||||
echo $output
|
||||
end
|
||||
end
|
||||
|
||||
complete -c $GO_TASK_PROGNAME -d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was
|
||||
specified.' -xa "(__task_get_tasks)"
|
||||
complete -c $GO_TASK_PROGNAME \
|
||||
-d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was specified.' \
|
||||
-xa "(__task_get_tasks)" \
|
||||
-n "not __fish_seen_subcommand_from --"
|
||||
|
||||
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
|
||||
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'sets directory of execution'
|
||||
complete -c $GO_TASK_PROGNAME -l dry -d 'compiles and prints tasks in the order that they would be run, without executing them'
|
||||
complete -c $GO_TASK_PROGNAME -s f -l force -d 'forces execution even when the task is up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -s h -l help -d 'shows Task usage'
|
||||
complete -c $GO_TASK_PROGNAME -s i -l init -d 'creates a new Taskfile.yml in the current folder'
|
||||
complete -c $GO_TASK_PROGNAME -s l -l list -d 'lists tasks with description of current Taskfile'
|
||||
complete -c $GO_TASK_PROGNAME -s o -l output -d 'sets output style: [interleaved|group|prefixed]' -xa "interleaved group prefixed"
|
||||
complete -c $GO_TASK_PROGNAME -s p -l parallel -d 'executes tasks provided on command line in parallel'
|
||||
complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disables echoing'
|
||||
complete -c $GO_TASK_PROGNAME -l status -d 'exits with non-zero exit code if any of the given tasks is not up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -l summary -d 'show summary about a task'
|
||||
complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose which Taskfile to run. Defaults to "Taskfile.yml"'
|
||||
complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'enables verbose mode'
|
||||
complete -c $GO_TASK_PROGNAME -l version -d 'show Task version'
|
||||
complete -c $GO_TASK_PROGNAME -s w -l watch -d 'enables watch of the given task'
|
||||
# Standard flags
|
||||
complete -c $GO_TASK_PROGNAME -s a -l list-all -d 'list all tasks'
|
||||
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
|
||||
complete -c $GO_TASK_PROGNAME -s C -l concurrency -d 'limit number of concurrent tasks'
|
||||
complete -c $GO_TASK_PROGNAME -l completion -d 'generate shell completion script' -xa "bash zsh fish powershell"
|
||||
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set directory of execution'
|
||||
complete -c $GO_TASK_PROGNAME -l disable-fuzzy -d 'disable fuzzy matching for task names'
|
||||
complete -c $GO_TASK_PROGNAME -s n -l dry -d 'compile and print tasks without executing'
|
||||
complete -c $GO_TASK_PROGNAME -s x -l exit-code -d 'pass-through exit code of task command'
|
||||
complete -c $GO_TASK_PROGNAME -l experiments -d 'list available experiments'
|
||||
complete -c $GO_TASK_PROGNAME -s F -l failfast -d 'when running tasks in parallel, stop all tasks if one fails'
|
||||
complete -c $GO_TASK_PROGNAME -s f -l force -d 'force execution even when up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -s g -l global -d 'run global Taskfile from home directory'
|
||||
complete -c $GO_TASK_PROGNAME -s h -l help -d 'show help'
|
||||
complete -c $GO_TASK_PROGNAME -s i -l init -d 'create new Taskfile'
|
||||
complete -c $GO_TASK_PROGNAME -l insecure -d 'allow insecure Taskfile downloads'
|
||||
complete -c $GO_TASK_PROGNAME -s I -l interval -d 'interval to watch for changes'
|
||||
complete -c $GO_TASK_PROGNAME -s j -l json -d 'format task list as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -s l -l list -d 'list tasks with descriptions'
|
||||
complete -c $GO_TASK_PROGNAME -l nested -d 'nest namespaces when listing as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -l no-status -d 'ignore status when listing as JSON'
|
||||
complete -c $GO_TASK_PROGNAME -l interactive -d 'prompt for missing required variables'
|
||||
complete -c $GO_TASK_PROGNAME -s o -l output -d 'set output style' -xa "interleaved group prefixed"
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-begin -d 'message template before grouped output'
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-end -d 'message template after grouped output'
|
||||
complete -c $GO_TASK_PROGNAME -l output-group-error-only -d 'hide output from successful tasks'
|
||||
complete -c $GO_TASK_PROGNAME -s p -l parallel -d 'execute tasks in parallel'
|
||||
complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disable echoing'
|
||||
complete -c $GO_TASK_PROGNAME -l sort -d 'set task sorting order' -xa "default alphanumeric none"
|
||||
complete -c $GO_TASK_PROGNAME -l status -d 'exit non-zero if tasks not up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -l summary -d 'show task summary'
|
||||
complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose Taskfile to run'
|
||||
complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'verbose output'
|
||||
complete -c $GO_TASK_PROGNAME -l version -d 'show version'
|
||||
complete -c $GO_TASK_PROGNAME -s w -l watch -d 'watch mode, re-run on changes'
|
||||
complete -c $GO_TASK_PROGNAME -s y -l yes -d 'assume yes to all prompts'
|
||||
|
||||
# Experimental flags (dynamically checked at completion time via -n condition)
|
||||
# GentleForce experiment
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled GENTLE_FORCE" -l force-all -d 'force execution of task and all dependencies'
|
||||
|
||||
# RemoteTaskfiles experiment - Options
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l offline -d 'use only local or cached Taskfiles'
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l timeout -d 'timeout for remote Taskfile downloads'
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l expiry -d 'cache expiry duration'
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l remote-cache-dir -d 'directory to cache remote Taskfiles' -xa "(__fish_complete_directories)"
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cacert -d 'custom CA certificate for TLS' -r
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cert -d 'client certificate for mTLS' -r
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l cert-key -d 'client certificate private key' -r
|
||||
|
||||
# RemoteTaskfiles experiment - Operations
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l download -d 'download remote Taskfile'
|
||||
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l clear-cache -d 'clear remote Taskfile cache'
|
||||
|
||||
@@ -5,22 +5,86 @@ Register-ArgumentCompleter -CommandName task -ScriptBlock {
|
||||
|
||||
if ($commandName.StartsWith('-')) {
|
||||
$completions = @(
|
||||
[CompletionResult]::new('--list-all ', '--list-all ', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('--color ', '--color', [CompletionResultType]::ParameterName, '--color'),
|
||||
[CompletionResult]::new('--concurrency=', '--concurrency=', [CompletionResultType]::ParameterName, 'concurrency'),
|
||||
[CompletionResult]::new('--interval=', '--interval=', [CompletionResultType]::ParameterName, 'interval'),
|
||||
[CompletionResult]::new('--output=interleaved ', '--output=interleaved', [CompletionResultType]::ParameterName, '--output='),
|
||||
[CompletionResult]::new('--output=group ', '--output=group', [CompletionResultType]::ParameterName, '--output='),
|
||||
[CompletionResult]::new('--output=prefixed ', '--output=prefixed', [CompletionResultType]::ParameterName, '--output='),
|
||||
[CompletionResult]::new('--dry ', '--dry', [CompletionResultType]::ParameterName, '--dry'),
|
||||
[CompletionResult]::new('--force ', '--force', [CompletionResultType]::ParameterName, '--force'),
|
||||
[CompletionResult]::new('--parallel ', '--parallel', [CompletionResultType]::ParameterName, '--parallel'),
|
||||
[CompletionResult]::new('--silent ', '--silent', [CompletionResultType]::ParameterName, '--silent'),
|
||||
[CompletionResult]::new('--status ', '--status', [CompletionResultType]::ParameterName, '--status'),
|
||||
[CompletionResult]::new('--verbose ', '--verbose', [CompletionResultType]::ParameterName, '--verbose'),
|
||||
[CompletionResult]::new('--watch ', '--watch', [CompletionResultType]::ParameterName, '--watch')
|
||||
# Standard flags (alphabetical order)
|
||||
[CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('--list-all', '--list-all', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'colored output'),
|
||||
[CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'colored output'),
|
||||
[CompletionResult]::new('-C', '-C', [CompletionResultType]::ParameterName, 'limit concurrent tasks'),
|
||||
[CompletionResult]::new('--concurrency', '--concurrency', [CompletionResultType]::ParameterName, 'limit concurrent tasks'),
|
||||
[CompletionResult]::new('--completion', '--completion', [CompletionResultType]::ParameterName, 'generate shell completion'),
|
||||
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'set directory'),
|
||||
[CompletionResult]::new('--dir', '--dir', [CompletionResultType]::ParameterName, 'set directory'),
|
||||
[CompletionResult]::new('--disable-fuzzy', '--disable-fuzzy', [CompletionResultType]::ParameterName, 'disable fuzzy matching'),
|
||||
[CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'dry run'),
|
||||
[CompletionResult]::new('--dry', '--dry', [CompletionResultType]::ParameterName, 'dry run'),
|
||||
[CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[CompletionResult]::new('--exit-code', '--exit-code', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[CompletionResult]::new('--experiments', '--experiments', [CompletionResultType]::ParameterName, 'list experiments'),
|
||||
[CompletionResult]::new('-F', '-F', [CompletionResultType]::ParameterName, 'fail fast on pallalel tasks'),
|
||||
[CompletionResult]::new('--failfast', '--failfast', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('--force', '--force', [CompletionResultType]::ParameterName, 'force execution'),
|
||||
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'run global Taskfile'),
|
||||
[CompletionResult]::new('--global', '--global', [CompletionResultType]::ParameterName, 'run global Taskfile'),
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'show help'),
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'show help'),
|
||||
[CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'create new Taskfile'),
|
||||
[CompletionResult]::new('--init', '--init', [CompletionResultType]::ParameterName, 'create new Taskfile'),
|
||||
[CompletionResult]::new('--insecure', '--insecure', [CompletionResultType]::ParameterName, 'allow insecure downloads'),
|
||||
[CompletionResult]::new('-I', '-I', [CompletionResultType]::ParameterName, 'watch interval'),
|
||||
[CompletionResult]::new('--interval', '--interval', [CompletionResultType]::ParameterName, 'watch interval'),
|
||||
[CompletionResult]::new('-j', '-j', [CompletionResultType]::ParameterName, 'format as JSON'),
|
||||
[CompletionResult]::new('--json', '--json', [CompletionResultType]::ParameterName, 'format as JSON'),
|
||||
[CompletionResult]::new('-l', '-l', [CompletionResultType]::ParameterName, 'list tasks'),
|
||||
[CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'list tasks'),
|
||||
[CompletionResult]::new('--nested', '--nested', [CompletionResultType]::ParameterName, 'nest namespaces in JSON'),
|
||||
[CompletionResult]::new('--no-status', '--no-status', [CompletionResultType]::ParameterName, 'ignore status in JSON'),
|
||||
[CompletionResult]::new('--interactive', '--interactive', [CompletionResultType]::ParameterName, 'prompt for missing required variables'),
|
||||
[CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'set output style'),
|
||||
[CompletionResult]::new('--output', '--output', [CompletionResultType]::ParameterName, 'set output style'),
|
||||
[CompletionResult]::new('--output-group-begin', '--output-group-begin', [CompletionResultType]::ParameterName, 'template before group'),
|
||||
[CompletionResult]::new('--output-group-end', '--output-group-end', [CompletionResultType]::ParameterName, 'template after group'),
|
||||
[CompletionResult]::new('--output-group-error-only', '--output-group-error-only', [CompletionResultType]::ParameterName, 'hide successful output'),
|
||||
[CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'execute in parallel'),
|
||||
[CompletionResult]::new('--parallel', '--parallel', [CompletionResultType]::ParameterName, 'execute in parallel'),
|
||||
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'silent mode'),
|
||||
[CompletionResult]::new('--silent', '--silent', [CompletionResultType]::ParameterName, 'silent mode'),
|
||||
[CompletionResult]::new('--sort', '--sort', [CompletionResultType]::ParameterName, 'task sorting order'),
|
||||
[CompletionResult]::new('--status', '--status', [CompletionResultType]::ParameterName, 'check task status'),
|
||||
[CompletionResult]::new('--summary', '--summary', [CompletionResultType]::ParameterName, 'show task summary'),
|
||||
[CompletionResult]::new('-t', '-t', [CompletionResultType]::ParameterName, 'choose Taskfile'),
|
||||
[CompletionResult]::new('--taskfile', '--taskfile', [CompletionResultType]::ParameterName, 'choose Taskfile'),
|
||||
[CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'verbose output'),
|
||||
[CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'verbose output'),
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'show version'),
|
||||
[CompletionResult]::new('-w', '-w', [CompletionResultType]::ParameterName, 'watch mode'),
|
||||
[CompletionResult]::new('--watch', '--watch', [CompletionResultType]::ParameterName, 'watch mode'),
|
||||
[CompletionResult]::new('-y', '-y', [CompletionResultType]::ParameterName, 'assume yes'),
|
||||
[CompletionResult]::new('--yes', '--yes', [CompletionResultType]::ParameterName, 'assume yes')
|
||||
)
|
||||
|
||||
# Experimental flags (dynamically added based on enabled experiments)
|
||||
$experiments = & task --experiments 2>$null | Out-String
|
||||
|
||||
if ($experiments -match '\* GENTLE_FORCE:.*on') {
|
||||
$completions += [CompletionResult]::new('--force-all', '--force-all', [CompletionResultType]::ParameterName, 'force all dependencies')
|
||||
}
|
||||
|
||||
if ($experiments -match '\* REMOTE_TASKFILES:.*on') {
|
||||
# Options
|
||||
$completions += [CompletionResult]::new('--offline', '--offline', [CompletionResultType]::ParameterName, 'use cached Taskfiles')
|
||||
$completions += [CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'download timeout')
|
||||
$completions += [CompletionResult]::new('--expiry', '--expiry', [CompletionResultType]::ParameterName, 'cache expiry')
|
||||
$completions += [CompletionResult]::new('--remote-cache-dir', '--remote-cache-dir', [CompletionResultType]::ParameterName, 'cache directory')
|
||||
$completions += [CompletionResult]::new('--cacert', '--cacert', [CompletionResultType]::ParameterName, 'custom CA certificate')
|
||||
$completions += [CompletionResult]::new('--cert', '--cert', [CompletionResultType]::ParameterName, 'client certificate')
|
||||
$completions += [CompletionResult]::new('--cert-key', '--cert-key', [CompletionResultType]::ParameterName, 'client private key')
|
||||
# Operations
|
||||
$completions += [CompletionResult]::new('--download', '--download', [CompletionResultType]::ParameterName, 'download remote Taskfile')
|
||||
$completions += [CompletionResult]::new('--clear-cache', '--clear-cache', [CompletionResultType]::ParameterName, 'clear cache')
|
||||
}
|
||||
|
||||
return $completions.Where{ $_.CompletionText.StartsWith($commandName) }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +1,151 @@
|
||||
#compdef task
|
||||
compdef _task task
|
||||
typeset -A opt_args
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
compdef _task "$TASK_CMD"
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"
|
||||
|
||||
# Check if an experiment is enabled
|
||||
function __task_is_experiment_enabled() {
|
||||
local experiment=$1
|
||||
task --experiments 2>/dev/null | grep -q "^\* ${experiment}:.*on"
|
||||
}
|
||||
|
||||
# Listing commands from Taskfile.yml
|
||||
function __task_list() {
|
||||
local -a scripts cmd
|
||||
local -i enabled=0
|
||||
local taskfile item task desc
|
||||
|
||||
cmd=(task)
|
||||
cmd=($TASK_CMD)
|
||||
taskfile=${(Qv)opt_args[(i)-t|--taskfile]}
|
||||
taskfile=${taskfile//\~/$HOME}
|
||||
|
||||
for arg in "${words[@]:0:$CURRENT}"; do
|
||||
if [[ "$arg" = "--" ]]; then
|
||||
# Use default completion for words after `--` as they are CLI_ARGS.
|
||||
_default
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$taskfile" && -f "$taskfile" ]]; then
|
||||
enabled=1
|
||||
cmd+=(--taskfile "$taskfile")
|
||||
else
|
||||
for taskfile in {T,t}askfile{,.dist}.{yaml,yml}; do
|
||||
if [[ -f "$taskfile" ]]; then
|
||||
enabled=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Check if global flag is set
|
||||
if (( ${+opt_args[-g]} || ${+opt_args[--global]} )); then
|
||||
cmd+=(--global)
|
||||
fi
|
||||
|
||||
if output=$("${cmd[@]}" $_GO_TASK_COMPLETION_LIST_OPTION 2>/dev/null); then
|
||||
enabled=1
|
||||
fi
|
||||
|
||||
(( enabled )) || return 0
|
||||
|
||||
scripts=()
|
||||
for item in "${(@)${(f)$("${cmd[@]}" $_GO_TASK_COMPLETION_LIST_OPTION)}[2,-1]#\* }"; do
|
||||
|
||||
# Read zstyle verbose option (default = true via -T)
|
||||
local show_desc
|
||||
zstyle -T ":completion:${curcontext}:" verbose && show_desc=true || show_desc=false
|
||||
|
||||
for item in "${(@)${(f)output}[2,-1]#\* }"; do
|
||||
task="${item%%:[[:space:]]*}"
|
||||
desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
|
||||
if [[ "$show_desc" == "true" ]]; then
|
||||
local desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
else
|
||||
scripts+=( "$task" )
|
||||
fi
|
||||
done
|
||||
_describe 'Task to run' scripts
|
||||
|
||||
if [[ "$show_desc" == "true" ]]; then
|
||||
_describe 'Task to run' scripts
|
||||
else
|
||||
compadd -Q -a scripts
|
||||
fi
|
||||
}
|
||||
|
||||
_task() {
|
||||
_arguments \
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]' \
|
||||
'(-c --color)'{-c,--color}'[colored output]' \
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]' \
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: ' \
|
||||
'(-s --silent)'{-s,--silent}'[disable echoing]' \
|
||||
'(--status)--status[exit non-zero if supplied tasks not up-to-date]' \
|
||||
'(--summary)--summary[show summary\: field from tasks instead of running them]' \
|
||||
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files' \
|
||||
'(-v --verbose)'{-v,--verbose}'[verbose mode]' \
|
||||
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]' \
|
||||
+ '(operation)' \
|
||||
{-l,--list}'[list describable tasks]' \
|
||||
{-a,--list-all}'[list all tasks]' \
|
||||
{-i,--init}'[create new Taskfile.yml]' \
|
||||
'(-*)'{-h,--help}'[show help]' \
|
||||
'(-*)--version[show version and exit]' \
|
||||
'*: :__task_list'
|
||||
local -a standard_args operation_args
|
||||
|
||||
standard_args=(
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: '
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]'
|
||||
'(-F --failfast)'{-F,--failfast}'[when running tasks in parallel, stop all tasks if one fails]'
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]'
|
||||
'(-c --color)'{-c,--color}'[colored output]'
|
||||
'(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell)'
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs'
|
||||
'(--disable-fuzzy)--disable-fuzzy[disable fuzzy matching for task names]'
|
||||
'(-n --dry)'{-n,--dry}'[compiles and prints tasks without executing]'
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]'
|
||||
'(-x --exit-code)'{-x,--exit-code}'[pass-through exit code of task command]'
|
||||
'(--experiments)--experiments[list available experiments]'
|
||||
'(-g --global)'{-g,--global}'[run global Taskfile from home directory]'
|
||||
'(--insecure)--insecure[allow insecure Taskfile downloads]'
|
||||
'(-I --interval)'{-I,--interval}'[interval to watch for changes]:duration: '
|
||||
'(-j --json)'{-j,--json}'[format task list as JSON]'
|
||||
'(--nested)--nested[nest namespaces when listing as JSON]'
|
||||
'(--no-status)--no-status[ignore status when listing as JSON]'
|
||||
'(--interactive)--interactive[prompt for missing required variables]'
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)'
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: '
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: '
|
||||
'(--output-group-error-only)--output-group-error-only[hide output from successful tasks]'
|
||||
'(-s --silent)'{-s,--silent}'[disable echoing]'
|
||||
'(--sort)--sort[set task sorting order]:order:(default alphanumeric none)'
|
||||
'(--status)--status[exit non-zero if supplied tasks not up-to-date]'
|
||||
'(--summary)--summary[show summary\: field from tasks instead of running them]'
|
||||
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files'
|
||||
'(-v --verbose)'{-v,--verbose}'[verbose mode]'
|
||||
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]'
|
||||
'(-y --yes)'{-y,--yes}'[assume yes to all prompts]'
|
||||
)
|
||||
|
||||
# Experimental flags (dynamically added based on enabled experiments)
|
||||
# Options (modify behavior)
|
||||
if __task_is_experiment_enabled "GENTLE_FORCE"; then
|
||||
standard_args+=('(--force-all)--force-all[force execution of task and all dependencies]')
|
||||
fi
|
||||
|
||||
if __task_is_experiment_enabled "REMOTE_TASKFILES"; then
|
||||
standard_args+=(
|
||||
'(--offline --download)--offline[use only local or cached Taskfiles]'
|
||||
'(--timeout)--timeout[timeout for remote Taskfile downloads]:duration: '
|
||||
'(--expiry)--expiry[cache expiry duration]:duration: '
|
||||
'(--remote-cache-dir)--remote-cache-dir[directory to cache remote Taskfiles]:cache dir:_dirs'
|
||||
'(--cacert)--cacert[custom CA certificate for TLS]:file:_files'
|
||||
'(--cert)--cert[client certificate for mTLS]:file:_files'
|
||||
'(--cert-key)--cert-key[client certificate private key]:file:_files'
|
||||
)
|
||||
fi
|
||||
|
||||
operation_args=(
|
||||
# Task names completion (can be specified multiple times)
|
||||
'(operation)*: :__task_list'
|
||||
# Operational args completion (mutually exclusive)
|
||||
+ '(operation)'
|
||||
'(*)'{-l,--list}'[list describable tasks]'
|
||||
'(*)'{-a,--list-all}'[list all tasks]'
|
||||
'(*)'{-i,--init}'[create new Taskfile.yml]'
|
||||
'(- *)'{-h,--help}'[show help]'
|
||||
'(- *)--version[show version and exit]'
|
||||
)
|
||||
|
||||
# Experimental operations (dynamically added based on enabled experiments)
|
||||
if __task_is_experiment_enabled "REMOTE_TASKFILES"; then
|
||||
standard_args+=(
|
||||
'(--offline --clear-cache)--download[download remote Taskfile]'
|
||||
)
|
||||
operation_args+=(
|
||||
'(* --download)--clear-cache[clear remote Taskfile cache]'
|
||||
)
|
||||
fi
|
||||
|
||||
_arguments -S $standard_args $operation_args
|
||||
}
|
||||
|
||||
# don't run the completion function when being source-ed or eval-ed
|
||||
|
||||
@@ -5,15 +5,12 @@ import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
var typeErrorRegex = regexp.MustCompile(`line \d+: (.*)`)
|
||||
|
||||
type (
|
||||
TaskfileDecodeError struct {
|
||||
Message string
|
||||
@@ -53,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", extractTypeErrorMessage(message)))
|
||||
fmt.Fprintln(buf, color.RedString("- %s", message))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", extractTypeErrorMessage(te.Errors[0])))
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", te.Errors[0]))
|
||||
}
|
||||
} else {
|
||||
// Otherwise print the error message normally
|
||||
@@ -128,11 +125,3 @@ func (err *TaskfileDecodeError) WithFileInfo(location string, snippet string) *T
|
||||
err.Snippet = snippet
|
||||
return err
|
||||
}
|
||||
|
||||
func extractTypeErrorMessage(message string) string {
|
||||
matches := typeErrorRegex.FindStringSubmatch(message)
|
||||
if len(matches) == 2 {
|
||||
return matches[1]
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
CodeTaskfileNetworkTimeout
|
||||
CodeTaskfileInvalid
|
||||
CodeTaskfileCycle
|
||||
CodeTaskfileDoesNotMatchChecksum
|
||||
)
|
||||
|
||||
// Task related exit codes
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -46,12 +47,17 @@ func (err *TaskRunError) Code() int {
|
||||
}
|
||||
|
||||
func (err *TaskRunError) TaskExitCode() int {
|
||||
if c, ok := interp.IsExitStatus(err.Err); ok {
|
||||
return int(c)
|
||||
var exit interp.ExitStatus
|
||||
if errors.As(err.Err, &exit) {
|
||||
return int(exit)
|
||||
}
|
||||
return err.Code()
|
||||
}
|
||||
|
||||
func (err *TaskRunError) Unwrap() error {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
// TaskInternalError when the user attempts to invoke a task that is internal.
|
||||
type TaskInternalError struct {
|
||||
TaskName string
|
||||
@@ -160,7 +166,7 @@ func (v MissingVar) String() string {
|
||||
}
|
||||
|
||||
func (err *TaskMissingRequiredVarsError) Error() string {
|
||||
var vars []string
|
||||
vars := make([]string, 0, len(err.MissingVars))
|
||||
for _, v := range err.MissingVars {
|
||||
vars = append(vars, v.String())
|
||||
}
|
||||
@@ -189,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,16 +12,23 @@ import (
|
||||
// TaskfileNotFoundError is returned when no appropriate Taskfile is found when
|
||||
// searching the filesystem.
|
||||
type TaskfileNotFoundError struct {
|
||||
URI string
|
||||
Walk bool
|
||||
URI string
|
||||
Walk bool
|
||||
AskInit bool
|
||||
OwnerChange bool
|
||||
}
|
||||
|
||||
func (err TaskfileNotFoundError) Error() string {
|
||||
var walkText string
|
||||
if err.Walk {
|
||||
walkText = " (or any of the parent directories)"
|
||||
if err.OwnerChange {
|
||||
walkText = " (or any of the parent directories until ownership changed)."
|
||||
} else if err.Walk {
|
||||
walkText = " (or any of the parent directories)."
|
||||
}
|
||||
return fmt.Sprintf(`task: No Taskfile found at %q%s`, err.URI, walkText)
|
||||
if err.AskInit {
|
||||
walkText += " Run `task --init` to create a new Taskfile."
|
||||
}
|
||||
return fmt.Sprintf(`task: No Taskfile found at %q%s`, filepath.ToSlash(err.URI), walkText)
|
||||
}
|
||||
|
||||
func (err TaskfileNotFoundError) Code() int {
|
||||
@@ -47,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 {
|
||||
@@ -66,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 {
|
||||
@@ -82,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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -116,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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -137,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,
|
||||
)
|
||||
@@ -162,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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -179,11 +187,32 @@ 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),
|
||||
)
|
||||
}
|
||||
|
||||
func (err TaskfileCycleError) Code() int {
|
||||
return CodeTaskfileCycle
|
||||
}
|
||||
|
||||
// TaskfileDoesNotMatchChecksum is returned when a Taskfile's checksum does not
|
||||
// match the one pinned in the parent Taskfile.
|
||||
type TaskfileDoesNotMatchChecksum struct {
|
||||
URI string
|
||||
ExpectedChecksum string
|
||||
ActualChecksum string
|
||||
}
|
||||
|
||||
func (err *TaskfileDoesNotMatchChecksum) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"task: The checksum of the Taskfile at %q does not match!\ngot: %q\nwant: %q",
|
||||
filepath.ToSlash(err.URI),
|
||||
err.ActualChecksum,
|
||||
err.ExpectedChecksum,
|
||||
)
|
||||
}
|
||||
|
||||
func (err *TaskfileDoesNotMatchChecksum) Code() int {
|
||||
return CodeTaskfileDoesNotMatchChecksum
|
||||
}
|
||||
|
||||
125
executor.go
125
executor.go
@@ -4,11 +4,10 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/sajari/fuzzy"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
@@ -35,19 +34,27 @@ type (
|
||||
Insecure bool
|
||||
Download bool
|
||||
Offline bool
|
||||
TrustedHosts []string
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
RemoteCacheDir string
|
||||
CACert string
|
||||
Cert string
|
||||
CertKey string
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
DisableFuzzy bool
|
||||
AssumeYes bool
|
||||
AssumeTerm bool // Used for testing
|
||||
Interactive bool
|
||||
Dry bool
|
||||
Summary bool
|
||||
Parallel bool
|
||||
Color bool
|
||||
Concurrency int
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
|
||||
// I/O
|
||||
Stdin io.Reader
|
||||
@@ -64,14 +71,16 @@ type (
|
||||
UserWorkingDir string
|
||||
EnableVersionCheck bool
|
||||
|
||||
fuzzyModel *fuzzy.Model
|
||||
fuzzyModel *fuzzy.Model
|
||||
fuzzyModelOnce sync.Once
|
||||
|
||||
promptedVars *ast.Vars // vars collected via interactive prompts
|
||||
concurrencySemaphore chan struct{}
|
||||
taskCallCount map[string]*int32
|
||||
mkdirMutexMap map[string]*sync.Mutex
|
||||
executionHashes map[string]context.Context
|
||||
executionHashesMutex sync.Mutex
|
||||
watchedDirs *xsync.MapOf[string, bool]
|
||||
watchedDirs *xsync.Map[string, bool]
|
||||
}
|
||||
TempDir struct {
|
||||
Remote string
|
||||
@@ -123,7 +132,6 @@ type dirOption struct {
|
||||
}
|
||||
|
||||
func (o *dirOption) ApplyToExecutor(e *Executor) {
|
||||
e.UserWorkingDir, _ = filepath.Abs(o.dir)
|
||||
e.Dir = o.dir
|
||||
}
|
||||
|
||||
@@ -227,6 +235,20 @@ func (o *offlineOption) ApplyToExecutor(e *Executor) {
|
||||
e.Offline = o.offline
|
||||
}
|
||||
|
||||
// WithTrustedHosts configures the [Executor] with a list of trusted hosts for remote
|
||||
// Taskfiles. Hosts in this list will not prompt for user confirmation.
|
||||
func WithTrustedHosts(trustedHosts []string) ExecutorOption {
|
||||
return &trustedHostsOption{trustedHosts}
|
||||
}
|
||||
|
||||
type trustedHostsOption struct {
|
||||
trustedHosts []string
|
||||
}
|
||||
|
||||
func (o *trustedHostsOption) ApplyToExecutor(e *Executor) {
|
||||
e.TrustedHosts = o.trustedHosts
|
||||
}
|
||||
|
||||
// WithTimeout sets the [Executor]'s timeout for fetching remote taskfiles. By
|
||||
// default, the timeout is set to 10 seconds.
|
||||
func WithTimeout(timeout time.Duration) ExecutorOption {
|
||||
@@ -242,7 +264,7 @@ func (o *timeoutOption) ApplyToExecutor(e *Executor) {
|
||||
}
|
||||
|
||||
// WithCacheExpiryDuration sets the duration after which the cache is considered
|
||||
// expired. By default, the cache is considered expired after 24 hours.
|
||||
// expired. By default, the cache is 0 (disabled).
|
||||
func WithCacheExpiryDuration(duration time.Duration) ExecutorOption {
|
||||
return &cacheExpiryDurationOption{duration: duration}
|
||||
}
|
||||
@@ -255,6 +277,58 @@ func (o *cacheExpiryDurationOption) ApplyToExecutor(r *Executor) {
|
||||
r.CacheExpiryDuration = o.duration
|
||||
}
|
||||
|
||||
// WithRemoteCacheDir sets the directory where remote taskfiles are cached.
|
||||
func WithRemoteCacheDir(dir string) ExecutorOption {
|
||||
return &remoteCacheDirOption{dir: dir}
|
||||
}
|
||||
|
||||
type remoteCacheDirOption struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
func (o *remoteCacheDirOption) ApplyToExecutor(e *Executor) {
|
||||
e.RemoteCacheDir = o.dir
|
||||
}
|
||||
|
||||
// WithCACert sets the path to a custom CA certificate for TLS connections.
|
||||
func WithCACert(caCert string) ExecutorOption {
|
||||
return &caCertOption{caCert: caCert}
|
||||
}
|
||||
|
||||
type caCertOption struct {
|
||||
caCert string
|
||||
}
|
||||
|
||||
func (o *caCertOption) ApplyToExecutor(e *Executor) {
|
||||
e.CACert = o.caCert
|
||||
}
|
||||
|
||||
// WithCert sets the path to a client certificate for TLS connections.
|
||||
func WithCert(cert string) ExecutorOption {
|
||||
return &certOption{cert: cert}
|
||||
}
|
||||
|
||||
type certOption struct {
|
||||
cert string
|
||||
}
|
||||
|
||||
func (o *certOption) ApplyToExecutor(e *Executor) {
|
||||
e.Cert = o.cert
|
||||
}
|
||||
|
||||
// WithCertKey sets the path to a client certificate key for TLS connections.
|
||||
func WithCertKey(certKey string) ExecutorOption {
|
||||
return &certKeyOption{certKey: certKey}
|
||||
}
|
||||
|
||||
type certKeyOption struct {
|
||||
certKey string
|
||||
}
|
||||
|
||||
func (o *certKeyOption) ApplyToExecutor(e *Executor) {
|
||||
e.CertKey = o.certKey
|
||||
}
|
||||
|
||||
// WithWatch tells the [Executor] to keep running in the background and watch
|
||||
// for changes to the fingerprint of the tasks that are run. When changes are
|
||||
// detected, a new task run is triggered.
|
||||
@@ -298,6 +372,19 @@ func (o *silentOption) ApplyToExecutor(e *Executor) {
|
||||
e.Silent = o.silent
|
||||
}
|
||||
|
||||
// WithDisableFuzzy tells the [Executor] to disable fuzzy matching for task names.
|
||||
func WithDisableFuzzy(disableFuzzy bool) ExecutorOption {
|
||||
return &disableFuzzyOption{disableFuzzy}
|
||||
}
|
||||
|
||||
type disableFuzzyOption struct {
|
||||
disableFuzzy bool
|
||||
}
|
||||
|
||||
func (o *disableFuzzyOption) ApplyToExecutor(e *Executor) {
|
||||
e.DisableFuzzy = o.disableFuzzy
|
||||
}
|
||||
|
||||
// WithAssumeYes tells the [Executor] to assume "yes" for all prompts.
|
||||
func WithAssumeYes(assumeYes bool) ExecutorOption {
|
||||
return &assumeYesOption{assumeYes}
|
||||
@@ -324,6 +411,19 @@ func (o *assumeTermOption) ApplyToExecutor(e *Executor) {
|
||||
e.AssumeTerm = o.assumeTerm
|
||||
}
|
||||
|
||||
// WithInteractive tells the [Executor] to prompt for missing required variables.
|
||||
func WithInteractive(interactive bool) ExecutorOption {
|
||||
return &interactiveOption{interactive}
|
||||
}
|
||||
|
||||
type interactiveOption struct {
|
||||
interactive bool
|
||||
}
|
||||
|
||||
func (o *interactiveOption) ApplyToExecutor(e *Executor) {
|
||||
e.Interactive = o.interactive
|
||||
}
|
||||
|
||||
// WithDry tells the [Executor] to output the commands that would be run without
|
||||
// actually running them.
|
||||
func WithDry(dry bool) ExecutorOption {
|
||||
@@ -504,3 +604,16 @@ type versionCheckOption struct {
|
||||
func (o *versionCheckOption) ApplyToExecutor(e *Executor) {
|
||||
e.EnableVersionCheck = o.enableVersionCheck
|
||||
}
|
||||
|
||||
// WithFailfast tells the [Executor] whether or not to check the version of
|
||||
func WithFailfast(failfast bool) ExecutorOption {
|
||||
return &failfastOption{failfast}
|
||||
}
|
||||
|
||||
type failfastOption struct {
|
||||
failfast bool
|
||||
}
|
||||
|
||||
func (o *failfastOption) ApplyToExecutor(e *Executor) {
|
||||
e.Failfast = o.failfast
|
||||
}
|
||||
|
||||
313
executor_test.go
313
executor_test.go
@@ -3,7 +3,6 @@ package task_test
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -13,7 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
@@ -50,7 +49,8 @@ func NewExecutorTest(t *testing.T, opts ...ExecutorTestOption) {
|
||||
task: "default",
|
||||
vars: map[string]any{},
|
||||
TaskTest: TaskTest{
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
fixtureTemplateData: map[string]any{},
|
||||
},
|
||||
}
|
||||
// Apply the functional options
|
||||
@@ -143,12 +143,12 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
t.Helper()
|
||||
f := func(t *testing.T) {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
var buffer SyncBuffer
|
||||
|
||||
opts := append(
|
||||
tt.executorOpts,
|
||||
task.WithStdout(&buf),
|
||||
task.WithStderr(&buf),
|
||||
task.WithStdout(&buffer),
|
||||
task.WithStderr(&buffer),
|
||||
)
|
||||
|
||||
// If the test has input, create a reader for it and add it to the
|
||||
@@ -165,13 +165,14 @@ 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
|
||||
if err := e.Setup(); tt.wantSetupError {
|
||||
require.Error(t, err)
|
||||
tt.writeFixtureErrSetup(t, g, err)
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
tt.writeFixtureBuffer(t, g, buffer.buf)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
@@ -188,11 +189,11 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
}
|
||||
|
||||
// Run the task and check for errors
|
||||
ctx := context.Background()
|
||||
ctx := t.Context()
|
||||
if err := e.Run(ctx, call); tt.wantRunError {
|
||||
require.Error(t, err)
|
||||
tt.writeFixtureErrRun(t, g, err)
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
tt.writeFixtureBuffer(t, g, buffer.buf)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
@@ -205,7 +206,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
tt.writeFixtureBuffer(t, g, buffer.buf)
|
||||
}
|
||||
|
||||
// Run the test (with a name if it has one)
|
||||
@@ -232,7 +233,7 @@ func TestEmptyTaskfile(t *testing.T) {
|
||||
task.WithDir("testdata/empty_taskfile"),
|
||||
),
|
||||
WithSetupError(),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -263,6 +264,23 @@ func TestVars(t *testing.T) {
|
||||
task.WithSilent(true),
|
||||
),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("cli-var-priority-default"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/vars"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithTask("cli-var-priority"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("cli-var-priority-override"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/vars"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithTask("cli-var-priority"),
|
||||
WithVar("CLI_VAR", "from_cli"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestRequires(t *testing.T) {
|
||||
@@ -334,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
|
||||
@@ -347,6 +400,7 @@ func TestSpecialVars(t *testing.T) {
|
||||
// Root
|
||||
"print-task",
|
||||
"print-root-dir",
|
||||
"print-root-taskfile",
|
||||
"print-taskfile",
|
||||
"print-taskfile-dir",
|
||||
"print-task-dir",
|
||||
@@ -367,7 +421,7 @@ func TestSpecialVars(t *testing.T) {
|
||||
task.WithVersionCheck(true),
|
||||
),
|
||||
WithTask(test),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -551,7 +605,7 @@ func TestStatus(t *testing.T) {
|
||||
task.WithVerbose(true),
|
||||
),
|
||||
WithTask("gen-silent-baz"),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -621,6 +675,30 @@ func TestAlias(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
func TestSummaryWithVarsAndRequires(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Test basic case from prompt.md - vars and requires
|
||||
NewExecutorTest(t,
|
||||
WithName("vars-and-requires"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/summary-vars-requires"),
|
||||
task.WithSummary(true),
|
||||
),
|
||||
WithTask("mytask"),
|
||||
)
|
||||
|
||||
// Test with shell variables
|
||||
NewExecutorTest(t,
|
||||
WithName("shell-vars"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/summary-vars-requires"),
|
||||
task.WithSummary(true),
|
||||
),
|
||||
WithTask("with-sh-var"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestLabel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -665,6 +743,36 @@ func TestLabel(t *testing.T) {
|
||||
),
|
||||
WithTask("foo"),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("label in error"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/label_error"),
|
||||
),
|
||||
WithTask("foo"),
|
||||
WithRunError(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("up to date"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/prefix_uptodate"),
|
||||
task.WithOutputStyle(ast.Output{Name: "prefixed"}),
|
||||
),
|
||||
WithTask("foo"),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("up to dat with no output style"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/prefix_uptodate"),
|
||||
),
|
||||
WithTask("foo"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestPromptInSummary(t *testing.T) {
|
||||
@@ -777,7 +885,7 @@ func TestForCmds(t *testing.T) {
|
||||
task.WithForce(true),
|
||||
),
|
||||
WithTask(test.name),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
WithFixtureTemplating(),
|
||||
}
|
||||
if test.wantErr {
|
||||
opts = append(opts, WithRunError())
|
||||
@@ -822,7 +930,7 @@ func TestForDeps(t *testing.T) {
|
||||
task.WithOutputStyle(ast.Output{Name: "group"}),
|
||||
),
|
||||
WithTask(test.name),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
WithFixtureTemplating(),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
}
|
||||
if test.wantErr {
|
||||
@@ -937,3 +1045,178 @@ func TestVarInheritance(t *testing.T) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuzzyModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("fuzzy"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/fuzzy"),
|
||||
),
|
||||
WithTask("instal"),
|
||||
WithRunError(),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("not-fuzzy"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/fuzzy"),
|
||||
),
|
||||
WithTask("install"),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("intern"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/fuzzy"),
|
||||
),
|
||||
WithTask("intern"),
|
||||
WithRunError(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestIncludeChecksum(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("correct"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/includes_checksum/correct"),
|
||||
),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("incorrect"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/includes_checksum/incorrect"),
|
||||
),
|
||||
WithSetupError(),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestIncludeSilent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("include-taskfile-silent"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/includes_silent"),
|
||||
),
|
||||
WithTask("default"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestFailfast(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("default"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/failfast/default"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
WithRunError(),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Option", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("default"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/failfast/default"),
|
||||
task.WithSilent(true),
|
||||
task.WithFailfast(true),
|
||||
),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
WithRunError(),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Task", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("task"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/failfast/task"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
WithRunError(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
task string
|
||||
vars map[string]any
|
||||
verbose bool
|
||||
}{
|
||||
// Basic command-level if
|
||||
{name: "cmd-if-true", task: "cmd-if-true"},
|
||||
{name: "cmd-if-false", task: "cmd-if-false"},
|
||||
|
||||
// Task-level if
|
||||
{name: "task-if-true", task: "task-if-true"},
|
||||
{name: "task-if-false", task: "task-if-false", verbose: true},
|
||||
|
||||
// Task call with if
|
||||
{name: "task-call-if-true", task: "task-call-if-true"},
|
||||
{name: "task-call-if-false", task: "task-call-if-false", verbose: true},
|
||||
|
||||
// Go template conditions
|
||||
{name: "template-eq-true", task: "template-eq-true"},
|
||||
{name: "template-eq-false", task: "template-eq-false", verbose: true},
|
||||
{name: "template-ne", task: "template-ne"},
|
||||
{name: "template-bool-true", task: "template-bool-true"},
|
||||
{name: "template-bool-false", task: "template-bool-false"},
|
||||
{name: "template-direct-true", task: "template-direct-true"},
|
||||
{name: "template-direct-false", task: "template-direct-false"},
|
||||
{name: "template-and", task: "template-and"},
|
||||
{name: "template-or", task: "template-or"},
|
||||
|
||||
// CLI variable override
|
||||
{name: "template-cli-var", task: "template-cli-var", vars: map[string]any{"MY_VAR": "yes"}},
|
||||
|
||||
// Task-level if with template
|
||||
{name: "task-level-template", task: "task-level-template"},
|
||||
{name: "task-level-template-false", task: "task-level-template-false", verbose: true},
|
||||
|
||||
// For loop with if
|
||||
{name: "if-in-for-loop", task: "if-in-for-loop", verbose: true},
|
||||
|
||||
// Task-level if with dynamic variable
|
||||
{name: "task-if-dynamic-true", task: "task-if-dynamic-true"},
|
||||
{name: "task-if-dynamic-false", task: "task-if-dynamic-false", verbose: true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
opts := []ExecutorTestOption{
|
||||
WithName(test.name),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/if"),
|
||||
task.WithSilent(true),
|
||||
task.WithVerbose(test.verbose),
|
||||
),
|
||||
WithTask(test.task),
|
||||
}
|
||||
if test.vars != nil {
|
||||
for k, v := range test.vars {
|
||||
opts = append(opts, WithVar(k, v))
|
||||
}
|
||||
}
|
||||
NewExecutorTest(t, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"github.com/go-task/task/v3/taskrc"
|
||||
"github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
|
||||
const envPrefix = "TASK_X_"
|
||||
@@ -31,16 +32,13 @@ var (
|
||||
var xList []Experiment
|
||||
|
||||
func Parse(dir string) {
|
||||
config, _ := taskrc.GetConfig(dir)
|
||||
ParseWithConfig(dir, config)
|
||||
}
|
||||
|
||||
func ParseWithConfig(dir string, config *ast.TaskRC) {
|
||||
// Read any .env files
|
||||
readDotEnv(dir)
|
||||
|
||||
// Create a node for the Task config reader
|
||||
node, _ := taskrc.NewNode("", dir)
|
||||
|
||||
// Read the Task config file
|
||||
reader := taskrc.NewReader()
|
||||
config, _ := reader.Read(node)
|
||||
|
||||
// Initialize the experiments
|
||||
GentleForce = New("GENTLE_FORCE", config, 1)
|
||||
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -44,7 +44,8 @@ func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) {
|
||||
task: "default",
|
||||
vars: map[string]any{},
|
||||
TaskTest: TaskTest{
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
fixtureTemplateData: map[string]any{},
|
||||
},
|
||||
}
|
||||
// Apply the functional options
|
||||
@@ -126,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
|
||||
@@ -218,3 +220,17 @@ func TestListDescInterpolation(t *testing.T) {
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func TestJsonListFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/json_list_format"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
FormatTaskListAsJSON: true,
|
||||
}),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
|
||||
158
go.mod
158
go.mod
@@ -1,60 +1,136 @@
|
||||
module github.com/go-task/task/v3
|
||||
|
||||
go 1.23.0
|
||||
go 1.25.10
|
||||
|
||||
require (
|
||||
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.3.1
|
||||
github.com/alecthomas/chroma/v2 v2.16.0
|
||||
github.com/Masterminds/semver/v3 v3.5.0
|
||||
github.com/alecthomas/chroma/v2 v2.24.1
|
||||
github.com/chainguard-dev/git-urls v1.0.2
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/dominikbraun/graph v0.23.0
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/go-git/go-billy/v5 v5.6.2
|
||||
github.com/go-git/go-git/v5 v5.16.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.1.0
|
||||
github.com/go-task/template v0.2.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/go-getter v1.8.6
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0
|
||||
github.com/sajari/fuzzy v1.0.0
|
||||
github.com/sebdah/goldie/v2 v2.5.5
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
golang.org/x/sync v0.13.0
|
||||
golang.org/x/term v0.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/sh/v3 v3.11.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.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.13.2-0.20260510185049-f5c6e2779117
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
cloud.google.com/go v0.123.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.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.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.4.3 // indirect
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.7 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // 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.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // 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
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.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
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // 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.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/net v0.54.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sys v0.44.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
|
||||
)
|
||||
|
||||
398
go.sum
398
go.sum
@@ -1,93 +1,198 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
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.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.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.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
||||
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
|
||||
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
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.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
|
||||
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
|
||||
github.com/alecthomas/chroma/v2 v2.16.0 h1:QC5ZMizk67+HzxFDjQ4ASjni5kWBTGiigRG1u23IGvA=
|
||||
github.com/alecthomas/chroma/v2 v2.16.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
|
||||
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
||||
github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM=
|
||||
github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI=
|
||||
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.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/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
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=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
|
||||
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
||||
github.com/clipperhouse/displaywidth v0.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/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
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 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
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/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
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.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/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
|
||||
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
|
||||
github.com/go-git/go-git/v5 v5.15.0 h1:f5Qn0W0F7ry1iN0ZwIU5m/n7/BKB4hiZfc+zlZx7ly0=
|
||||
github.com/go-git/go-git/v5 v5.15.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ=
|
||||
github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
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=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-task/template v0.1.0 h1:ym/r2G937RZA1bsgiWedNnY9e5kxDT+3YcoAnuIetTE=
|
||||
github.com/go-task/template v0.1.0/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviBE=
|
||||
github.com/go-task/template v0.2.0/go.mod h1:dbdoUb6qKnHQi1y6o+IdIrs0J4o/SEhSTA6bbzZmdtc=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
|
||||
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.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.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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
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.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -95,103 +200,130 @@ 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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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.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=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
|
||||
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
|
||||
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
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.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=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
||||
github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
|
||||
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc=
|
||||
github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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.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=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8 h1:cq+DjLAjz3ZPwh0+G571O/jMH0c0DzReDPLjQGL2/BA=
|
||||
github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8/go.mod h1:JNauIV2zopCBv/6o+umxcT3bKe8YUqYJaTZQYSYpKss=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.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.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-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.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.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.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=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
|
||||
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997 h1:3bbJwtPFh98dJ6lxRdR3eLHTH1CmR3BcU6TriIMiXjE=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997/go.mod h1:Qy/zdaMDxq9sT72Gi43K3gsV+TtTohyDO3f1cyBVwuo=
|
||||
mvdan.cc/sh/v3 v3.13.2-0.20260503214111-9e7dd28c81cf h1:3mGRe/xSr7fd9m+c5FSab/CSCtADsbdMcyhYGdVR6DY=
|
||||
mvdan.cc/sh/v3 v3.13.2-0.20260503214111-9e7dd28c81cf/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=
|
||||
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117 h1:BfzdGSjcnJBb8sPNLudpzTml8zFUxS1N0N/v9IIS6tQ=
|
||||
mvdan.cc/sh/v3 v3.13.2-0.20260510185049-f5c6e2779117/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0=
|
||||
|
||||
64
help.go
64
help.go
@@ -24,15 +24,17 @@ type ListOptions struct {
|
||||
ListAllTasks bool
|
||||
FormatTaskListAsJSON bool
|
||||
NoStatus bool
|
||||
Nested bool
|
||||
}
|
||||
|
||||
// NewListOptions creates a new ListOptions instance
|
||||
func NewListOptions(list, listAll, listAsJson, noStatus bool) ListOptions {
|
||||
func NewListOptions(list, listAll, listAsJson, noStatus, nested bool) ListOptions {
|
||||
return ListOptions{
|
||||
ListOnlyTasksWithDescriptions: list,
|
||||
ListAllTasks: listAll,
|
||||
FormatTaskListAsJSON: listAsJson,
|
||||
NoStatus: noStatus,
|
||||
Nested: nested,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +65,7 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
if o.FormatTaskListAsJSON {
|
||||
output, err := e.ToEditorOutput(tasks, o.NoStatus)
|
||||
output, err := e.ToEditorOutput(tasks, o.NoStatus, o.Nested)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -135,32 +137,17 @@ func (e *Executor) ListTaskNames(allTasks bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Taskfile, error) {
|
||||
o := &editors.Taskfile{
|
||||
Tasks: make([]editors.Task, len(tasks)),
|
||||
Location: e.Taskfile.Location,
|
||||
}
|
||||
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool) (*editors.Namespace, error) {
|
||||
var g errgroup.Group
|
||||
editorTasks := make([]editors.Task, len(tasks))
|
||||
|
||||
// Look over each task in parallel and turn it into an editor task
|
||||
for i := range tasks {
|
||||
aliases := []string{}
|
||||
if len(tasks[i].Aliases) > 0 {
|
||||
aliases = tasks[i].Aliases
|
||||
}
|
||||
g.Go(func() error {
|
||||
o.Tasks[i] = editors.Task{
|
||||
Name: tasks[i].Name(),
|
||||
Desc: tasks[i].Desc,
|
||||
Summary: tasks[i].Summary,
|
||||
Aliases: aliases,
|
||||
UpToDate: false,
|
||||
Location: &editors.Location{
|
||||
Line: tasks[i].Location.Line,
|
||||
Column: tasks[i].Location.Column,
|
||||
Taskfile: tasks[i].Location.Taskfile,
|
||||
},
|
||||
}
|
||||
editorTask := editors.NewTask(tasks[i])
|
||||
|
||||
if noStatus {
|
||||
editorTasks[i] = editorTask
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -179,10 +166,35 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
||||
return err
|
||||
}
|
||||
|
||||
o.Tasks[i].UpToDate = upToDate
|
||||
|
||||
editorTask.UpToDate = &upToDate
|
||||
editorTasks[i] = editorTask
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return o, g.Wait()
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the root namespace
|
||||
var tasksLen int
|
||||
if !nested {
|
||||
tasksLen = len(editorTasks)
|
||||
}
|
||||
rootNamespace := &editors.Namespace{
|
||||
Tasks: make([]editors.Task, tasksLen),
|
||||
Location: e.Taskfile.Location,
|
||||
}
|
||||
|
||||
// Recursively add namespaces to the root namespace or if nesting is
|
||||
// disabled add them all to the root namespace
|
||||
for i, task := range editorTasks {
|
||||
taskNamespacePath := strings.Split(task.Task, ast.NamespaceSeparator)
|
||||
if nested {
|
||||
rootNamespace.AddNamespace(taskNamespacePath, task)
|
||||
} else {
|
||||
rootNamespace.Tasks[i] = task
|
||||
}
|
||||
}
|
||||
|
||||
return rootNamespace, g.Wait()
|
||||
}
|
||||
|
||||
25
init.go
25
init.go
@@ -6,9 +6,10 @@ import (
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
const defaultTaskFilename = "Taskfile.yml"
|
||||
const defaultFilename = "Taskfile.yml"
|
||||
|
||||
//go:embed taskfile/templates/default.yml
|
||||
var DefaultTaskfile string
|
||||
@@ -20,22 +21,30 @@ var DefaultTaskfile string
|
||||
//
|
||||
// The final file path is always returned and may be different from the input path.
|
||||
func InitTaskfile(path string) (string, error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err == nil && !fi.IsDir() {
|
||||
info, err := os.Stat(path)
|
||||
if err == nil && !info.IsDir() {
|
||||
return path, errors.TaskfileAlreadyExistsError{}
|
||||
}
|
||||
|
||||
if fi != nil && fi.IsDir() {
|
||||
path = filepathext.SmartJoin(path, defaultTaskFilename)
|
||||
// path was a directory, so check if Taskfile.yml exists in it
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
if info != nil && info.IsDir() {
|
||||
// path was a directory, check if there is a Taskfile already
|
||||
if hasDefaultTaskfile(path) {
|
||||
return path, errors.TaskfileAlreadyExistsError{}
|
||||
}
|
||||
path = filepathext.SmartJoin(path, defaultFilename)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, []byte(DefaultTaskfile), 0o644); err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func hasDefaultTaskfile(dir string) bool {
|
||||
for _, name := range taskfile.DefaultTaskfiles {
|
||||
if _, err := os.Stat(filepathext.SmartJoin(dir, name)); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -64,21 +64,15 @@ get_binaries() {
|
||||
case "$PLATFORM" in
|
||||
darwin/amd64) BINARIES="task" ;;
|
||||
darwin/arm64) BINARIES="task" ;;
|
||||
darwin/armv5) BINARIES="task" ;;
|
||||
darwin/armv6) BINARIES="task" ;;
|
||||
darwin/armv7) BINARIES="task" ;;
|
||||
darwin/arm) BINARIES="task" ;;
|
||||
linux/386) BINARIES="task" ;;
|
||||
linux/amd64) BINARIES="task" ;;
|
||||
linux/arm64) BINARIES="task" ;;
|
||||
linux/armv5) BINARIES="task" ;;
|
||||
linux/armv6) BINARIES="task" ;;
|
||||
linux/armv7) BINARIES="task" ;;
|
||||
linux/arm) BINARIES="task" ;;
|
||||
windows/386) BINARIES="task" ;;
|
||||
windows/amd64) BINARIES="task" ;;
|
||||
windows/arm64) BINARIES="task" ;;
|
||||
windows/armv5) BINARIES="task" ;;
|
||||
windows/armv6) BINARIES="task" ;;
|
||||
windows/armv7) BINARIES="task" ;;
|
||||
windows/arm) BINARIES="task" ;;
|
||||
*)
|
||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
||||
exit 1
|
||||
|
||||
@@ -10,6 +10,15 @@ type Copier[T any] interface {
|
||||
DeepCopy() T
|
||||
}
|
||||
|
||||
func Scalar[T any](orig *T) *T {
|
||||
if orig == nil {
|
||||
return nil
|
||||
} else {
|
||||
v := *orig
|
||||
return &v
|
||||
}
|
||||
}
|
||||
|
||||
func Slice[T any](orig []T) []T {
|
||||
if orig == nil {
|
||||
return nil
|
||||
@@ -72,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
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
package editors
|
||||
|
||||
import (
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
type (
|
||||
// Taskfile wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
||||
Taskfile struct {
|
||||
Tasks []Task `json:"tasks"`
|
||||
Location string `json:"location"`
|
||||
// Namespace wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
||||
Namespace struct {
|
||||
Tasks []Task `json:"tasks"`
|
||||
Namespaces map[string]*Namespace `json:"namespaces,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
}
|
||||
// Task describes a single task
|
||||
Task struct {
|
||||
Name string `json:"name"`
|
||||
Task string `json:"task"`
|
||||
Desc string `json:"desc"`
|
||||
Summary string `json:"summary"`
|
||||
Aliases []string `json:"aliases"`
|
||||
UpToDate bool `json:"up_to_date"`
|
||||
UpToDate *bool `json:"up_to_date,omitempty"`
|
||||
Location *Location `json:"location"`
|
||||
}
|
||||
// Location describes a task's location in a taskfile
|
||||
@@ -22,3 +28,59 @@ type (
|
||||
Taskfile string `json:"taskfile"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewTask(task *ast.Task) Task {
|
||||
aliases := []string{}
|
||||
if len(task.Aliases) > 0 {
|
||||
aliases = task.Aliases
|
||||
}
|
||||
return Task{
|
||||
Name: task.Name(),
|
||||
Task: task.Task,
|
||||
Desc: task.Desc,
|
||||
Summary: task.Summary,
|
||||
Aliases: aliases,
|
||||
Location: &Location{
|
||||
Line: task.Location.Line,
|
||||
Column: task.Location.Column,
|
||||
Taskfile: task.Location.Taskfile,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (parent *Namespace) AddNamespace(namespacePath []string, task Task) {
|
||||
if len(namespacePath) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// If there are no child namespaces, then we have found a task and we can
|
||||
// simply add it to the current namespace
|
||||
if len(namespacePath) == 1 {
|
||||
parent.Tasks = append(parent.Tasks, task)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the key of the current namespace in the path
|
||||
namespaceKey := namespacePath[0]
|
||||
|
||||
// Add the namespace to the parent namespaces map using the namespace key
|
||||
if parent.Namespaces == nil {
|
||||
parent.Namespaces = make(map[string]*Namespace, 0)
|
||||
}
|
||||
|
||||
// Search for the current namespace in the parent namespaces map
|
||||
// If it doesn't exist, create it
|
||||
namespace, ok := parent.Namespaces[namespaceKey]
|
||||
if !ok {
|
||||
namespace = &Namespace{}
|
||||
parent.Namespaces[namespaceKey] = namespace
|
||||
}
|
||||
|
||||
// Remove the current namespace key from the namespace path.
|
||||
childNamespacePath := namespacePath[1:]
|
||||
|
||||
// If there are no child namespaces in the task name, then we have found the
|
||||
// namespace of the task and we can add it to the current namespace.
|
||||
// Otherwise, we need to go deeper
|
||||
namespace.AddNamespace(childNamespacePath, task)
|
||||
}
|
||||
|
||||
64
internal/env/env.go
vendored
64
internal/env/env.go
vendored
@@ -3,9 +3,11 @@ package env
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"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
|
||||
}
|
||||
|
||||
20
internal/execext/coreutils.go
Normal file
20
internal/execext/coreutils.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package execext
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
)
|
||||
|
||||
var useGoCoreUtils bool
|
||||
|
||||
func init() {
|
||||
// If TASK_CORE_UTILS is set to either true or false, respect that.
|
||||
// By default, enable on Windows only.
|
||||
if v, err := strconv.ParseBool(env.GetTaskEnv("CORE_UTILS")); err == nil {
|
||||
useGoCoreUtils = v
|
||||
} else {
|
||||
useGoCoreUtils = runtime.GOOS == "windows"
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"mvdan.cc/sh/moreinterp/coreutils"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
@@ -59,7 +59,7 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
r, err := interp.New(
|
||||
interp.Params(params...),
|
||||
interp.Env(expand.ListEnviron(environ...)),
|
||||
interp.ExecHandlers(execHandler),
|
||||
interp.ExecHandlers(execHandlers()...),
|
||||
interp.OpenHandler(openHandler),
|
||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
||||
dirOption(opts.Dir),
|
||||
@@ -106,25 +106,17 @@ func ExpandLiteral(s string) (string, error) {
|
||||
if s == "" {
|
||||
return "", nil
|
||||
}
|
||||
s = escape(s)
|
||||
p := syntax.NewParser()
|
||||
var words []*syntax.Word
|
||||
err := p.Words(strings.NewReader(s), func(w *syntax.Word) bool {
|
||||
words = append(words, w)
|
||||
return true
|
||||
})
|
||||
word, err := p.Document(strings.NewReader(s))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(words) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
cfg := &expand.Config{
|
||||
Env: expand.FuncEnviron(os.Getenv),
|
||||
ReadDir2: os.ReadDir,
|
||||
GlobStar: true,
|
||||
}
|
||||
return expand.Literal(cfg, words[0])
|
||||
return expand.Literal(cfg, word)
|
||||
}
|
||||
|
||||
// ExpandFields is a wrapper around [expand.Fields]. It will escape the input
|
||||
@@ -135,23 +127,26 @@ func ExpandFields(s string) ([]string, error) {
|
||||
s = escape(s)
|
||||
p := syntax.NewParser()
|
||||
var words []*syntax.Word
|
||||
err := p.Words(strings.NewReader(s), func(w *syntax.Word) bool {
|
||||
for w, err := range p.WordsSeq(strings.NewReader(s)) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
words = append(words, w)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := &expand.Config{
|
||||
Env: expand.FuncEnviron(os.Getenv),
|
||||
ReadDir2: os.ReadDir,
|
||||
GlobStar: true,
|
||||
NullGlob: true,
|
||||
}
|
||||
return expand.Fields(cfg, words...)
|
||||
}
|
||||
|
||||
func execHandler(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
|
||||
return interp.DefaultExecHandler(15 * time.Second)
|
||||
func execHandlers() (handlers []func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc) {
|
||||
if useGoCoreUtils {
|
||||
handlers = append(handlers, coreutils.ExecHandler)
|
||||
}
|
||||
return handlers
|
||||
}
|
||||
|
||||
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package fingerprint
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
@@ -50,7 +51,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
|
||||
}
|
||||
|
||||
@@ -32,6 +32,28 @@ func (checker *TimestampChecker) IsUpToDate(t *ast.Task) (bool, error) {
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -164,7 +163,7 @@ func TestIsTaskUpToDate(t *testing.T) {
|
||||
}
|
||||
|
||||
result, err := IsTaskUpToDate(
|
||||
context.Background(),
|
||||
t.Context(),
|
||||
tt.task,
|
||||
WithStatusChecker(mockStatusChecker),
|
||||
WithSourcesChecker(mockSourcesChecker),
|
||||
|
||||
@@ -8,14 +8,17 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/internal/sort"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
"github.com/go-task/task/v3/taskrc"
|
||||
taskrcast "github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
|
||||
const usage = `Usage: task [flags...] [task...]
|
||||
@@ -51,12 +54,14 @@ var (
|
||||
TaskSort string
|
||||
Status bool
|
||||
NoStatus bool
|
||||
Nested bool
|
||||
Insecure bool
|
||||
Force bool
|
||||
ForceAll bool
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
DisableFuzzy bool
|
||||
AssumeYes bool
|
||||
Dry bool
|
||||
Summary bool
|
||||
@@ -68,13 +73,20 @@ var (
|
||||
Output ast.Output
|
||||
Color bool
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
Global bool
|
||||
Experiments bool
|
||||
Download bool
|
||||
Offline bool
|
||||
TrustedHosts []string
|
||||
ClearCache bool
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
RemoteCacheDir string
|
||||
CACert string
|
||||
Cert string
|
||||
CertKey string
|
||||
Interactive bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -95,7 +107,9 @@ func init() {
|
||||
|
||||
// Parse the experiments
|
||||
dir = cmp.Or(dir, filepath.Dir(entrypoint))
|
||||
experiments.Parse(dir)
|
||||
|
||||
config, _ := taskrc.GetConfig(dir)
|
||||
experiments.ParseWithConfig(dir, config)
|
||||
|
||||
// Parse the rest of the flags
|
||||
log.SetFlags(0)
|
||||
@@ -104,10 +118,7 @@ func init() {
|
||||
log.Print(usage)
|
||||
pflag.PrintDefaults()
|
||||
}
|
||||
offline, err := strconv.ParseBool(cmp.Or(env.GetTaskEnv("OFFLINE"), "false"))
|
||||
if err != nil {
|
||||
offline = false
|
||||
}
|
||||
|
||||
pflag.BoolVar(&Version, "version", false, "Show Task version.")
|
||||
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
|
||||
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
|
||||
@@ -118,13 +129,16 @@ func init() {
|
||||
pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].")
|
||||
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(&Insecure, "insecure", false, "Forces Task to download Taskfiles over insecure connections.")
|
||||
pflag.BoolVar(&Nested, "nested", false, "Nest namespaces when listing tasks as JSON")
|
||||
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", false, "Enables verbose mode.")
|
||||
pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.")
|
||||
pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.")
|
||||
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.")
|
||||
@@ -133,9 +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", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
|
||||
pflag.IntVarP(&Concurrency, "concurrency", "C", 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, "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.")
|
||||
|
||||
@@ -150,13 +165,40 @@ 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", offline, "Forces Task to only use local or cached Taskfiles.")
|
||||
pflag.DurationVar(&Timeout, "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", 0, "Expiry duration for cached remote Taskfiles.")
|
||||
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 > 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
|
||||
color.NoColor = true
|
||||
} else if os.Getenv("FORCE_COLOR") != "" || isCI() {
|
||||
Color = true
|
||||
color.NoColor = false // Force colors even without TTY
|
||||
}
|
||||
// Otherwise, let fatih/color auto-detect TTY
|
||||
} else {
|
||||
// Explicit config: sync with fatih/color
|
||||
color.NoColor = !Color
|
||||
}
|
||||
}
|
||||
|
||||
// isCI returns true if running in a CI environment
|
||||
func isCI() bool {
|
||||
ci, _ := strconv.ParseBool(os.Getenv("CI"))
|
||||
return ci
|
||||
}
|
||||
|
||||
func Validate() error {
|
||||
@@ -196,6 +238,15 @@ func Validate() error {
|
||||
return errors.New("task: --no-status only applies to --json with --list or --list-all")
|
||||
}
|
||||
|
||||
if Nested && !ListJson {
|
||||
return errors.New("task: --nested only applies to --json with --list or --list-all")
|
||||
}
|
||||
|
||||
// Validate certificate flags
|
||||
if (Cert != "" && CertKey == "") || (Cert == "" && CertKey != "") {
|
||||
return errors.New("task: --cert and --cert-key must be provided together")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -234,12 +285,19 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
task.WithInsecure(Insecure),
|
||||
task.WithDownload(Download),
|
||||
task.WithOffline(Offline),
|
||||
task.WithTrustedHosts(TrustedHosts),
|
||||
task.WithTimeout(Timeout),
|
||||
task.WithCacheExpiryDuration(CacheExpiryDuration),
|
||||
task.WithRemoteCacheDir(RemoteCacheDir),
|
||||
task.WithCACert(CACert),
|
||||
task.WithCert(Cert),
|
||||
task.WithCertKey(CertKey),
|
||||
task.WithWatch(Watch),
|
||||
task.WithVerbose(Verbose),
|
||||
task.WithSilent(Silent),
|
||||
task.WithDisableFuzzy(DisableFuzzy),
|
||||
task.WithAssumeYes(AssumeYes),
|
||||
task.WithInteractive(Interactive),
|
||||
task.WithDry(Dry || Status),
|
||||
task.WithSummary(Summary),
|
||||
task.WithParallel(Parallel),
|
||||
@@ -249,5 +307,49 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
task.WithOutputStyle(Output),
|
||||
task.WithTaskSorter(sorter),
|
||||
task.WithVersionCheck(true),
|
||||
task.WithFailfast(Failfast),
|
||||
)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -37,51 +37,87 @@ func DefaultDir(entrypoint, dir string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Search will look for files with the given possible filenames using the given
|
||||
// entrypoint and directory. If the entrypoint is set, it will check if the
|
||||
// ResolveDir returns an absolute path to the directory that the task should be
|
||||
// run in. If the entrypoint and dir are BOTH set, then the Taskfile will not
|
||||
// sit inside the directory specified by dir and we should ensure that the dir
|
||||
// is absolute. Otherwise, the dir will always be the parent directory of the
|
||||
// resolved entrypoint, so we should return that parent directory.
|
||||
func ResolveDir(entrypoint, resolvedEntrypoint, dir string) (string, error) {
|
||||
if entrypoint != "" && dir != "" {
|
||||
return filepath.Abs(dir)
|
||||
}
|
||||
return filepath.Dir(resolvedEntrypoint), nil
|
||||
}
|
||||
|
||||
// Search looks for files with the given possible filenames using the given
|
||||
// entrypoint and directory. If the entrypoint is set, it checks if the
|
||||
// entrypoint matches a file or if it matches a directory containing one of the
|
||||
// possible filenames. Otherwise, it will walk up the file tree starting at the
|
||||
// given directory and perform a search in each directory for the possible
|
||||
// possible filenames. Otherwise, it walks up the file tree starting at the
|
||||
// given directory and performs a search in each directory for the possible
|
||||
// filenames until it finds a match or reaches the root directory. If the
|
||||
// entrypoint and directory are both empty, it will default the directory to the
|
||||
// current working directory and perform a recursive search starting there. If a
|
||||
// match is found, the absolute path to the file will be returned with its
|
||||
// directory. If no match is found, an error will be returned.
|
||||
func Search(entrypoint, dir string, possibleFilenames []string) (string, string, error) {
|
||||
// entrypoint and directory are both empty, it defaults the directory to the
|
||||
// current working directory and performs a recursive search starting there. If
|
||||
// a match is found, the absolute path to the file is returned with its
|
||||
// directory. If no match is found, an error is returned.
|
||||
func Search(entrypoint, dir string, possibleFilenames []string) (string, error) {
|
||||
var err error
|
||||
if entrypoint != "" {
|
||||
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", err
|
||||
}
|
||||
if dir == "" {
|
||||
dir = filepath.Dir(entrypoint)
|
||||
} else {
|
||||
dir, err = filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
return entrypoint, dir, nil
|
||||
return entrypoint, nil
|
||||
}
|
||||
if dir == "" {
|
||||
dir, err = os.Getwd()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
entrypoint, err = SearchPathRecursively(dir, possibleFilenames)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", err
|
||||
}
|
||||
dir = filepath.Dir(entrypoint)
|
||||
return entrypoint, dir, nil
|
||||
return entrypoint, nil
|
||||
}
|
||||
|
||||
// Search will check if a file at the given path exists or not. If it does, it
|
||||
// will return the path to it. If it does not, it will search for any files at
|
||||
// the given path with any of the given possible names. If any of these match a
|
||||
// file, the first matching path will be returned. If no files are found, an
|
||||
// SearchAll looks for files with the given possible filenames using the given
|
||||
// entrypoint and directory. If the entrypoint is set, it checks if the
|
||||
// entrypoint matches a file or if it matches a directory containing one of the
|
||||
// possible filenames and add it to a list of matches. It then walks up the file
|
||||
// tree starting at the given directory and performs a search in each directory
|
||||
// for the possible filenames until it finds a match or reaches the root
|
||||
// directory. If the entrypoint and directory are both empty, it defaults the
|
||||
// directory to the current working directory and performs a recursive search
|
||||
// starting there. If matches are found, the absolute path to each file is added
|
||||
// to the list and returned.
|
||||
func SearchAll(entrypoint, dir string, possibleFilenames []string) ([]string, error) {
|
||||
var err error
|
||||
var entrypoints []string
|
||||
if entrypoint != "" {
|
||||
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entrypoints = append(entrypoints, entrypoint)
|
||||
}
|
||||
if dir == "" {
|
||||
dir, err = os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
paths, err := SearchNPathRecursively(dir, possibleFilenames, -1)
|
||||
// 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,
|
||||
// it will return the path to it. If it does not, it will search for any files
|
||||
// at the given path with any of the given possible names. If any of these match
|
||||
// a file, the first matching path will be returned. If no files are found, an
|
||||
// error will be returned.
|
||||
func SearchPath(path string, possibleFilenames []string) (string, error) {
|
||||
// Get file info about the path
|
||||
@@ -111,36 +147,62 @@ func SearchPath(path string, possibleFilenames []string) (string, error) {
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
|
||||
// SearchRecursively will check if a file at the given path exists by calling
|
||||
// the exists function. If a file is not found, it will walk up the directory
|
||||
// tree calling the Search function until it finds a file or reaches the root
|
||||
// directory. On supported operating systems, it will also check if the user ID
|
||||
// of the directory changes and abort if it does.
|
||||
// SearchPathRecursively walks up the directory tree starting at the given
|
||||
// path, calling the Search function in each directory until it finds a matching
|
||||
// file or reaches the root directory. On supported operating systems, it will
|
||||
// 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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SearchNPathRecursively walks up the directory tree starting at the given
|
||||
// path, calling the Search function in each directory and adding each matching
|
||||
// file that it finds to a list until it reaches the root directory or the
|
||||
// length of the list exceeds n. On supported operating systems, it will also
|
||||
// check if the user ID of the directory changes and abort if it does.
|
||||
func SearchNPathRecursively(path string, possibleFilenames []string, n int) ([]string, error) {
|
||||
var paths []string
|
||||
|
||||
owner, err := sysinfo.Owner(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
|
||||
for n == -1 || len(paths) < n {
|
||||
fpath, err := SearchPath(path, possibleFilenames)
|
||||
if err == nil {
|
||||
return fpath, nil
|
||||
paths = append(paths, fpath)
|
||||
}
|
||||
|
||||
// Get the parent path/user id
|
||||
parentPath := filepath.Dir(path)
|
||||
parentOwner, err := sysinfo.Owner(parentPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
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) {
|
||||
return "", os.ErrNotExist
|
||||
// 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
|
||||
path = parentPath
|
||||
}
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
@@ -71,35 +71,30 @@ func TestSearch(t *testing.T) {
|
||||
dir string
|
||||
possibleFilenames []string
|
||||
expectedEntrypoint string
|
||||
expectedDir string
|
||||
}{
|
||||
{
|
||||
name: "find foo.txt using relative entrypoint",
|
||||
entrypoint: "./testdata/foo.txt",
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using absolute entrypoint",
|
||||
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using relative dir",
|
||||
dir: "./testdata",
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using absolute dir",
|
||||
dir: filepath.Join(wd, "testdata"),
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using relative dir and relative entrypoint",
|
||||
@@ -107,7 +102,6 @@ func TestSearch(t *testing.T) {
|
||||
dir: "./testdata/some/other/dir",
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
|
||||
},
|
||||
{
|
||||
name: "find fs.go using no entrypoint or dir",
|
||||
@@ -115,7 +109,6 @@ func TestSearch(t *testing.T) {
|
||||
dir: "",
|
||||
possibleFilenames: []string{"fs.go"},
|
||||
expectedEntrypoint: filepath.Join(wd, "fs.go"),
|
||||
expectedDir: wd,
|
||||
},
|
||||
{
|
||||
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
||||
@@ -123,30 +116,109 @@ func TestSearch(t *testing.T) {
|
||||
dir: "",
|
||||
possibleFilenames: []string{"Taskfile.yml"},
|
||||
expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
||||
expectedDir: filepath.Join(wd, "..", ".."),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt first if listed first in possible filenames",
|
||||
entrypoint: "./testdata",
|
||||
possibleFilenames: []string{"foo.txt", "bar.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find bar.txt first if listed first in possible filenames",
|
||||
entrypoint: "./testdata",
|
||||
possibleFilenames: []string{"bar.txt", "foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
entrypoint, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedEntrypoint, entrypoint)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
entrypoint string
|
||||
resolvedEntrypoint string
|
||||
dir string
|
||||
expectedDir string
|
||||
}{
|
||||
{
|
||||
name: "find foo.txt using relative entrypoint",
|
||||
entrypoint: "./testdata/foo.txt",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using absolute entrypoint",
|
||||
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using relative dir",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
dir: "./testdata",
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using absolute dir",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
dir: filepath.Join(wd, "testdata"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using relative dir and relative entrypoint",
|
||||
entrypoint: "./testdata/foo.txt",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
dir: "./testdata/some/other/dir",
|
||||
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
|
||||
},
|
||||
{
|
||||
name: "find fs.go using no entrypoint or dir",
|
||||
entrypoint: "",
|
||||
resolvedEntrypoint: filepath.Join(wd, "fs.go"),
|
||||
dir: "",
|
||||
expectedDir: wd,
|
||||
},
|
||||
{
|
||||
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
||||
entrypoint: "",
|
||||
resolvedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
||||
dir: "",
|
||||
expectedDir: filepath.Join(wd, "..", ".."),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt first if listed first in possible filenames",
|
||||
entrypoint: "./testdata",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find bar.txt first if listed first in possible filenames",
|
||||
entrypoint: "./testdata",
|
||||
resolvedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
entrypoint, dir, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames)
|
||||
dir, err := ResolveDir(tt.entrypoint, tt.resolvedEntrypoint, tt.dir)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedEntrypoint, entrypoint)
|
||||
require.Equal(t, tt.expectedDir, dir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package fsnotifyext
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
@@ -11,7 +10,6 @@ import (
|
||||
type Deduper struct {
|
||||
w *fsnotify.Watcher
|
||||
waitTime time.Duration
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewDeduper(w *fsnotify.Watcher, waitTime time.Duration) *Deduper {
|
||||
@@ -21,31 +19,28 @@ func NewDeduper(w *fsnotify.Watcher, waitTime time.Duration) *Deduper {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Deduper) GetChan() chan fsnotify.Event {
|
||||
// GetChan returns a chan of deduplicated [fsnotify.Event].
|
||||
//
|
||||
// [fsnotify.Chmod] operations will be skipped.
|
||||
func (d *Deduper) GetChan() <-chan fsnotify.Event {
|
||||
channel := make(chan fsnotify.Event)
|
||||
timers := make(map[string]*time.Timer)
|
||||
|
||||
go func() {
|
||||
timers := make(map[string]*time.Timer)
|
||||
for {
|
||||
event, ok := <-d.w.Events
|
||||
switch {
|
||||
case !ok:
|
||||
return
|
||||
case event.Op == fsnotify.Chmod:
|
||||
case event.Has(fsnotify.Chmod):
|
||||
continue
|
||||
}
|
||||
|
||||
d.mutex.Lock()
|
||||
timer, ok := timers[event.String()]
|
||||
d.mutex.Unlock()
|
||||
|
||||
if !ok {
|
||||
timer = time.AfterFunc(math.MaxInt64, func() { channel <- event })
|
||||
timer.Stop()
|
||||
|
||||
d.mutex.Lock()
|
||||
timers[event.String()] = timer
|
||||
d.mutex.Unlock()
|
||||
}
|
||||
|
||||
timer.Reset(d.waitTime)
|
||||
|
||||
@@ -20,5 +20,5 @@ func Name(t *ast.Task) (string, error) {
|
||||
|
||||
func Hash(t *ast.Task) (string, error) {
|
||||
h, err := hashstructure.Hash(t, hashstructure.FormatV2, nil)
|
||||
return fmt.Sprintf("%s:%d", t.Task, h), err
|
||||
return fmt.Sprintf("%s:%s:%d", t.Location.Taskfile, t.LocalName(), h), err
|
||||
}
|
||||
|
||||
211
internal/input/input.go
Normal file
211
internal/input/input.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"charm.land/bubbles/v2/textinput"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
var ErrCancelled = errors.New("prompt cancelled")
|
||||
|
||||
var (
|
||||
promptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6")).Bold(true) // cyan bold
|
||||
cursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6")).Bold(true) // cyan bold
|
||||
selectedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("2")).Bold(true) // green bold
|
||||
dimStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8")) // gray
|
||||
)
|
||||
|
||||
// Prompter handles interactive variable prompting
|
||||
type Prompter struct {
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
// Text prompts the user for a text value
|
||||
func (p *Prompter) Text(varName string) (string, error) {
|
||||
m := newTextModel(varName)
|
||||
|
||||
prog := tea.NewProgram(m,
|
||||
tea.WithInput(p.Stdin),
|
||||
tea.WithOutput(p.Stderr),
|
||||
)
|
||||
|
||||
result, err := prog.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
model := result.(textModel)
|
||||
if model.cancelled {
|
||||
return "", ErrCancelled
|
||||
}
|
||||
|
||||
return model.value, nil
|
||||
}
|
||||
|
||||
// Select prompts the user to select from a list of options
|
||||
func (p *Prompter) Select(varName string, options []string) (string, error) {
|
||||
if len(options) == 0 {
|
||||
return "", errors.New("no options provided")
|
||||
}
|
||||
|
||||
m := newSelectModel(varName, options)
|
||||
|
||||
prog := tea.NewProgram(m,
|
||||
tea.WithInput(p.Stdin),
|
||||
tea.WithOutput(p.Stderr),
|
||||
)
|
||||
|
||||
result, err := prog.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
model := result.(selectModel)
|
||||
if model.cancelled {
|
||||
return "", ErrCancelled
|
||||
}
|
||||
|
||||
return model.options[model.cursor], nil
|
||||
}
|
||||
|
||||
// Prompt prompts for a variable value, using Select if enum is provided, Text otherwise
|
||||
func (p *Prompter) Prompt(varName string, enum []string) (string, error) {
|
||||
if len(enum) > 0 {
|
||||
return p.Select(varName, enum)
|
||||
}
|
||||
return p.Text(varName)
|
||||
}
|
||||
|
||||
// textModel is the Bubble Tea model for text input
|
||||
type textModel struct {
|
||||
varName string
|
||||
textInput textinput.Model
|
||||
value string
|
||||
cancelled bool
|
||||
done bool
|
||||
}
|
||||
|
||||
func newTextModel(varName string) textModel {
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = ""
|
||||
ti.CharLimit = 256
|
||||
ti.SetWidth(40)
|
||||
ti.Focus()
|
||||
|
||||
return textModel{
|
||||
varName: varName,
|
||||
textInput: ti,
|
||||
}
|
||||
}
|
||||
|
||||
func (m textModel) Init() tea.Cmd {
|
||||
return tea.Batch(m.textInput.Focus(), textinput.Blink)
|
||||
}
|
||||
|
||||
func (m textModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyPressMsg:
|
||||
switch msg.Keystroke() {
|
||||
case "ctrl+c", "escape":
|
||||
m.cancelled = true
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
case "enter":
|
||||
m.value = m.textInput.Value()
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.textInput, cmd = m.textInput.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m textModel) View() tea.View {
|
||||
if m.done {
|
||||
return tea.NewView("")
|
||||
}
|
||||
|
||||
prompt := promptStyle.Render(fmt.Sprintf("? Enter value for %s: ", m.varName))
|
||||
return tea.NewView(prompt + m.textInput.View() + "\n")
|
||||
}
|
||||
|
||||
// selectModel is the Bubble Tea model for selection
|
||||
type selectModel struct {
|
||||
varName string
|
||||
options []string
|
||||
cursor int
|
||||
cancelled bool
|
||||
done bool
|
||||
}
|
||||
|
||||
func newSelectModel(varName string, options []string) selectModel {
|
||||
return selectModel{
|
||||
varName: varName,
|
||||
options: options,
|
||||
cursor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (m selectModel) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m selectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyPressMsg:
|
||||
switch msg.Keystroke() {
|
||||
case "ctrl+c", "escape":
|
||||
m.cancelled = true
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
case "up", "shift+tab", "k":
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
}
|
||||
case "down", "tab", "j":
|
||||
if m.cursor < len(m.options)-1 {
|
||||
m.cursor++
|
||||
}
|
||||
case "enter":
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m selectModel) View() tea.View {
|
||||
if m.done {
|
||||
return tea.NewView("")
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(promptStyle.Render(fmt.Sprintf("? Select value for %s:", m.varName)))
|
||||
b.WriteString("\n")
|
||||
|
||||
for i, opt := range m.options {
|
||||
if i == m.cursor {
|
||||
b.WriteString(cursorStyle.Render("❯ "))
|
||||
b.WriteString(selectedStyle.Render(opt))
|
||||
} else {
|
||||
b.WriteString(" " + opt)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
b.WriteString(dimStyle.Render(" (↑/↓ to move, enter to select, esc to cancel)"))
|
||||
|
||||
return tea.NewView(b.String())
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package logger
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -12,8 +11,8 @@ import (
|
||||
"github.com/fatih/color"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/internal/term"
|
||||
)
|
||||
|
||||
@@ -43,6 +42,12 @@ type (
|
||||
PrintFunc func(io.Writer, string, ...any)
|
||||
)
|
||||
|
||||
func None() PrintFunc {
|
||||
c := color.New()
|
||||
c.DisableColor()
|
||||
return c.FprintfFunc()
|
||||
}
|
||||
|
||||
func Default() PrintFunc {
|
||||
return color.New(attrsReset...).FprintfFunc()
|
||||
}
|
||||
@@ -96,10 +101,6 @@ func BrightRed() PrintFunc {
|
||||
}
|
||||
|
||||
func envColor(name string, defaultColor color.Attribute) []color.Attribute {
|
||||
if os.Getenv("FORCE_COLOR") != "" {
|
||||
color.NoColor = false
|
||||
}
|
||||
|
||||
// Fetch the environment variable
|
||||
override := env.GetTaskEnv(name)
|
||||
|
||||
@@ -149,7 +150,7 @@ func (l *Logger) FOutf(w io.Writer, color Color, s string, args ...any) {
|
||||
s, args = "%s", []any{s}
|
||||
}
|
||||
if !l.Color {
|
||||
color = Default
|
||||
color = None
|
||||
}
|
||||
print := color()
|
||||
print(w, s, args...)
|
||||
@@ -168,7 +169,7 @@ func (l *Logger) Errf(color Color, s string, args ...any) {
|
||||
s, args = "%s", []any{s}
|
||||
}
|
||||
if !l.Color {
|
||||
color = Default
|
||||
color = None
|
||||
}
|
||||
print := color()
|
||||
print(l.Stderr, s, args...)
|
||||
|
||||
@@ -24,7 +24,6 @@ func (g Group) WrapWriter(stdOut, _ io.Writer, _ string, cache *templater.Cache)
|
||||
if g.ErrorOnly && err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gw.close()
|
||||
}
|
||||
}
|
||||
@@ -40,14 +39,22 @@ func (gw *groupWriter) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (gw *groupWriter) close() error {
|
||||
if gw.buff.Len() == 0 {
|
||||
// don't print begin/end messages if there's no buffered entries
|
||||
switch {
|
||||
case gw.buff.Len() == 0:
|
||||
return nil
|
||||
}
|
||||
if _, err := io.WriteString(gw.writer, gw.begin); err != nil {
|
||||
case gw.begin == "" && gw.end == "":
|
||||
_, err := io.Copy(gw.writer, &gw.buff)
|
||||
return err
|
||||
default:
|
||||
_, err := io.Copy(gw.writer, gw.combinedBuff())
|
||||
return err
|
||||
}
|
||||
gw.buff.WriteString(gw.end)
|
||||
_, err := io.Copy(gw.writer, &gw.buff)
|
||||
return err
|
||||
}
|
||||
|
||||
func (gw *groupWriter) combinedBuff() io.Reader {
|
||||
var b bytes.Buffer
|
||||
_, _ = b.WriteString(gw.begin)
|
||||
_, _ = io.Copy(&b, &gw.buff)
|
||||
_, _ = b.WriteString(gw.end)
|
||||
return &b
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package summary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
@@ -29,6 +31,9 @@ func PrintSpaceBetweenSummaries(l *logger.Logger, i int) {
|
||||
func PrintTask(l *logger.Logger, t *ast.Task) {
|
||||
printTaskName(l, t)
|
||||
printTaskDescribingText(t, l)
|
||||
printTaskVars(l, t)
|
||||
printTaskEnv(l, t)
|
||||
printTaskRequires(l, t)
|
||||
printTaskDependencies(l, t)
|
||||
printTaskAliases(l, t)
|
||||
printTaskCommands(l, t)
|
||||
@@ -118,3 +123,172 @@ func printTaskCommands(l *logger.Logger, t *ast.Task) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTaskVars(l *logger.Logger, t *ast.Task) {
|
||||
if t.Vars == nil || t.Vars.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
osEnvVars := getEnvVarNames()
|
||||
|
||||
taskfileEnvVars := make(map[string]bool)
|
||||
if t.Env != nil {
|
||||
for key := range t.Env.All() {
|
||||
taskfileEnvVars[key] = true
|
||||
}
|
||||
}
|
||||
|
||||
hasNonEnvVars := false
|
||||
for key := range t.Vars.All() {
|
||||
if !isEnvVar(key, osEnvVars) && !taskfileEnvVars[key] {
|
||||
hasNonEnvVars = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasNonEnvVars {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf(logger.Default, "\n")
|
||||
l.Outf(logger.Default, "vars:\n")
|
||||
|
||||
for key, value := range t.Vars.All() {
|
||||
// Only display variables that are not from OS environment or Taskfile env
|
||||
if !isEnvVar(key, osEnvVars) && !taskfileEnvVars[key] {
|
||||
formattedValue := formatVarValue(value)
|
||||
l.Outf(logger.Yellow, " %s: %s\n", key, formattedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTaskEnv(l *logger.Logger, t *ast.Task) {
|
||||
if t.Env == nil || t.Env.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
envVars := getEnvVarNames()
|
||||
|
||||
hasNonEnvVars := false
|
||||
for key := range t.Env.All() {
|
||||
if !isEnvVar(key, envVars) {
|
||||
hasNonEnvVars = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasNonEnvVars {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf(logger.Default, "\n")
|
||||
l.Outf(logger.Default, "env:\n")
|
||||
|
||||
for key, value := range t.Env.All() {
|
||||
// Only display variables that are not from OS environment
|
||||
if !isEnvVar(key, envVars) {
|
||||
formattedValue := formatVarValue(value)
|
||||
l.Outf(logger.Yellow, " %s: %s\n", key, formattedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// formatVarValue formats a variable value based on its type.
|
||||
// Handles static values, shell commands (sh:), references (ref:), and maps.
|
||||
func formatVarValue(v ast.Var) string {
|
||||
// Shell command - check this first before Value
|
||||
// because dynamic vars may have both Sh and an empty Value
|
||||
if v.Sh != nil {
|
||||
return fmt.Sprintf("sh: %s", *v.Sh)
|
||||
}
|
||||
|
||||
// Reference
|
||||
if v.Ref != "" {
|
||||
return fmt.Sprintf("ref: %s", v.Ref)
|
||||
}
|
||||
|
||||
// Static value
|
||||
if v.Value != nil {
|
||||
// Check if it's a map or complex type
|
||||
if m, ok := v.Value.(map[string]any); ok {
|
||||
return formatMap(m, 4)
|
||||
}
|
||||
// Simple string value
|
||||
return fmt.Sprintf(`"%v"`, v.Value)
|
||||
}
|
||||
|
||||
return `""`
|
||||
}
|
||||
|
||||
// formatMap formats a map value with proper indentation for YAML.
|
||||
func formatMap(m map[string]any, indent int) string {
|
||||
if len(m) == 0 {
|
||||
return "{}"
|
||||
}
|
||||
|
||||
var result strings.Builder
|
||||
result.WriteString("\n")
|
||||
spaces := strings.Repeat(" ", indent)
|
||||
|
||||
for k, v := range m {
|
||||
result.WriteString(fmt.Sprintf("%s%s: %v\n", spaces, k, v)) //nolint:staticcheck
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func printTaskRequires(l *logger.Logger, t *ast.Task) {
|
||||
if t.Requires == nil || len(t.Requires.Vars) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
l.Outf(logger.Default, "\n")
|
||||
l.Outf(logger.Default, "requires:\n")
|
||||
l.Outf(logger.Default, " vars:\n")
|
||||
|
||||
for _, v := range t.Requires.Vars {
|
||||
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.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 {
|
||||
l.Outf(logger.Yellow, " - %s\n", v.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getEnvVarNames() map[string]bool {
|
||||
envMap := make(map[string]bool)
|
||||
for _, e := range os.Environ() {
|
||||
parts := strings.SplitN(e, "=", 2)
|
||||
if len(parts) > 0 {
|
||||
envMap[parts[0]] = true
|
||||
}
|
||||
}
|
||||
return envMap
|
||||
}
|
||||
|
||||
// isEnvVar checks if a variable is from OS environment or auto-generated by Task.
|
||||
func isEnvVar(key string, envVars map[string]bool) bool {
|
||||
// Filter out auto-generated Task variables
|
||||
if strings.HasPrefix(key, "TASK_") ||
|
||||
strings.HasPrefix(key, "CLI_") ||
|
||||
strings.HasPrefix(key, "ROOT_") ||
|
||||
key == "TASK" ||
|
||||
key == "TASKFILE" ||
|
||||
key == "TASKFILE_DIR" ||
|
||||
key == "USER_WORKING_DIR" ||
|
||||
key == "ALIAS" ||
|
||||
key == "MATCH" ||
|
||||
key == "PATH_LIST_SEPARATOR" ||
|
||||
key == "FILE_PATH_SEPARATOR" {
|
||||
return true
|
||||
}
|
||||
return envVars[key]
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@ 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/v3"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
|
||||
@@ -18,58 +23,30 @@ var templateFuncs template.FuncMap
|
||||
|
||||
func init() {
|
||||
taskFuncs := template.FuncMap{
|
||||
"OS": func() string { return runtime.GOOS },
|
||||
"ARCH": func() string { return runtime.GOARCH },
|
||||
"numCPU": func() int { return runtime.NumCPU() },
|
||||
"catLines": func(s string) string {
|
||||
s = strings.ReplaceAll(s, "\r\n", " ")
|
||||
return strings.ReplaceAll(s, "\n", " ")
|
||||
},
|
||||
"splitLines": func(s string) []string {
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||
return strings.Split(s, "\n")
|
||||
},
|
||||
"fromSlash": func(path string) string {
|
||||
return filepath.FromSlash(path)
|
||||
},
|
||||
"toSlash": func(path string) string {
|
||||
return filepath.ToSlash(path)
|
||||
},
|
||||
"exeExt": func() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return ".exe"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"shellQuote": func(str string) (string, error) {
|
||||
return syntax.Quote(str, syntax.LangBash)
|
||||
},
|
||||
"splitArgs": func(s string) ([]string, error) {
|
||||
return shell.Fields(s, nil)
|
||||
},
|
||||
// IsSH is deprecated.
|
||||
"IsSH": func() bool { return true },
|
||||
"joinPath": func(elem ...string) string {
|
||||
return filepath.Join(elem...)
|
||||
},
|
||||
"relPath": func(basePath, targetPath string) (string, error) {
|
||||
return filepath.Rel(basePath, targetPath)
|
||||
},
|
||||
"merge": func(base map[string]any, v ...map[string]any) map[string]any {
|
||||
cap := len(v)
|
||||
for _, m := range v {
|
||||
cap += len(m)
|
||||
}
|
||||
result := make(map[string]any, cap)
|
||||
maps.Copy(result, base)
|
||||
for _, m := range v {
|
||||
maps.Copy(result, m)
|
||||
}
|
||||
return result
|
||||
},
|
||||
"spew": func(v any) string {
|
||||
return spew.Sdump(v)
|
||||
},
|
||||
"OS": goos,
|
||||
"ARCH": goarch,
|
||||
"numCPU": runtime.NumCPU,
|
||||
"catLines": catLines,
|
||||
"splitLines": splitLines,
|
||||
"fromSlash": filepath.FromSlash,
|
||||
"toSlash": filepath.ToSlash,
|
||||
"exeExt": exeExt,
|
||||
"shellQuote": shellQuote,
|
||||
"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,
|
||||
"mustFromYaml": mustFromYaml,
|
||||
"toYaml": toYaml,
|
||||
"mustToYaml": mustToYaml,
|
||||
"uuid": uuid.New,
|
||||
"randIntN": rand.IntN,
|
||||
}
|
||||
|
||||
// aliases
|
||||
@@ -83,3 +60,86 @@ func init() {
|
||||
templateFuncs = template.FuncMap(sprig.TxtFuncMap())
|
||||
maps.Copy(templateFuncs, taskFuncs)
|
||||
}
|
||||
|
||||
func goos() string {
|
||||
return runtime.GOOS
|
||||
}
|
||||
|
||||
func goarch() string {
|
||||
return runtime.GOARCH
|
||||
}
|
||||
|
||||
func catLines(s string) string {
|
||||
s = strings.ReplaceAll(s, "\r\n", " ")
|
||||
return strings.ReplaceAll(s, "\n", " ")
|
||||
}
|
||||
|
||||
func splitLines(s string) []string {
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||
return strings.Split(s, "\n")
|
||||
}
|
||||
|
||||
func exeExt() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return ".exe"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func shellQuote(str string) (string, error) {
|
||||
return syntax.Quote(str, syntax.LangBash)
|
||||
}
|
||||
|
||||
func splitArgs(s string) ([]string, error) {
|
||||
return shell.Fields(s, nil)
|
||||
}
|
||||
|
||||
// Deprecated: now always returns true
|
||||
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 {
|
||||
cap += len(m)
|
||||
}
|
||||
result := make(map[string]any, cap)
|
||||
maps.Copy(result, base)
|
||||
for _, m := range v {
|
||||
maps.Copy(result, m)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func fromYaml(v string) any {
|
||||
output, _ := mustFromYaml(v)
|
||||
return output
|
||||
}
|
||||
|
||||
func mustFromYaml(v string) (any, error) {
|
||||
var output any
|
||||
err := yaml.Unmarshal([]byte(v), &output)
|
||||
return output, err
|
||||
}
|
||||
|
||||
func toYaml(v any) string {
|
||||
output, _ := yaml.Marshal(v)
|
||||
return string(output)
|
||||
}
|
||||
|
||||
func mustToYaml(v any) (string, error) {
|
||||
output, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ import (
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/template"
|
||||
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
"github.com/go-task/template"
|
||||
)
|
||||
|
||||
// Cache is a help struct that allow us to call "replaceX" funcs multiple
|
||||
@@ -67,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()
|
||||
@@ -81,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.43.0
|
||||
3.51.1
|
||||
|
||||
32
package-lock.json
generated
32
package-lock.json
generated
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.43.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.26.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@go-task/go-npm": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@go-task/go-npm": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.2.0.tgz",
|
||||
"integrity": "sha512-vQbdtBvesHm8EUFHX8QKg4rbBodmu9VsAXH1ozpbiN5jdTMOYHTCMM31EurAYmY+rNNtxJQ4JGy6t383RPlqbw==",
|
||||
"bin": {
|
||||
"go-npm": "bin/index.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@go-task/go-npm": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.2.0.tgz",
|
||||
"integrity": "sha512-vQbdtBvesHm8EUFHX8QKg4rbBodmu9VsAXH1ozpbiN5jdTMOYHTCMM31EurAYmY+rNNtxJQ4JGy6t383RPlqbw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
34
package.json
34
package.json
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.43.0",
|
||||
"description": "A task runner / simpler Make alternative written in Go",
|
||||
"scripts": {
|
||||
"postinstall": "go-npm install",
|
||||
"preuninstall": "go-npm uninstall"
|
||||
},
|
||||
"goBinary": {
|
||||
"name": "task",
|
||||
"path": "./bin",
|
||||
"url": "https://github.com/go-task/task/releases/download/v{{version}}/task_{{platform}}_{{arch}}{{archive_ext}}"
|
||||
},
|
||||
"files": [],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/go-task/task.git"
|
||||
},
|
||||
"keywords": [
|
||||
"task",
|
||||
"taskfile",
|
||||
"build-tool",
|
||||
"task-runner"
|
||||
],
|
||||
"author": "The Task authors",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/go-task/task/issues"
|
||||
},
|
||||
"homepage": "https://taskfile.dev",
|
||||
"dependencies": {
|
||||
"@go-task/go-npm": "^0.2.0"
|
||||
}
|
||||
}
|
||||
186
requires.go
186
requires.go
@@ -4,35 +4,180 @@ import (
|
||||
"slices"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/input"
|
||||
"github.com/go-task/task/v3/internal/term"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func (e *Executor) areTaskRequiredVarsSet(t *ast.Task) error {
|
||||
if t.Requires == nil || len(t.Requires.Vars) == 0 {
|
||||
func (e *Executor) canPrompt() bool {
|
||||
return e.Interactive && (e.AssumeTerm || term.IsTerminal())
|
||||
}
|
||||
|
||||
func (e *Executor) newPrompter() *input.Prompter {
|
||||
return &input.Prompter{
|
||||
Stdin: e.Stdin,
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
}
|
||||
}
|
||||
|
||||
// promptDepsVars traverses the dependency tree, collects all missing required
|
||||
// variables, and prompts for them upfront. This is used for deps which execute
|
||||
// in parallel, so all prompts must happen before execution to avoid interleaving.
|
||||
// Prompted values are stored in e.promptedVars for injection into task calls.
|
||||
func (e *Executor) promptDepsVars(calls []*Call) error {
|
||||
if !e.canPrompt() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var missingVars []errors.MissingVar
|
||||
for _, requiredVar := range t.Requires.Vars {
|
||||
_, ok := t.Vars.Get(requiredVar.Name)
|
||||
if !ok {
|
||||
missingVars = append(missingVars, errors.MissingVar{
|
||||
Name: requiredVar.Name,
|
||||
AllowedValues: requiredVar.Enum,
|
||||
})
|
||||
// Collect all missing vars from the dependency tree
|
||||
visited := make(map[string]bool)
|
||||
varsMap := make(map[string]*ast.VarsWithValidation)
|
||||
|
||||
var collect func(call *Call) error
|
||||
collect = func(call *Call) error {
|
||||
compiledTask, err := e.FastCompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range getMissingRequiredVars(compiledTask) {
|
||||
if _, exists := varsMap[v.Name]; !exists {
|
||||
varsMap[v.Name] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Check visited AFTER collecting vars to handle duplicate task calls with different vars
|
||||
if visited[call.Task] {
|
||||
return nil
|
||||
}
|
||||
visited[call.Task] = true
|
||||
|
||||
for _, dep := range compiledTask.Deps {
|
||||
depCall := &Call{
|
||||
Task: dep.Task,
|
||||
Vars: dep.Vars,
|
||||
Silent: dep.Silent,
|
||||
}
|
||||
if err := collect(depCall); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, call := range calls {
|
||||
if err := collect(call); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingVars) > 0 {
|
||||
return &errors.TaskMissingRequiredVarsError{
|
||||
TaskName: t.Name(),
|
||||
MissingVars: missingVars,
|
||||
if len(varsMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
prompter := e.newPrompter()
|
||||
e.promptedVars = ast.NewVars()
|
||||
|
||||
for _, v := range varsMap {
|
||||
value, err := prompter.Prompt(v.Name, getEnumValues(v.Enum))
|
||||
if err != nil {
|
||||
if errors.Is(err, input.ErrCancelled) {
|
||||
return &errors.TaskCancelledByUserError{TaskName: "interactive prompt"}
|
||||
}
|
||||
return err
|
||||
}
|
||||
e.promptedVars.Set(v.Name, ast.Var{Value: value})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// promptTaskVars prompts for any missing required vars from a single task.
|
||||
// Used for sequential task calls (cmds) where we can prompt just-in-time.
|
||||
// Returns true if any vars were prompted (caller should recompile the task).
|
||||
func (e *Executor) promptTaskVars(t *ast.Task, call *Call) (bool, error) {
|
||||
if !e.canPrompt() || t.Requires == nil || len(t.Requires.Vars) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Find missing vars, excluding already prompted ones
|
||||
var missing []*ast.VarsWithValidation
|
||||
for _, v := range getMissingRequiredVars(t) {
|
||||
if e.promptedVars != nil {
|
||||
if _, ok := e.promptedVars.Get(v.Name); ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
missing = append(missing, v)
|
||||
}
|
||||
|
||||
if len(missing) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
prompter := e.newPrompter()
|
||||
|
||||
for _, v := range missing {
|
||||
value, err := prompter.Prompt(v.Name, getEnumValues(v.Enum))
|
||||
if err != nil {
|
||||
if errors.Is(err, input.ErrCancelled) {
|
||||
return false, &errors.TaskCancelledByUserError{TaskName: t.Name()}
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Add to call.Vars for recompilation
|
||||
if call.Vars == nil {
|
||||
call.Vars = ast.NewVars()
|
||||
}
|
||||
call.Vars.Set(v.Name, ast.Var{Value: value})
|
||||
|
||||
// Cache for reuse by other tasks
|
||||
if e.promptedVars == nil {
|
||||
e.promptedVars = ast.NewVars()
|
||||
}
|
||||
e.promptedVars.Set(v.Name, ast.Var{Value: value})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// getMissingRequiredVars returns required vars that are not set in the task's vars.
|
||||
func getMissingRequiredVars(t *ast.Task) []*ast.VarsWithValidation {
|
||||
if t.Requires == nil {
|
||||
return nil
|
||||
}
|
||||
var missing []*ast.VarsWithValidation
|
||||
for _, v := range t.Requires.Vars {
|
||||
if _, ok := t.Vars.Get(v.Name); !ok {
|
||||
missing = append(missing, v)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
}
|
||||
|
||||
func (e *Executor) areTaskRequiredVarsSet(t *ast.Task) error {
|
||||
missing := getMissingRequiredVars(t)
|
||||
if len(missing) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
missingVars := make([]errors.MissingVar, len(missing))
|
||||
for i, v := range missing {
|
||||
missingVars[i] = errors.MissingVar{
|
||||
Name: v.Name,
|
||||
AllowedValues: getEnumValues(v.Enum),
|
||||
}
|
||||
}
|
||||
|
||||
return &errors.TaskMissingRequiredVarsError{
|
||||
TaskName: t.Name(),
|
||||
MissingVars: missingVars,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) areTaskRequiredVarsAllowedValuesSet(t *ast.Task) error {
|
||||
if t.Requires == nil || len(t.Requires.Vars) == 0 {
|
||||
return nil
|
||||
@@ -42,15 +187,15 @@ 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,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(notAllowedValuesVars) > 0 {
|
||||
@@ -62,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
|
||||
}
|
||||
|
||||
34
setup.go
34
setup.go
@@ -35,7 +35,6 @@ func (e *Executor) Setup() error {
|
||||
if err := e.readTaskfile(node); err != nil {
|
||||
return err
|
||||
}
|
||||
e.setupFuzzyModel()
|
||||
e.setupStdFiles()
|
||||
if err := e.setupOutput(); err != nil {
|
||||
return err
|
||||
@@ -55,11 +54,21 @@ func (e *Executor) Setup() error {
|
||||
}
|
||||
|
||||
func (e *Executor) getRootNode() (taskfile.Node, error) {
|
||||
node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout)
|
||||
node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout,
|
||||
taskfile.WithCACert(e.CACert),
|
||||
taskfile.WithCert(e.Cert),
|
||||
taskfile.WithCertKey(e.CertKey),
|
||||
)
|
||||
var taskNotFoundError errors.TaskfileNotFoundError
|
||||
if errors.As(err, &taskNotFoundError) {
|
||||
taskNotFoundError.AskInit = true
|
||||
return nil, taskNotFoundError
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.Dir = node.Dir()
|
||||
e.Entrypoint = node.Location()
|
||||
return node, err
|
||||
}
|
||||
|
||||
@@ -76,8 +85,12 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
|
||||
taskfile.WithInsecure(e.Insecure),
|
||||
taskfile.WithDownload(e.Download),
|
||||
taskfile.WithOffline(e.Offline),
|
||||
taskfile.WithTrustedHosts(e.TrustedHosts),
|
||||
taskfile.WithTempDir(e.TempDir.Remote),
|
||||
taskfile.WithCacheExpiryDuration(e.CacheExpiryDuration),
|
||||
taskfile.WithReaderCACert(e.CACert),
|
||||
taskfile.WithReaderCert(e.Cert),
|
||||
taskfile.WithReaderCertKey(e.CertKey),
|
||||
taskfile.WithDebugFunc(debugFunc),
|
||||
taskfile.WithPromptFunc(promptFunc),
|
||||
)
|
||||
@@ -95,7 +108,7 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
|
||||
}
|
||||
|
||||
func (e *Executor) setupFuzzyModel() {
|
||||
if e.Taskfile != nil {
|
||||
if e.Taskfile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -104,6 +117,9 @@ func (e *Executor) setupFuzzyModel() {
|
||||
|
||||
var words []string
|
||||
for name, task := range e.Taskfile.Tasks.All(nil) {
|
||||
if task.Internal {
|
||||
continue
|
||||
}
|
||||
words = append(words, name)
|
||||
words = slices.Concat(words, task.Aliases)
|
||||
}
|
||||
@@ -142,16 +158,16 @@ func (e *Executor) setupTempDir() error {
|
||||
}
|
||||
}
|
||||
|
||||
remoteDir := env.GetTaskEnv("REMOTE_DIR")
|
||||
if remoteDir != "" {
|
||||
if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") {
|
||||
remoteTempDir, err := execext.ExpandLiteral(remoteDir)
|
||||
// RemoteCacheDir from taskrc/env can override the remote cache directory
|
||||
if e.RemoteCacheDir != "" {
|
||||
if filepath.IsAbs(e.RemoteCacheDir) || strings.HasPrefix(e.RemoteCacheDir, "~") {
|
||||
remoteCacheDir, err := execext.ExpandLiteral(e.RemoteCacheDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.TempDir.Remote = remoteTempDir
|
||||
e.TempDir.Remote = remoteCacheDir
|
||||
} else {
|
||||
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task")
|
||||
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, e.RemoteCacheDir)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
213
task.go
213
task.go
@@ -6,8 +6,12 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
@@ -19,9 +23,6 @@ import (
|
||||
"github.com/go-task/task/v3/internal/summary"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -73,14 +74,21 @@ func (e *Executor) Run(ctx context.Context, calls ...*Call) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prompt for all required vars from deps upfront (parallel execution)
|
||||
if err := e.promptDepsVars(calls); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
regularCalls, watchCalls, err := e.splitRegularAndWatchCalls(calls...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
g := &errgroup.Group{}
|
||||
if e.Failfast {
|
||||
g, ctx = errgroup.WithContext(ctx)
|
||||
}
|
||||
for _, c := range regularCalls {
|
||||
c := c
|
||||
if e.Parallel {
|
||||
g.Go(func() error { return e.RunTask(ctx, c) })
|
||||
} else {
|
||||
@@ -113,11 +121,24 @@ func (e *Executor) splitRegularAndWatchCalls(calls ...*Call) (regularCalls []*Ca
|
||||
regularCalls = append(regularCalls, c)
|
||||
}
|
||||
}
|
||||
return
|
||||
return regularCalls, watchCalls, err
|
||||
}
|
||||
|
||||
// RunTask runs a task by its name
|
||||
func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
// Inject prompted vars into call if available
|
||||
if e.promptedVars != nil {
|
||||
if call.Vars == nil {
|
||||
call.Vars = ast.NewVars()
|
||||
}
|
||||
for name, v := range e.promptedVars.All() {
|
||||
// Only inject if not already set in call
|
||||
if _, ok := call.Vars.Get(name); !ok {
|
||||
call.Vars.Set(name, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t, err := e.FastCompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -127,8 +148,12 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := e.areTaskRequiredVarsSet(t); err != nil {
|
||||
return err
|
||||
// Check required vars early (before template compilation) if we can't prompt.
|
||||
// This gives a clear "missing required variables" error instead of a template error.
|
||||
if !e.canPrompt() {
|
||||
if err := e.areTaskRequiredVarsSet(t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
t, err = e.CompiledTask(call)
|
||||
@@ -136,6 +161,35 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if condition after CompiledTask so dynamic variables are resolved
|
||||
if strings.TrimSpace(t.If) != "" {
|
||||
if err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: t.If,
|
||||
Dir: t.Dir,
|
||||
Env: env.Get(t),
|
||||
}); err != nil {
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: if condition not met - skipped: %q\n", call.Task)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Prompt for missing required vars after if check (avoid prompting if task won't run)
|
||||
prompted, err := e.promptTaskVars(t, call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if prompted {
|
||||
// Recompile with the new vars
|
||||
t, err = e.FastCompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.areTaskRequiredVarsSet(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.areTaskRequiredVarsAllowedValuesSet(t); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,7 +204,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
release := e.acquireConcurrencyLimit()
|
||||
defer release()
|
||||
|
||||
return e.startExecution(ctx, t, func(ctx context.Context) error {
|
||||
if err = e.startExecution(ctx, t, func(ctx context.Context) error {
|
||||
e.Logger.VerboseErrf(logger.Magenta, "task: %q started\n", call.Task)
|
||||
if err := e.runDeps(ctx, t); err != nil {
|
||||
return err
|
||||
@@ -172,7 +226,6 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
if t.Method != "" {
|
||||
method = t.Method
|
||||
}
|
||||
|
||||
upToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
|
||||
fingerprint.WithMethod(method),
|
||||
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
||||
@@ -184,8 +237,12 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
}
|
||||
|
||||
if upToDate && preCondMet {
|
||||
if e.Verbose || (!call.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
|
||||
e.Logger.Errf(logger.Magenta, "task: Task %q is up to date\n", t.Name())
|
||||
if e.Verbose || (!call.Silent && !t.IsSilent() && !e.Taskfile.Silent && !e.Silent) {
|
||||
name := t.Name()
|
||||
if e.OutputStyle.Name == "prefixed" {
|
||||
name = t.Prefix
|
||||
}
|
||||
e.Logger.Errf(logger.Magenta, "task: Task %q is up to date\n", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -211,7 +268,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
|
||||
for i := range t.Cmds {
|
||||
if t.Cmds[i].Defer {
|
||||
defer e.runDeferred(t, call, i, &deferredExitCode)
|
||||
defer e.runDeferred(t, call, i, t.Vars, &deferredExitCode)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -220,25 +277,25 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v\n", err2)
|
||||
}
|
||||
|
||||
exitCode, isExitError := interp.IsExitStatus(err)
|
||||
if isExitError {
|
||||
var exitCode interp.ExitStatus
|
||||
if errors.As(err, &exitCode) {
|
||||
if t.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
|
||||
continue
|
||||
}
|
||||
deferredExitCode = exitCode
|
||||
deferredExitCode = uint8(exitCode)
|
||||
}
|
||||
|
||||
if call.Indirect {
|
||||
return err
|
||||
}
|
||||
|
||||
return &errors.TaskRunError{TaskName: t.Task, Err: err}
|
||||
return err
|
||||
}
|
||||
}
|
||||
e.Logger.VerboseErrf(logger.Magenta, "task: %q finished\n", call.Task)
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return &errors.TaskRunError{TaskName: t.Name(), Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) mkdir(t *ast.Task) error {
|
||||
@@ -259,13 +316,15 @@ func (e *Executor) mkdir(t *ast.Task) error {
|
||||
}
|
||||
|
||||
func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
g := &errgroup.Group{}
|
||||
if e.Failfast || t.Failfast {
|
||||
g, ctx = errgroup.WithContext(ctx)
|
||||
}
|
||||
|
||||
reacquire := e.releaseConcurrencyLimit()
|
||||
defer reacquire()
|
||||
|
||||
for _, d := range t.Deps {
|
||||
d := d
|
||||
g.Go(func() error {
|
||||
err := e.RunTask(ctx, &Call{Task: d.Task, Vars: d.Vars, Silent: d.Silent, Indirect: true})
|
||||
if err != nil {
|
||||
@@ -278,17 +337,11 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, deferredExitCode *uint8) {
|
||||
func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, deferredExitCode *uint8) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
origTask, err := e.GetTask(call)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := t.Cmds[i]
|
||||
vars, _ := e.Compiler.GetVariables(origTask, call)
|
||||
cache := &templater.Cache{Vars: vars}
|
||||
extra := map[string]any{}
|
||||
|
||||
@@ -298,6 +351,7 @@ func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, deferredExitCode
|
||||
|
||||
cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
|
||||
cmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
|
||||
cmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra)
|
||||
cmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
|
||||
|
||||
if err := e.runCommand(ctx, t, call, i); err != nil {
|
||||
@@ -308,23 +362,37 @@ func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, deferredExitCode
|
||||
func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i int) error {
|
||||
cmd := t.Cmds[i]
|
||||
|
||||
// Check if condition for any command type
|
||||
if strings.TrimSpace(cmd.If) != "" {
|
||||
if err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: cmd.If,
|
||||
Dir: t.Dir,
|
||||
Env: env.Get(t),
|
||||
}); err != nil {
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: [%s] if condition not met - skipped\n", t.Name())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case cmd.Task != "":
|
||||
reacquire := e.releaseConcurrencyLimit()
|
||||
defer reacquire()
|
||||
|
||||
err := e.RunTask(ctx, &Call{Task: cmd.Task, Vars: cmd.Vars, Silent: cmd.Silent, Indirect: true})
|
||||
if err != nil {
|
||||
return err
|
||||
var exitCode interp.ExitStatus
|
||||
if errors.As(err, &exitCode) && cmd.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] task error ignored: %v\n", t.Name(), err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
case cmd.Cmd != "":
|
||||
if !shouldRunOnCurrentPlatform(cmd.Platforms) {
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: [%s] %s not for current platform - ignored\n", t.Name(), cmd.Cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
if e.Verbose || (!call.Silent && !cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
|
||||
if e.Verbose || (!call.Silent && !cmd.Silent && !t.IsSilent() && !e.Taskfile.Silent && !e.Silent) {
|
||||
e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmd.Cmd)
|
||||
}
|
||||
|
||||
@@ -356,7 +424,8 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
|
||||
if closeErr := closer(err); closeErr != nil {
|
||||
e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr)
|
||||
}
|
||||
if _, isExitError := interp.IsExitStatus(err); isExitError && cmd.IgnoreError {
|
||||
var exitCode interp.ExitStatus
|
||||
if errors.As(err, &exitCode) && cmd.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v\n", t.Name(), err)
|
||||
return nil
|
||||
}
|
||||
@@ -372,7 +441,7 @@ func (e *Executor) startExecution(ctx context.Context, t *ast.Task, execute func
|
||||
return err
|
||||
}
|
||||
|
||||
if h == "" {
|
||||
if h == "" || t.Watch {
|
||||
return execute(ctx)
|
||||
}
|
||||
|
||||
@@ -400,19 +469,40 @@ func (e *Executor) startExecution(ctx context.Context, t *ast.Task, execute func
|
||||
}
|
||||
|
||||
// FindMatchingTasks returns a list of tasks that match the given call. A task
|
||||
// matches a call if its name is equal to the call's task name or if it matches
|
||||
// matches a call if its name is equal to the call's task name, or one of aliases, or if it matches
|
||||
// a wildcard pattern. The function returns a list of MatchingTask structs, each
|
||||
// containing a task and a list of wildcards that were matched.
|
||||
func (e *Executor) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
// If multiple tasks match due to aliases, a TaskNameConflictError is returned.
|
||||
func (e *Executor) FindMatchingTasks(call *Call) ([]*MatchingTask, error) {
|
||||
if call == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
var matchingTasks []*MatchingTask
|
||||
// If there is a direct match, return it
|
||||
if task, ok := e.Taskfile.Tasks.Get(call.Task); ok {
|
||||
matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil})
|
||||
return matchingTasks
|
||||
return matchingTasks, nil
|
||||
}
|
||||
var aliasedTasks []string
|
||||
for task := range e.Taskfile.Tasks.Values(nil) {
|
||||
if slices.Contains(task.Aliases, call.Task) {
|
||||
aliasedTasks = append(aliasedTasks, task.Task)
|
||||
matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil})
|
||||
}
|
||||
}
|
||||
|
||||
if len(aliasedTasks) == 1 {
|
||||
return matchingTasks, nil
|
||||
}
|
||||
|
||||
// If we found multiple tasks
|
||||
if len(aliasedTasks) > 1 {
|
||||
return nil, &errors.TaskNameConflictError{
|
||||
Call: call.Task,
|
||||
TaskNames: aliasedTasks,
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt a wildcard match
|
||||
for _, value := range e.Taskfile.Tasks.All(nil) {
|
||||
if match, wildcards := value.WildcardMatch(call.Task); match {
|
||||
@@ -422,7 +512,7 @@ func (e *Executor) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
})
|
||||
}
|
||||
}
|
||||
return matchingTasks
|
||||
return matchingTasks, nil
|
||||
}
|
||||
|
||||
// GetTask will return the task with the name matching the given call from the taskfile.
|
||||
@@ -430,7 +520,11 @@ func (e *Executor) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
// If multiple tasks contain the same alias or no matches are found an error is returned.
|
||||
func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
|
||||
// Search for a matching task
|
||||
matchingTasks := e.FindMatchingTasks(call)
|
||||
matchingTasks, err := e.FindMatchingTasks(call)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(matchingTasks) > 0 {
|
||||
if call.Vars == nil {
|
||||
call.Vars = ast.NewVars()
|
||||
@@ -439,35 +533,18 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
|
||||
return matchingTasks[0].Task, nil
|
||||
}
|
||||
|
||||
// If didn't find one, search for a task with a matching alias
|
||||
var matchingTask *ast.Task
|
||||
var aliasedTasks []string
|
||||
for task := range e.Taskfile.Tasks.Values(nil) {
|
||||
if slices.Contains(task.Aliases, call.Task) {
|
||||
aliasedTasks = append(aliasedTasks, task.Task)
|
||||
matchingTask = task
|
||||
}
|
||||
}
|
||||
// If we found multiple tasks
|
||||
if len(aliasedTasks) > 1 {
|
||||
return nil, &errors.TaskNameConflictError{
|
||||
Call: call.Task,
|
||||
TaskNames: aliasedTasks,
|
||||
}
|
||||
}
|
||||
// If we found no tasks
|
||||
if len(aliasedTasks) == 0 {
|
||||
didYouMean := ""
|
||||
didYouMean := ""
|
||||
if !e.DisableFuzzy {
|
||||
e.fuzzyModelOnce.Do(e.setupFuzzyModel)
|
||||
if e.fuzzyModel != nil {
|
||||
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
||||
}
|
||||
return nil, &errors.TaskNotFoundError{
|
||||
TaskName: call.Task,
|
||||
DidYouMean: didYouMean,
|
||||
}
|
||||
}
|
||||
|
||||
return matchingTask, nil
|
||||
return nil, &errors.TaskNotFoundError{
|
||||
TaskName: call.Task,
|
||||
DidYouMean: didYouMean,
|
||||
}
|
||||
}
|
||||
|
||||
type FilterFunc func(task *ast.Task) bool
|
||||
@@ -499,7 +576,7 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) {
|
||||
// Compile the list of tasks
|
||||
for i := range tasks {
|
||||
g.Go(func() error {
|
||||
compiledTask, err := e.FastCompiledTask(&Call{Task: tasks[i].Task})
|
||||
compiledTask, err := e.CompiledTaskForTaskList(&Call{Task: tasks[i].Task})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
541
task_test.go
541
task_test.go
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
@@ -12,6 +12,7 @@ type Cmd struct {
|
||||
Cmd string
|
||||
Task string
|
||||
For *For
|
||||
If string
|
||||
Silent bool
|
||||
Set []string
|
||||
Shopt []string
|
||||
@@ -29,6 +30,7 @@ func (c *Cmd) DeepCopy() *Cmd {
|
||||
Cmd: c.Cmd,
|
||||
Task: c.Task,
|
||||
For: c.For.DeepCopy(),
|
||||
If: c.If,
|
||||
Silent: c.Silent,
|
||||
Set: deepcopy.Slice(c.Set),
|
||||
Shopt: deepcopy.Slice(c.Shopt),
|
||||
@@ -55,6 +57,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
Cmd string
|
||||
Task string
|
||||
For *For
|
||||
If string
|
||||
Silent bool
|
||||
Set []string
|
||||
Shopt []string
|
||||
@@ -92,7 +95,9 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
c.Task = cmdStruct.Task
|
||||
c.Vars = cmdStruct.Vars
|
||||
c.For = cmdStruct.For
|
||||
c.If = cmdStruct.If
|
||||
c.Silent = cmdStruct.Silent
|
||||
c.IgnoreError = cmdStruct.IgnoreError
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -100,6 +105,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
if cmdStruct.Cmd != "" {
|
||||
c.Cmd = cmdStruct.Cmd
|
||||
c.For = cmdStruct.For
|
||||
c.If = cmdStruct.If
|
||||
c.Silent = cmdStruct.Silent
|
||||
c.Set = cmdStruct.Set
|
||||
c.Shopt = cmdStruct.Shopt
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"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 (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
@@ -24,6 +24,7 @@ type (
|
||||
AdvancedImport bool
|
||||
Vars *Vars
|
||||
Flatten bool
|
||||
Checksum string
|
||||
}
|
||||
// Includes is an ordered map of namespaces to includes.
|
||||
Includes struct {
|
||||
@@ -165,6 +166,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
||||
Aliases []string
|
||||
Excludes []string
|
||||
Vars *Vars
|
||||
Checksum string
|
||||
}
|
||||
if err := node.Decode(&includedTaskfile); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
@@ -178,6 +180,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
||||
include.AdvancedImport = true
|
||||
include.Vars = includedTaskfile.Vars
|
||||
include.Flatten = includedTaskfile.Flatten
|
||||
include.Checksum = includedTaskfile.Checksum
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -200,5 +203,7 @@ func (include *Include) DeepCopy() *Include {
|
||||
AdvancedImport: include.AdvancedImport,
|
||||
Vars: include.Vars.DeepCopy(),
|
||||
Flatten: include.Flatten,
|
||||
Aliases: deepcopy.Slice(include.Aliases),
|
||||
Checksum: include.Checksum,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"iter"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"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 (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"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"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"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"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"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"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// Task represents a task
|
||||
type Task struct {
|
||||
Task string
|
||||
Task string `hash:"ignore"`
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Label string
|
||||
@@ -32,58 +32,74 @@ type Task struct {
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Silent *bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
Prefix string `hash:"ignore"`
|
||||
IgnoreError bool
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
If string
|
||||
Watch bool
|
||||
Location *Location
|
||||
Failfast bool
|
||||
// Populated during merging
|
||||
Namespace string
|
||||
Namespace string `hash:"ignore"`
|
||||
IncludeVars *Vars
|
||||
IncludedTaskfileVars *Vars
|
||||
|
||||
FullName string `hash:"ignore"`
|
||||
}
|
||||
|
||||
func (t *Task) Name() string {
|
||||
if t.Label != "" {
|
||||
return t.Label
|
||||
}
|
||||
if t.FullName != "" {
|
||||
return t.FullName
|
||||
}
|
||||
return t.Task
|
||||
}
|
||||
|
||||
func (t *Task) LocalName() string {
|
||||
name := t.Task
|
||||
name := t.FullName
|
||||
name = strings.TrimPrefix(name, t.Namespace)
|
||||
name = strings.TrimPrefix(name, ":")
|
||||
return name
|
||||
}
|
||||
|
||||
// IsSilent returns true if the task has silent mode explicitly enabled.
|
||||
// Returns false if Silent is nil (not set) or explicitly set to false.
|
||||
func (t *Task) IsSilent() bool {
|
||||
return t.Silent != nil && *t.Silent
|
||||
}
|
||||
|
||||
// WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values.
|
||||
func (t *Task) WildcardMatch(name string) (bool, []string) {
|
||||
// Convert the name into a regex string
|
||||
regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(t.Task, "*", "(.*)"))
|
||||
regex := regexp.MustCompile(regexStr)
|
||||
wildcards := regex.FindStringSubmatch(name)
|
||||
wildcardCount := strings.Count(t.Task, "*")
|
||||
names := append([]string{t.Task}, t.Aliases...)
|
||||
|
||||
// If there are no wildcards, return false
|
||||
if len(wildcards) == 0 {
|
||||
return false, nil
|
||||
for _, taskName := range names {
|
||||
regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(taskName, "*", "(.*)"))
|
||||
regex := regexp.MustCompile(regexStr)
|
||||
wildcards := regex.FindStringSubmatch(name)
|
||||
|
||||
if len(wildcards) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove the first match, which is the full string
|
||||
wildcards = wildcards[1:]
|
||||
wildcardCount := strings.Count(taskName, "*")
|
||||
|
||||
if len(wildcards) != wildcardCount {
|
||||
continue
|
||||
}
|
||||
|
||||
return true, wildcards
|
||||
}
|
||||
|
||||
// Remove the first match, which is the full string
|
||||
wildcards = wildcards[1:]
|
||||
|
||||
// If there are more/less wildcards than matches, return false
|
||||
if len(wildcards) != wildcardCount {
|
||||
return false, wildcards
|
||||
}
|
||||
|
||||
return true, wildcards
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
@@ -128,7 +144,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Silent *bool `yaml:"silent,omitempty"`
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
@@ -136,8 +152,10 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
If string
|
||||
Requires *Requires
|
||||
Watch bool
|
||||
Failfast bool
|
||||
}
|
||||
if err := node.Decode(&task); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
@@ -166,7 +184,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
t.Vars = task.Vars
|
||||
t.Env = task.Env
|
||||
t.Dotenv = task.Dotenv
|
||||
t.Silent = task.Silent
|
||||
t.Silent = deepcopy.Scalar(task.Silent)
|
||||
t.Interactive = task.Interactive
|
||||
t.Internal = task.Internal
|
||||
t.Method = task.Method
|
||||
@@ -174,8 +192,10 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
t.IgnoreError = task.IgnoreError
|
||||
t.Run = task.Run
|
||||
t.Platforms = task.Platforms
|
||||
t.If = task.If
|
||||
t.Requires = task.Requires
|
||||
t.Watch = task.Watch
|
||||
t.Failfast = task.Failfast
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -207,7 +227,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
Vars: t.Vars.DeepCopy(),
|
||||
Env: t.Env.DeepCopy(),
|
||||
Dotenv: deepcopy.Slice(t.Dotenv),
|
||||
Silent: t.Silent,
|
||||
Silent: deepcopy.Scalar(t.Silent),
|
||||
Interactive: t.Interactive,
|
||||
Internal: t.Internal,
|
||||
Method: t.Method,
|
||||
@@ -217,9 +237,13 @@ func (t *Task) DeepCopy() *Task {
|
||||
IncludeVars: t.IncludeVars.DeepCopy(),
|
||||
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
||||
Platforms: deepcopy.Slice(t.Platforms),
|
||||
If: t.If,
|
||||
Location: t.Location.DeepCopy(),
|
||||
Requires: t.Requires.DeepCopy(),
|
||||
Namespace: t.Namespace,
|
||||
FullName: t.FullName,
|
||||
Watch: t.Watch,
|
||||
Failfast: t.Failfast,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
@@ -59,6 +59,14 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
||||
if t1.Tasks == nil {
|
||||
t1.Tasks = NewTasks()
|
||||
}
|
||||
if t2.Silent {
|
||||
for _, t := range t2.Tasks.All(nil) {
|
||||
if t.Silent == nil {
|
||||
v := true
|
||||
t.Silent = &v
|
||||
}
|
||||
}
|
||||
}
|
||||
t1.Vars.Merge(t2.Vars, include)
|
||||
t1.Env.Merge(t2.Env, include)
|
||||
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
@@ -244,8 +244,8 @@ func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
|
||||
}
|
||||
|
||||
func taskNameWithNamespace(taskName string, namespace string) string {
|
||||
if strings.HasPrefix(taskName, NamespaceSeparator) {
|
||||
return strings.TrimPrefix(taskName, NamespaceSeparator)
|
||||
if after, ok := strings.CutPrefix(taskName, NamespaceSeparator); ok {
|
||||
return after
|
||||
}
|
||||
return fmt.Sprintf("%s%s%s", namespace, NamespaceSeparator, taskName)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
@@ -18,7 +18,10 @@ type Var struct {
|
||||
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
key := node.Content[0].Value
|
||||
key := "<none>"
|
||||
if len(node.Content) > 0 {
|
||||
key = node.Content[0].Value
|
||||
}
|
||||
switch key {
|
||||
case "sh", "ref", "map":
|
||||
var m struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
@@ -113,12 +113,12 @@ func (vars *Vars) ToCacheMap() (m map[string]any) {
|
||||
m[k] = v.Value
|
||||
}
|
||||
}
|
||||
return
|
||||
return m
|
||||
}
|
||||
|
||||
// Merge loops over other and merges it values with the variables in vars. If
|
||||
// the include parameter is not nil and its it is an advanced import, the
|
||||
// directory is set set to the value of the include parameter.
|
||||
// directory is set to the value of the include parameter.
|
||||
func (vars *Vars) Merge(other *Vars, include *Include) {
|
||||
if vars == nil || vars.om == nil || other == nil {
|
||||
return
|
||||
@@ -133,6 +133,35 @@ func (vars *Vars) Merge(other *Vars, include *Include) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReverseMerge merges other variables with the existing variables in vars, but
|
||||
// keeps the other variables first in order. If the include parameter is not
|
||||
// nil and it is an advanced import, the directory is set to the value of the
|
||||
// include parameter.
|
||||
func (vars *Vars) ReverseMerge(other *Vars, include *Include) {
|
||||
if vars == nil || vars.om == nil || other == nil || other.om == nil {
|
||||
return
|
||||
}
|
||||
|
||||
newOM := orderedmap.NewOrderedMap[string, Var]()
|
||||
|
||||
other.mutex.RLock()
|
||||
for pair := other.om.Front(); pair != nil; pair = pair.Next() {
|
||||
val := pair.Value
|
||||
if include != nil && include.AdvancedImport {
|
||||
val.Dir = include.Dir
|
||||
}
|
||||
newOM.Set(pair.Key, val)
|
||||
}
|
||||
other.mutex.RUnlock()
|
||||
|
||||
vars.mutex.Lock()
|
||||
for pair := vars.om.Front(); pair != nil; pair = pair.Next() {
|
||||
newOM.Set(pair.Key, pair.Value)
|
||||
}
|
||||
vars.om = newOM
|
||||
vars.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (vs *Vars) DeepCopy() *Vars {
|
||||
if vs == nil {
|
||||
return nil
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
giturls "github.com/chainguard-dev/git-urls"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/fsext"
|
||||
)
|
||||
|
||||
@@ -17,6 +17,8 @@ type Node interface {
|
||||
Parent() Node
|
||||
Location() string
|
||||
Dir() string
|
||||
Checksum() string
|
||||
Verify(checksum string) bool
|
||||
ResolveEntrypoint(entrypoint string) (string, error)
|
||||
ResolveDir(dir string) (string, error)
|
||||
}
|
||||
@@ -32,13 +34,14 @@ func NewRootNode(
|
||||
dir string,
|
||||
insecure bool,
|
||||
timeout time.Duration,
|
||||
opts ...NodeOption,
|
||||
) (Node, error) {
|
||||
dir = fsext.DefaultDir(entrypoint, dir)
|
||||
// If the entrypoint is "-", we read from stdin
|
||||
if entrypoint == "-" {
|
||||
return NewStdinNode(dir)
|
||||
}
|
||||
return NewNode(entrypoint, dir, insecure)
|
||||
return NewNode(entrypoint, dir, insecure, opts...)
|
||||
}
|
||||
|
||||
func NewNode(
|
||||
@@ -70,6 +73,16 @@ func NewNode(
|
||||
return node, err
|
||||
}
|
||||
|
||||
func isRemoteEntrypoint(entrypoint string) bool {
|
||||
scheme, _ := getScheme(entrypoint)
|
||||
switch scheme {
|
||||
case "git", "http", "https":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func getScheme(uri string) (string, error) {
|
||||
u, err := giturls.Parse(uri)
|
||||
if u == nil {
|
||||
@@ -80,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
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
package taskfile
|
||||
|
||||
type (
|
||||
NodeOption func(*BaseNode)
|
||||
// BaseNode is a generic node that implements the Parent() methods of the
|
||||
NodeOption func(*baseNode)
|
||||
// baseNode is a generic node that implements the Parent() methods of the
|
||||
// NodeReader interface. It does not implement the Read() method and it
|
||||
// designed to be embedded in other node types so that this boilerplate code
|
||||
// does not need to be repeated.
|
||||
BaseNode struct {
|
||||
parent Node
|
||||
dir string
|
||||
baseNode struct {
|
||||
parent Node
|
||||
dir string
|
||||
checksum string
|
||||
caCert string
|
||||
cert string
|
||||
certKey string
|
||||
}
|
||||
)
|
||||
|
||||
func NewBaseNode(dir string, opts ...NodeOption) *BaseNode {
|
||||
node := &BaseNode{
|
||||
func NewBaseNode(dir string, opts ...NodeOption) *baseNode {
|
||||
node := &baseNode{
|
||||
parent: nil,
|
||||
dir: dir,
|
||||
}
|
||||
@@ -27,15 +31,47 @@ func NewBaseNode(dir string, opts ...NodeOption) *BaseNode {
|
||||
}
|
||||
|
||||
func WithParent(parent Node) NodeOption {
|
||||
return func(node *BaseNode) {
|
||||
return func(node *baseNode) {
|
||||
node.parent = parent
|
||||
}
|
||||
}
|
||||
|
||||
func (node *BaseNode) Parent() Node {
|
||||
func WithChecksum(checksum string) NodeOption {
|
||||
return func(node *baseNode) {
|
||||
node.checksum = checksum
|
||||
}
|
||||
}
|
||||
|
||||
func (node *baseNode) Parent() Node {
|
||||
return node.parent
|
||||
}
|
||||
|
||||
func (node *BaseNode) Dir() string {
|
||||
func (node *baseNode) Dir() string {
|
||||
return node.dir
|
||||
}
|
||||
|
||||
func (node *baseNode) Checksum() string {
|
||||
return node.checksum
|
||||
}
|
||||
|
||||
func (node *baseNode) Verify(checksum string) bool {
|
||||
return node.checksum == "" || node.checksum == checksum
|
||||
}
|
||||
|
||||
func WithCACert(caCert string) NodeOption {
|
||||
return func(node *baseNode) {
|
||||
node.caCert = caCert
|
||||
}
|
||||
}
|
||||
|
||||
func WithCert(cert string) NodeOption {
|
||||
return func(node *baseNode) {
|
||||
node.cert = cert
|
||||
}
|
||||
}
|
||||
|
||||
func WithCertKey(certKey string) NodeOption {
|
||||
return func(node *baseNode) {
|
||||
node.certKey = certKey
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ import (
|
||||
const remoteCacheDir = "remote"
|
||||
|
||||
type CacheNode struct {
|
||||
*BaseNode
|
||||
*baseNode
|
||||
source RemoteNode
|
||||
}
|
||||
|
||||
func NewCacheNode(source RemoteNode, dir string) *CacheNode {
|
||||
return &CacheNode{
|
||||
BaseNode: &BaseNode{
|
||||
baseNode: &baseNode{
|
||||
dir: filepath.Join(dir, remoteCacheDir),
|
||||
},
|
||||
source: source,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user