mirror of
https://github.com/go-task/task.git
synced 2026-05-18 13:15:41 +02:00
Compare commits
226 Commits
v3.37.1
...
migrate-ho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c7bf6b41c | ||
|
|
240047152d | ||
|
|
24a830e384 | ||
|
|
fe9f489702 | ||
|
|
27de441ed2 | ||
|
|
79f7af2b04 | ||
|
|
b588d49cfb | ||
|
|
45006e2ce0 | ||
|
|
e5d8237053 | ||
|
|
89740ed72a | ||
|
|
c1e14c461b | ||
|
|
dc2eceb634 | ||
|
|
43f3dcea05 | ||
|
|
f27daea5c9 | ||
|
|
49e88e92cf | ||
|
|
da40aabcc7 | ||
|
|
8ce9bdc8c7 | ||
|
|
0409c3c3ba | ||
|
|
fd3532812e | ||
|
|
2965841eb7 | ||
|
|
dbe6e41ac8 | ||
|
|
8f73ced037 | ||
|
|
2a4f93eb41 | ||
|
|
9d8c4ba7e6 | ||
|
|
1bda388925 | ||
|
|
d64df3f9d7 | ||
|
|
d1f18d36b8 | ||
|
|
5f1d46c770 | ||
|
|
9727eef476 | ||
|
|
c5be676555 | ||
|
|
f3317266dc | ||
|
|
36ff00e3f9 | ||
|
|
041063b732 | ||
|
|
2ab1dcbf1d | ||
|
|
24a0f24835 | ||
|
|
4dffab2e0a | ||
|
|
b9a5d1c573 | ||
|
|
e1818e9e31 | ||
|
|
bb2de3fdf9 | ||
|
|
82f6029043 | ||
|
|
cfaecf8b4c | ||
|
|
4595c1e32a | ||
|
|
1a648dea50 | ||
|
|
a273183745 | ||
|
|
e2243fc6d9 | ||
|
|
c1209d9f13 | ||
|
|
2b54b04cfc | ||
|
|
32fa3a0156 | ||
|
|
973e928c28 | ||
|
|
bc844246d4 | ||
|
|
41884f0a69 | ||
|
|
2a96c20739 | ||
|
|
c28eb204fb | ||
|
|
b1535aedc1 | ||
|
|
7e3feb2993 | ||
|
|
8a79a41717 | ||
|
|
530818a742 | ||
|
|
517bb3fc97 | ||
|
|
6645a1f34c | ||
|
|
2aa2963565 | ||
|
|
390220ec9c | ||
|
|
c3bd6b9384 | ||
|
|
d8e176311d | ||
|
|
1c68f0fee4 | ||
|
|
118ef01a69 | ||
|
|
148b090d8e | ||
|
|
28a96d1427 | ||
|
|
47f5e6ab89 | ||
|
|
fe09c01637 | ||
|
|
7ef3164b16 | ||
|
|
b48a32b103 | ||
|
|
2d2c408652 | ||
|
|
c381923d3e | ||
|
|
7bfddaa25a | ||
|
|
5581954fb1 | ||
|
|
c4f708b222 | ||
|
|
27056a9827 | ||
|
|
a35910429c | ||
|
|
9a7e79258c | ||
|
|
8dd3f4b119 | ||
|
|
9ecc8fc878 | ||
|
|
e078261f12 | ||
|
|
bdb3ffddd1 | ||
|
|
a72e70b026 | ||
|
|
c5eea294aa | ||
|
|
0fff404eb8 | ||
|
|
61172fa8da | ||
|
|
a6bc3f51cc | ||
|
|
1af7bf2670 | ||
|
|
d75536bf00 | ||
|
|
ce3e058f89 | ||
|
|
8d0f0b049c | ||
|
|
e619bad4a9 | ||
|
|
e6ea0647d7 | ||
|
|
d1dc271b9a | ||
|
|
f5082f3692 | ||
|
|
30c59bf387 | ||
|
|
38d0fc2c55 | ||
|
|
460e587c66 | ||
|
|
ad5a3166ac | ||
|
|
ddccd1bb61 | ||
|
|
96a690ac2f | ||
|
|
cb07189bab | ||
|
|
7e6577eb5f | ||
|
|
58ab26c4ab | ||
|
|
65d332dfd0 | ||
|
|
5eaf0b2dcd | ||
|
|
56f3735b38 | ||
|
|
23d578ac8c | ||
|
|
1bf850592c | ||
|
|
0be05795b9 | ||
|
|
08a2a91180 | ||
|
|
84cc5e57b0 | ||
|
|
5aa68e47e5 | ||
|
|
15aa4b86af | ||
|
|
114d5e1404 | ||
|
|
8ab5fe0e80 | ||
|
|
c89a6add48 | ||
|
|
888071e234 | ||
|
|
ff2e0f846a | ||
|
|
3c177d3fdc | ||
|
|
41bc490e0f | ||
|
|
f8e3742d11 | ||
|
|
a6100b39f8 | ||
|
|
1275ab1b5b | ||
|
|
0c05dcbe0f | ||
|
|
e9983e299f | ||
|
|
a450f2daea | ||
|
|
c77c8a419b | ||
|
|
a233b52c65 | ||
|
|
0e2c9cc88f | ||
|
|
dd9cec611a | ||
|
|
6985413f93 | ||
|
|
cf77768c82 | ||
|
|
6c3b13b676 | ||
|
|
ad45c7aeb3 | ||
|
|
e4b4d04abd | ||
|
|
a3bdb6c40a | ||
|
|
eb39dd94d0 | ||
|
|
21cd573770 | ||
|
|
281d259e6e | ||
|
|
1cb5daf73e | ||
|
|
3747b2ab7f | ||
|
|
d727ef5393 | ||
|
|
a72b65b3b2 | ||
|
|
ef3b853728 | ||
|
|
f302b50519 | ||
|
|
c243b0ec7e | ||
|
|
32158dac87 | ||
|
|
0a59890a46 | ||
|
|
defbcf6acd | ||
|
|
045d054a5f | ||
|
|
0941de3318 | ||
|
|
b259edeb65 | ||
|
|
35119c12ab | ||
|
|
f6ff775d11 | ||
|
|
5e9851f42f | ||
|
|
51c569ef37 | ||
|
|
1ca432a80d | ||
|
|
e781b3d4e0 | ||
|
|
81ff1cdea0 | ||
|
|
1f2cbfb932 | ||
|
|
4b6c79aca5 | ||
|
|
5739495739 | ||
|
|
9d72fa3250 | ||
|
|
4123ffc780 | ||
|
|
cdafc67bef | ||
|
|
9ee4f21d62 | ||
|
|
133086d647 | ||
|
|
88b095020e | ||
|
|
cc14996b71 | ||
|
|
375106c988 | ||
|
|
6ce6a38899 | ||
|
|
76030c9146 | ||
|
|
a71020eab5 | ||
|
|
6bef2ff8a9 | ||
|
|
413dcd28a8 | ||
|
|
da6f5c66a0 | ||
|
|
6012da7a21 | ||
|
|
46c5eafe35 | ||
|
|
830b745112 | ||
|
|
b52d4e4f40 | ||
|
|
3aaa3223a0 | ||
|
|
a9ff58d0fe | ||
|
|
eeaebaf8c7 | ||
|
|
2213141fcb | ||
|
|
19956889a7 | ||
|
|
4c580ebf18 | ||
|
|
3dccde270a | ||
|
|
53dd0b138a | ||
|
|
ea85909e8b | ||
|
|
6bf6fe7ead | ||
|
|
f39c6352ac | ||
|
|
4294cc92b9 | ||
|
|
40d77156df | ||
|
|
856ba3b8c2 | ||
|
|
0810ef01b0 | ||
|
|
527bbc3bf5 | ||
|
|
912bbcab8e | ||
|
|
aa45491510 | ||
|
|
1e25ceab29 | ||
|
|
a74b0bc679 | ||
|
|
a3fce1c302 | ||
|
|
7958cf50b3 | ||
|
|
b0efbad591 | ||
|
|
30e9c7d4cd | ||
|
|
baa5e2c378 | ||
|
|
cc97e2da1d | ||
|
|
a55e21bbb7 | ||
|
|
8d138a5eea | ||
|
|
635e3f4e7d | ||
|
|
252d549e3f | ||
|
|
182d43e8d8 | ||
|
|
f35e51e4e5 | ||
|
|
fb3c64c46e | ||
|
|
7535467f45 | ||
|
|
3e5cd6cdfd | ||
|
|
dcc060af89 | ||
|
|
55593090fa | ||
|
|
57c094f415 | ||
|
|
2f4876b71c | ||
|
|
725f929778 | ||
|
|
8266b28b48 | ||
|
|
f5c7472f64 | ||
|
|
ced3e7a579 | ||
|
|
36dd71b122 |
@@ -8,6 +8,6 @@ charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
|
||||
[*.{md,yml,yaml,json,toml,htm,html,js,css,svg,sh,bash,fish}]
|
||||
[*.{md,mdx,yml,yaml,json,toml,htm,html,js,ts,css,svg,sh,bash,fish}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1 +1,2 @@
|
||||
* text=auto
|
||||
*.mdx -linguist-detectable
|
||||
|
||||
2
.github/CODE_OF_CONDUCT.md
vendored
2
.github/CODE_OF_CONDUCT.md
vendored
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at andrey@nering.com.br. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at task@taskfile.dev. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
||||
github: [andreynering, pd93]
|
||||
github: [andreynering, pd93, vmaerten]
|
||||
open_collective: task
|
||||
custom: https://taskfile.dev/donate/
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/bug_report.md
vendored
20
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Use this to report bugs and issues
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Thanks for your bug report!
|
||||
|
||||
Before submitting this issue, please make sure the same problem was not
|
||||
already reported by someone else.
|
||||
|
||||
Please describe the bug you're facing. Consider pasting example Taskfiles
|
||||
showing how to reproduce the problem.
|
||||
|
||||
-->
|
||||
|
||||
- Task version:
|
||||
- Operating system:
|
||||
- Experiments enabled:
|
||||
69
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
69
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: '🐞 Bug Report'
|
||||
description: Report a bug in Task.
|
||||
labels: ['state: needs-triage']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your bug report!
|
||||
|
||||
Before submitting, please check the list of [existing issues](https://github.com/go-task/task/issues) and make sure the same bug was not already reported by someone else.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Describe the bug you're seeing.
|
||||
placeholder: |
|
||||
- What did you do?
|
||||
- What did you expect to happen?
|
||||
- What happened instead?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version(s) of Task is the issue occurring on?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
description: What operating system(s) is the issue occurring on?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: experiments
|
||||
attributes:
|
||||
label: Experiments Enabled
|
||||
description: Do you have any experiments enabled? You can check by running `task --experiments`.
|
||||
multiple: true
|
||||
options:
|
||||
- Env Precedence
|
||||
- Gentle Force
|
||||
- Map Variables (1)
|
||||
- Map Variables (2)
|
||||
- Remote Taskfiles
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Example Taskfile
|
||||
description: |
|
||||
If you have a Taskfile that reproduces the issue, please paste it here.
|
||||
This will be automatically formatted into code, so no need for backticks.
|
||||
render: YAML
|
||||
placeholder: |
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- 'echo "This Taskfile is buggy :("'
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,11 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Extension for Visual Studio Code
|
||||
- name: '🔌 Task for Visual Studio Code'
|
||||
url: https://github.com/go-task/vscode-task
|
||||
about: Issues related to the Visual Studio Code extension should be opened here.
|
||||
- name: Help forum on Discord
|
||||
url: https://discord.gg/6TY36E39UK
|
||||
about: 'The Discord #help channel is the best way to get help from the community.'
|
||||
- name: Questions, Ideas and General Discussions
|
||||
about: 'Issues related to the Visual Studio Code extension should be opened here.'
|
||||
- name: '💬 Help forum on Discord'
|
||||
url: https://discord.com/channels/974121106208354339/1025054680289660989
|
||||
about: 'The #help channel on our Discord is the best way to get help from the community.'
|
||||
- name: '❓ Questions, Ideas and General Discussions'
|
||||
url: https://github.com/go-task/task/discussions
|
||||
about: Ask questions and discuss general ideas with the community.
|
||||
about: 'Ask questions and discuss general ideas with the community.'
|
||||
|
||||
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,15 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Use this to make feature requests
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Describe in detail what feature do you want to see in Task.
|
||||
Give examples if possible.
|
||||
|
||||
Please, search if this wasn't proposed before, and if this is more like an idea
|
||||
than a strong feature request, consider opening a
|
||||
[discussion](https://github.com/go-task/task/discussions) instead.
|
||||
|
||||
-->
|
||||
23
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
23
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: '✨ Feature Request'
|
||||
description: Suggest a new feature or enhancement for Task.
|
||||
labels: ['state: needs-triage']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your feature request!
|
||||
|
||||
Before submitting, please check the list of [existing issues](https://github.com/go-task/task/issues) and make sure the same change was not already requested by someone else.
|
||||
If your request is more of an idea than a feature request, consider opening a [discussion](https://github.com/go-task/task/discussions) instead.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Describe the feature/enhancement you want to see in Task.
|
||||
placeholder: |
|
||||
- Give a general overview of the feature/enhancement.
|
||||
- Explain problem is the change trying to solve.
|
||||
- Give examples of how you would use the feature.
|
||||
validations:
|
||||
required: true
|
||||
14
.github/pull_request_template.md
vendored
14
.github/pull_request_template.md
vendored
@@ -1,5 +1,9 @@
|
||||
> Thanks for your pull request, we really appreciate contributions!
|
||||
>
|
||||
> Please understand that it may take some time to be reviewed.
|
||||
>
|
||||
> Also, make sure to follow the [Contribution Guide](https://taskfile.dev/contributing/).
|
||||
<!--
|
||||
|
||||
Thanks for your pull request, we really appreciate contributions!
|
||||
|
||||
Please understand that it may take some time to be reviewed.
|
||||
|
||||
Also, make sure to follow the [Contribution Guide](https://taskfile.dev/contributing/).
|
||||
|
||||
-->
|
||||
|
||||
48
.github/renovate.json
vendored
Normal file
48
.github/renovate.json
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
"group:allNonMajor",
|
||||
"schedule:monthly"
|
||||
],
|
||||
"mode": "full",
|
||||
"reviewers": ["team:developer"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["github-actions"],
|
||||
"groupName": "Github Action",
|
||||
"labels": ["area: github actions", "area: dependencies"],
|
||||
"matchPackageNames": [
|
||||
"*"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"matchManagers": ["npm", "nvm"],
|
||||
"groupName": "Website",
|
||||
"labels": ["lang: javascript", "area: dependencies"],
|
||||
"matchPackageNames": [
|
||||
"*"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"matchManagers": ["gomod"],
|
||||
"groupName": "golang",
|
||||
"labels": ["lang: go", "area: dependencies"],
|
||||
"matchPackageNames": [
|
||||
"*"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
issue-awaiting-response:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
|
||||
2
.github/workflows/issue-closed.yml
vendored
2
.github/workflows/issue-closed.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
issue-closed:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
|
||||
14
.github/workflows/issue-experiment.yml
vendored
14
.github/workflows/issue-experiment.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
if: github.event.label.name == format('experiment{0} proposed', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
if: github.event.label.name == format('experiment{0} draft', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
if: github.event.label.name == format('experiment{0} candidate', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
if: github.event.label.name == format('experiment{0} stable', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
if: github.event.label.name == format('experiment{0} released', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
if: github.event.label.name == format('experiment{0} abandoned', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
if: github.event.label.name == format('experiment{0} superseded', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
|
||||
2
.github/workflows/issue-needs-triage.yml
vendored
2
.github/workflows/issue-needs-triage.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
issue-needs-triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
|
||||
27
.github/workflows/lint.yml
vendored
27
.github/workflows/lint.yml
vendored
@@ -13,19 +13,19 @@ jobs:
|
||||
name: Lint
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.21.x, 1.22.x]
|
||||
go-version: [1.22.x, 1.23.x]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.55.2
|
||||
version: v1.60.1
|
||||
|
||||
lint-jsonschema:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -34,10 +34,25 @@ jobs:
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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@v45
|
||||
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.')
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -10,15 +10,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.21.x
|
||||
go-version: 1.22.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean
|
||||
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -13,18 +13,18 @@ jobs:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.21.x, 1.22.x]
|
||||
go-version: [1.22.x, 1.23.x]
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{matrix.platform}}
|
||||
steps:
|
||||
- name: Set up Go ${{matrix.go-version}}
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
|
||||
38
.github/workflows/upload-source-documents.yml
vendored
38
.github/workflows/upload-source-documents.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Upload Source Documents
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
push_files_to_crowdin:
|
||||
name: Push files to Crowdin
|
||||
if: github.repository == 'go-task/task'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Verify changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: |
|
||||
website/docs
|
||||
website/blog
|
||||
website/i18n/en
|
||||
website/src/pages
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
with:
|
||||
version: 3.x
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload source documents
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
run: task crowdin:push
|
||||
env:
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
working-directory: ./website
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -24,8 +24,7 @@ dist/
|
||||
|
||||
# editors
|
||||
.idea/
|
||||
.vscode/*
|
||||
!.vscode/*-sample.json
|
||||
.vscode/settings.json
|
||||
.fleet/
|
||||
|
||||
# exuberant ctags
|
||||
|
||||
@@ -5,11 +5,28 @@
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- depguard
|
||||
- goimports
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- misspell
|
||||
- noctx
|
||||
- paralleltest
|
||||
- tenv
|
||||
- thelper
|
||||
- tparallel
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
files:
|
||||
- "$all"
|
||||
- "!$test"
|
||||
- "!**/errors/*.go"
|
||||
deny:
|
||||
- pkg: "errors"
|
||||
desc: "Use github.com/go-task/task/v3/errors instead"
|
||||
goimports:
|
||||
local-prefixes: github.com/go-task
|
||||
gofmt:
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
version: 2
|
||||
|
||||
builds:
|
||||
- binary: task
|
||||
main: ./cmd/task
|
||||
@@ -11,11 +14,16 @@ builds:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- riscv64
|
||||
goarm:
|
||||
- '6'
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: '386'
|
||||
- goos: darwin
|
||||
goarch: riscv64
|
||||
- goos: windows
|
||||
goarch: riscv64
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
@@ -41,7 +49,7 @@ release:
|
||||
draft: true
|
||||
|
||||
snapshot:
|
||||
name_template: "{{.Tag}}"
|
||||
version_template: "{{.Version}}"
|
||||
|
||||
checksum:
|
||||
name_template: "task_checksums.txt"
|
||||
@@ -49,7 +57,7 @@ checksum:
|
||||
nfpms:
|
||||
- vendor: Task
|
||||
homepage: https://taskfile.dev
|
||||
maintainer: Andrey Nering <andrey@nering.com.br>
|
||||
maintainer: The Task authors <task@taskfile.dev>
|
||||
description: Simple task runner written in Go
|
||||
license: MIT
|
||||
conflicts:
|
||||
@@ -79,9 +87,7 @@ brews:
|
||||
system "#{bin}/task", "--help"
|
||||
install: |-
|
||||
bin.install "task"
|
||||
bash_completion.install "completion/bash/task.bash" => "task"
|
||||
zsh_completion.install "completion/zsh/_task" => "_task"
|
||||
fish_completion.install "completion/fish/task.fish"
|
||||
generate_completions_from_executable(bin/"task", "--completion")
|
||||
commit_author:
|
||||
name: task-bot
|
||||
email: 106601941+task-bot@users.noreply.github.com
|
||||
|
||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"editorconfig.editorconfig",
|
||||
"golang.go",
|
||||
"task.vscode-task"
|
||||
]
|
||||
}
|
||||
174
CHANGELOG.md
174
CHANGELOG.md
@@ -1,5 +1,157 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Fixed an issue where dynamic variables were not properly logged in verbose
|
||||
mode (#1920, #1921 by @mgbowman).
|
||||
- Support `silent` for defer statements (#1877, #1879 by @danilobuerger).
|
||||
- Added an option to exclude some tasks from being included (#1859 by
|
||||
@vmaerten).
|
||||
- Fixed an issue where a required variable was incorrectly handled in a template
|
||||
function (#1950, #1962 by @vmaerten).
|
||||
- Expose a new `TASK_DIR` special variable, which will contain the absolute path
|
||||
of task directory. (#1959, #1961 by @vmaerten).
|
||||
- Fixed fatal bugs that caused concurrent map writes (#1605, #1972, #1974 by
|
||||
@pd93, @GrahamDennis and @trim21.
|
||||
- Refactored internal ordered map implementation to use
|
||||
[github.com/elliotchance/orderedmap](https://github.com/elliotchance/orderedmap)
|
||||
(#1797 by @pd93).
|
||||
- Fixed a bug where variables defined at the task level were being ignored in
|
||||
the `requires` section. (#1960, #1955, #1768 by @vmaerten and @mokeko)
|
||||
- The `CHECKSUM` and `TIMESTAMP` variables are now accessible within `cmds`
|
||||
(#1872 by @niklasr22).
|
||||
- Updated [installation docs](https://taskfile.dev/installation) and added pip
|
||||
installation method (#935, #1989 by @pd93).
|
||||
- Fixed a bug where dynamic variables could not access environment variables
|
||||
(#630, #1869 by @rohm1 and @pd93).
|
||||
|
||||
## v3.40.1 - 2024-12-06
|
||||
|
||||
- Fixed a security issue in `git-urls` by switching to the maintained fork
|
||||
`chainguard-dev/git-urls` (#1917 by @AlekSi).
|
||||
- Added missing `platforms` property to `cmds` that use `for` (#1915 by
|
||||
@dkarter).
|
||||
- Added misspell linter to check for misspelled English words (#1883 by
|
||||
@christiandins).
|
||||
|
||||
## v3.40.0 - 2024-11-05
|
||||
|
||||
- Fixed output of some functions (e.g. `splitArgs`/`splitLines`) not working in
|
||||
for loops (#1822, #1823 by @stawii).
|
||||
- Added a new `TASK_OFFLINE` environment variable to configure the `--offline`
|
||||
flag and expose it as a special variable in the templating system (#1470,
|
||||
#1716 by @vmaerten and @pd93).
|
||||
- Fixed a bug where multiple remote includes caused all prompts to display
|
||||
without waiting for user input (#1832, #1833 by @vmaerten and @pd93).
|
||||
- When using the
|
||||
"[Remote Taskfiles](https://taskfile.dev/experiments/remote-taskfiles/)".
|
||||
experiment, you can now include Taskfiles from Git repositories (#1652 by
|
||||
@vmaerten).
|
||||
- Improved the error message when a dotenv file cannot be parsed (#1842 by
|
||||
@pbitty).
|
||||
- Fix issue with directory when using the remote experiment (#1757 by @pbitty).
|
||||
- Fixed an issue where a special variable was used in combination with a dotenv
|
||||
file (#1232, #1810 by @vmaerten).
|
||||
- Refactor the way Task reads Taskfiles to improve readability (#1771 by
|
||||
@pbitty).
|
||||
- Added a new option to ensure variable is within the list of values (#1827 by
|
||||
@vmaerten).
|
||||
- Allow multiple prompts to be specified for a task (#1861, #1866 by @mfbmina).
|
||||
- Added new template function: `numCPU`, which returns the number of logical
|
||||
CPUs usable (#1890, #1887 by @Amoghrd).
|
||||
- Fixed a bug where non-nil, empty dynamic variables are returned as an empty
|
||||
interface (#1903, #1904 by @pd93).
|
||||
|
||||
## v3.39.2 - 2024-09-19
|
||||
|
||||
- Fix dynamic variables not working properly for a defer: statement (#1803,
|
||||
#1818 by @vmaerten).
|
||||
|
||||
## v3.39.1 - 2024-09-18
|
||||
|
||||
- Added Renovate configuration to automatically create PRs to keep dependencies
|
||||
up to date (#1783 by @vmaerten).
|
||||
- Fixed a bug where the help was displayed twice (#1805, #1806 by @vmaerten).
|
||||
- Fixed a bug where ZSH and PowerShell completions did not work when using the
|
||||
recommended method. (#1813, #1809 by @vmaerten and @shirayu)
|
||||
- Fix variables not working properly for a `defer:` statement (#1803, #1814 by
|
||||
@vmaerten and @andreynering).
|
||||
|
||||
## v3.39.0 - 2024-09-07
|
||||
|
||||
- Added
|
||||
[Env Precedence Experiment](https://taskfile.dev/experiments/env-precedence)
|
||||
(#1038, #1633 by @vmaerten).
|
||||
- Added a CI lint job to ensure that the docs are updated correctly (#1719 by
|
||||
@vmaerten).
|
||||
- Updated minimum required Go version to 1.22 (#1758 by @pd93).
|
||||
- Expose a new `EXIT_CODE` special variable on `defer:` when a command finishes
|
||||
with a non-zero exit code (#1484, #1762 by @dorimon-1 and @andreynering).
|
||||
- Expose a new `ALIAS` special variable, which will contain the alias used to
|
||||
call the current task. Falls back to the task name. (#1764 by @DanStory).
|
||||
- Fixed `TASK_REMOTE_DIR` environment variable not working when the path was
|
||||
absolute. (#1715 by @vmaerten).
|
||||
- Added an option to declare an included Taskfile as flattened (#1704 by
|
||||
@vmaerten).
|
||||
- Added a new
|
||||
[`--completion` flag](https://taskfile.dev/installation/#setup-completions) to
|
||||
output completion scripts for various shells (#293, #1157 by @pd93).
|
||||
- This is now the preferred way to install completions.
|
||||
- The completion scripts in the `completion` directory
|
||||
[are now deprecated](https://taskfile.dev/deprecations/completion-scripts/).
|
||||
- Added the ability to
|
||||
[loop over a matrix of values](https://taskfile.dev/usage/#looping-over-a-matrix)
|
||||
(#1766, #1767, #1784 by @pd93).
|
||||
- Fixed a bug in fish completion where aliases were not displayed (#1781, #1782
|
||||
by @vmaerten).
|
||||
- Fixed panic when having a flattened included Taskfile that contains a
|
||||
`default` task (#1777, #1778 by @vmaerten).
|
||||
- Optimized file existence checks for remote Taskfiles (#1713 by @vmaerten).
|
||||
|
||||
## v3.38.0 - 2024-06-30
|
||||
|
||||
- Added `TASK_EXE` special variable (#1616, #1624 by @pd93 and @andreynering).
|
||||
- Some YAML parsing errors will now show in a more user friendly way (#1619 by
|
||||
@pd93).
|
||||
- Prefixed outputs will now be colorized by default (#1572 by
|
||||
@AlexanderArvidsson)
|
||||
- [References](https://taskfile.dev/usage/#referencing-other-variables) are now
|
||||
generally available (no experiments required) (#1654 by @pd93).
|
||||
- Templating functions can now be used in references (#1645, #1654 by @pd93).
|
||||
- Added a new
|
||||
[templating reference page](https://taskfile.dev/reference/templating/) to the
|
||||
documentation (#1614, #1653 by @pd93).
|
||||
- If using the
|
||||
[Map Variables experiment (1)](https://taskfile.dev/experiments/map-variables/?proposal=1),
|
||||
references are available by
|
||||
[prefixing a string with a `#`](https://taskfile.dev/experiments/map-variables/?proposal=1#references)
|
||||
(#1654 by @pd93).
|
||||
- If using the
|
||||
[Map Variables experiment (2)](https://taskfile.dev/experiments/map-variables/?proposal=2),
|
||||
the `yaml` and `json` keys are no longer available (#1654 by @pd93).
|
||||
- Added a new `TASK_REMOTE_DIR` environment variable to configure where cached
|
||||
remote Taskfiles are stored (#1661 by @vmaerten).
|
||||
- Added a new `--clear-cache` flag to clear the cache of remote Taskfiles (#1639
|
||||
by @vmaerten).
|
||||
- Improved the readability of cached remote Taskfile filenames (#1636 by
|
||||
@vmaerten).
|
||||
- Starting releasing a binary for the `riscv64` architecture on Linux (#1699 by
|
||||
@mengzhuo).
|
||||
- Added `CLI_SILENT` and `CLI_VERBOSE` variables (#1480, #1669 by @Vince-Smith).
|
||||
- Fixed a couple of bugs with the `prompt:` feature (#1657 by @pd93).
|
||||
- Fixed JSON Schema to disallow invalid properties (#1657 by @pd93).
|
||||
- Fixed version checks not working as intended (#872, #1663 by @vmaerten).
|
||||
- Fixed a bug where included tasks were run multiple times even if `run: once`
|
||||
was set (#852, #1655 by @pd93).
|
||||
- Fixed some bugs related to column formatting in the terminal (#1350, #1637,
|
||||
#1656 by @vmaerten).
|
||||
|
||||
## v3.37.2 - 2024-05-12
|
||||
|
||||
- Fixed a bug where an empty Taskfile would cause a panic (#1648 by @pd93).
|
||||
- Fixed a bug where includes Taskfile variable were not being merged correctly
|
||||
(#1643, #1649 by @pd93).
|
||||
|
||||
## v3.37.1 - 2024-05-09
|
||||
|
||||
- Fix bug where non-string values (numbers, bools) added to `env:` weren't been
|
||||
@@ -14,7 +166,7 @@
|
||||
- Refactored how Task reads, parses and merges Taskfiles using a DAG (#1563,
|
||||
#1607 by @pd93).
|
||||
- Fix a bug which stopped tasks from using `stdin` as input (#1593, #1623 by
|
||||
@pd03).
|
||||
@pd93).
|
||||
- Fix error when a file or directory in the project contained a special char
|
||||
like `&`, `(` or `)` (#1551, #1584 by @andreynering).
|
||||
- Added alias `q` for template function `shellQuote` (#1601, #1603 by @vergenzt)
|
||||
@@ -174,8 +326,8 @@
|
||||
- Added the
|
||||
[Remote Taskfiles experiment](https://taskfile.dev/experiments/remote-taskfiles)
|
||||
as a draft (#1152, #1317 by @pd93).
|
||||
- Improve performance of content checksuming on `sources:` by replacing md5 with
|
||||
[XXH3](https://xxhash.com/) which is much faster. This is a soft breaking
|
||||
- Improve performance of content checksumming on `sources:` by replacing md5
|
||||
with [XXH3](https://xxhash.com/) which is much faster. This is a soft breaking
|
||||
change because checksums will be invalidated when upgrading to this release
|
||||
(#1325 by @ReillyBrogan).
|
||||
|
||||
@@ -234,7 +386,7 @@
|
||||
- Deprecated `version: 2` schema. This will be removed in the next major release
|
||||
(#1197, #1198, #1199 by @pd93).
|
||||
- Added a new `prompt:` prop to set a warning prompt to be shown before running
|
||||
a potential dangurous task (#100, #1163 by @MaxCheetham,
|
||||
a potential dangerous task (#100, #1163 by @MaxCheetham,
|
||||
[Documentation](https://taskfile.dev/usage/#warning-prompts)).
|
||||
- Added support for single command task syntax. With this change, it's now
|
||||
possible to declare just `cmd:` in a task, avoiding the more complex
|
||||
@@ -249,7 +401,7 @@
|
||||
percentage (#1173 by @misitebao).
|
||||
- Starting on this release, official binaries for FreeBSD will be available to
|
||||
download (#1068 by @andreynering).
|
||||
- Fix some errors being unintendedly supressed (#1134 by @clintmod).
|
||||
- Fix some errors being unintendedly suppressed (#1134 by @clintmod).
|
||||
- Fix a nil pointer error when `version` is omitted from a Taskfile (#1148,
|
||||
#1149 by @pd93).
|
||||
- Fix duplicate error message when a task does not exists (#1141, #1144 by
|
||||
@@ -322,8 +474,8 @@ it a go and let us know what you think via a
|
||||
- Fixed a bug where tasks were sometimes incorrectly marked as internal (#1007
|
||||
by @pd93).
|
||||
- Update to Go 1.20 (bump minimum version to 1.19) (#1010 by @pd93)
|
||||
- Added environment variable `FORCE_COLOR` support to force color output.
|
||||
Usefull for environments without TTY (#1003 by @automation-stack)
|
||||
- Added environment variable `FORCE_COLOR` support to force color output. Useful
|
||||
for environments without TTY (#1003 by @automation-stack)
|
||||
|
||||
## v3.20.0 - 2023-01-14
|
||||
|
||||
@@ -678,7 +830,7 @@ it a go and let us know what you think via a
|
||||
|
||||
- Fix error code for the `--help` flag (#300, #330).
|
||||
- Print version to stdout instead of stderr (#299, #329).
|
||||
- Supress `context` errors when using the `--watch` flag (#313, #317).
|
||||
- Suppress `context` errors when using the `--watch` flag (#313, #317).
|
||||
- Support templating on description (#276, #283).
|
||||
|
||||
## v2.8.0 - 2019-12-07
|
||||
@@ -687,7 +839,7 @@ it a go and let us know what you think via a
|
||||
parallel (#266).
|
||||
- Fixed bug where calling the `task` CLI only informing global vars would not
|
||||
execute the `default` task.
|
||||
- Add hability to silent all tasks by adding `silent: true` a the root of the
|
||||
- Add ability to silent all tasks by adding `silent: true` a the root of the
|
||||
Taskfile.
|
||||
|
||||
## v2.7.1 - 2019-11-10
|
||||
@@ -829,7 +981,7 @@ document, since it describes in depth what changed for this version.
|
||||
## v1.4.3 - 2017-09-07
|
||||
|
||||
- Allow assigning variables to tasks at run time via CLI (#33)
|
||||
- Added suport for multiline variables from sh (#64)
|
||||
- Added support for multiline variables from sh (#64)
|
||||
- Fixes env: remove square braces and evaluate shell (#62)
|
||||
- Watch: change watch library and few fixes and improvements
|
||||
- When use watching, cancel and restart long running process on file change (#59
|
||||
@@ -889,7 +1041,7 @@ document, since it describes in depth what changed for this version.
|
||||
- More tests and Travis integration
|
||||
- Watch a task (experimental)
|
||||
- Possibility to call another task
|
||||
- Fix "=" not being reconized in variables/environment variables
|
||||
- Fix "=" not being recognized in variables/environment variables
|
||||
- Tasks can now have a description, and help will print them (#10)
|
||||
- Task dependencies now run concurrently
|
||||
- Support for a default task (#16)
|
||||
|
||||
14
README.md
14
README.md
@@ -10,6 +10,18 @@
|
||||
</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://fosstodon.org/@task">Mastodon</a> | <a href="https://discord.gg/6TY36E39UK">Discord</a>
|
||||
<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>
|
||||
</p>
|
||||
|
||||
<h1>Gold Sponsors</h1>
|
||||
|
||||
<table>
|
||||
<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" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
52
Taskfile.yml
52
Taskfile.yml
@@ -27,7 +27,7 @@ tasks:
|
||||
- go install -v ./cmd/task
|
||||
|
||||
generate:
|
||||
desc: Runs Go generate to create mocks
|
||||
desc: Runs Mockery to create mocks
|
||||
aliases: [gen, g]
|
||||
deps: [install:mockery]
|
||||
sources:
|
||||
@@ -57,6 +57,7 @@ tasks:
|
||||
|
||||
clean:
|
||||
desc: Cleans temp files and folders
|
||||
aliases: [clear]
|
||||
cmds:
|
||||
- rm -rf dist/
|
||||
- rm -rf tmp/
|
||||
@@ -121,12 +122,55 @@ tasks:
|
||||
goreleaser:install:
|
||||
desc: Installs goreleaser
|
||||
cmds:
|
||||
- go install github.com/goreleaser/goreleaser@latest
|
||||
- go install github.com/goreleaser/goreleaser/v2@latest
|
||||
|
||||
release:
|
||||
release:*:
|
||||
desc: Prepare the project for a new release
|
||||
summary: |
|
||||
This task will do the following:
|
||||
|
||||
- Update the version and date in the CHANGELOG.md file
|
||||
- Update the version in the package.json and package-lock.json files
|
||||
- Copy the latest docs to the "current" version on the website
|
||||
- Commit the changes
|
||||
- Create a new tag
|
||||
- Push the commit/tag to the repository
|
||||
- Create a GitHub release
|
||||
|
||||
To use the task, simply run "task release:<version>" where "<version>" is is one of:
|
||||
|
||||
- "major" - Bumps the major number
|
||||
- "minor" - Bumps the minor number
|
||||
- "patch" - Bumps the patch number
|
||||
- A semver compatible version number (e.g. "1.2.3")
|
||||
vars:
|
||||
VERSION:
|
||||
sh: "go run ./cmd/release --version {{index .MATCH 0}}"
|
||||
COMPLETE_MESSAGE: |
|
||||
Creating release with GoReleaser: https://github.com/go-task/task/actions/workflows/release.yml
|
||||
|
||||
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"
|
||||
msg: "You must be on the main branch to release"
|
||||
- sh: "[[ -z $(git diff --shortstat main) ]]"
|
||||
msg: "You must have a clean working tree to release"
|
||||
prompt: "Are you sure you want to release version {{.VERSION}}?"
|
||||
cmds:
|
||||
- go run ./cmd/release {{.CLI_ARGS}}
|
||||
- cmd: echo "Releasing v{{.VERSION}}"
|
||||
silent: true
|
||||
- "go run ./cmd/release {{.VERSION}}"
|
||||
- "git add --all"
|
||||
- "git commit -m v{{.VERSION}}"
|
||||
- "git push"
|
||||
- "git tag v{{.VERSION}}"
|
||||
- "git push origin tag v{{.VERSION}}"
|
||||
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
|
||||
silent: true
|
||||
|
||||
npm:publish:
|
||||
desc: Publish release to npm
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
// Parse parses command line argument: tasks and global variables
|
||||
func Parse(args ...string) ([]*ast.Call, *ast.Vars) {
|
||||
calls := []*ast.Call{}
|
||||
globals := &ast.Vars{}
|
||||
globals := ast.NewVars()
|
||||
|
||||
for _, arg := range args {
|
||||
if !strings.Contains(arg, "=") {
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3/args"
|
||||
"github.com/go-task/task/v3/internal/omap"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func TestArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
Args []string
|
||||
ExpectedCalls []*ast.Call
|
||||
@@ -32,30 +33,40 @@ func TestArgs(t *testing.T) {
|
||||
{Task: "task-b"},
|
||||
{Task: "task-c"},
|
||||
},
|
||||
ExpectedGlobals: &ast.Vars{
|
||||
OrderedMap: omap.FromMapWithOrder(
|
||||
map[string]ast.Var{
|
||||
"FOO": {Value: "bar"},
|
||||
"BAR": {Value: "baz"},
|
||||
"BAZ": {Value: "foo"},
|
||||
ExpectedGlobals: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "FOO",
|
||||
Value: ast.Var{
|
||||
Value: "bar",
|
||||
},
|
||||
[]string{"FOO", "BAR", "BAZ"},
|
||||
),
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "BAR",
|
||||
Value: ast.Var{
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "BAZ",
|
||||
Value: ast.Var{
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
||||
ExpectedCalls: []*ast.Call{
|
||||
{Task: "task-a"},
|
||||
},
|
||||
ExpectedGlobals: &ast.Vars{
|
||||
OrderedMap: omap.FromMapWithOrder(
|
||||
map[string]ast.Var{
|
||||
"CONTENT": {Value: "with some spaces"},
|
||||
ExpectedGlobals: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "CONTENT",
|
||||
Value: ast.Var{
|
||||
Value: "with some spaces",
|
||||
},
|
||||
[]string{"CONTENT"},
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
Args: []string{"FOO=bar", "task-a", "task-b"},
|
||||
@@ -63,14 +74,14 @@ func TestArgs(t *testing.T) {
|
||||
{Task: "task-a"},
|
||||
{Task: "task-b"},
|
||||
},
|
||||
ExpectedGlobals: &ast.Vars{
|
||||
OrderedMap: omap.FromMapWithOrder(
|
||||
map[string]ast.Var{
|
||||
"FOO": {Value: "bar"},
|
||||
ExpectedGlobals: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "FOO",
|
||||
Value: ast.Var{
|
||||
Value: "bar",
|
||||
},
|
||||
[]string{"FOO"},
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
Args: nil,
|
||||
@@ -83,25 +94,32 @@ func TestArgs(t *testing.T) {
|
||||
{
|
||||
Args: []string{"FOO=bar", "BAR=baz"},
|
||||
ExpectedCalls: []*ast.Call{},
|
||||
ExpectedGlobals: &ast.Vars{
|
||||
OrderedMap: omap.FromMapWithOrder(
|
||||
map[string]ast.Var{
|
||||
"FOO": {Value: "bar"},
|
||||
"BAR": {Value: "baz"},
|
||||
ExpectedGlobals: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "FOO",
|
||||
Value: ast.Var{
|
||||
Value: "bar",
|
||||
},
|
||||
[]string{"FOO", "BAR"},
|
||||
),
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "BAR",
|
||||
Value: ast.Var{
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
calls, globals := args.Parse(test.Args...)
|
||||
assert.Equal(t, test.ExpectedCalls, calls)
|
||||
if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 {
|
||||
assert.Equal(t, test.ExpectedGlobals.Keys(), globals.Keys())
|
||||
assert.Equal(t, test.ExpectedGlobals.Values(), globals.Values())
|
||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -11,6 +10,9 @@ import (
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/otiai10/copy"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,6 +27,16 @@ var (
|
||||
versionRegex = regexp.MustCompile(`(?m)^ "version": "\d+\.\d+\.\d+",$`)
|
||||
)
|
||||
|
||||
// Flags
|
||||
var (
|
||||
versionFlag bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
pflag.BoolVarP(&versionFlag, "version", "v", false, "resolved version number")
|
||||
pflag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := release(); err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -33,7 +45,7 @@ func main() {
|
||||
}
|
||||
|
||||
func release() error {
|
||||
if len(os.Args) != 2 {
|
||||
if len(pflag.Args()) != 1 {
|
||||
return errors.New("error: expected version number")
|
||||
}
|
||||
|
||||
@@ -42,11 +54,14 @@ func release() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bumpVersion(version, os.Args[1]); err != nil {
|
||||
if err := bumpVersion(version, pflag.Arg(0)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(version)
|
||||
if versionFlag {
|
||||
fmt.Println(version)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := changelog(version); err != nil {
|
||||
return err
|
||||
|
||||
@@ -159,7 +159,7 @@ func worker(
|
||||
return workerDone
|
||||
}
|
||||
|
||||
// Do some work and then return, so that the caller can decide wether to continue or not.
|
||||
// Do some work and then return, so that the caller can decide whether to continue or not.
|
||||
// Return true when all work is done.
|
||||
func doSomeWork(deadline time.Time) bool {
|
||||
if time.Now().After(deadline) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/sort"
|
||||
ver "github.com/go-task/task/v3/internal/version"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -58,7 +59,7 @@ func run() error {
|
||||
entrypoint := flags.Entrypoint
|
||||
|
||||
if flags.Version {
|
||||
fmt.Printf("Task version: %s\n", ver.GetVersion())
|
||||
fmt.Printf("Task version: %s\n", ver.GetVersionWithSum())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -82,6 +83,15 @@ func run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if flags.Completion != "" {
|
||||
script, err := task.Completion(flags.Completion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(script)
|
||||
return nil
|
||||
}
|
||||
|
||||
if flags.Global {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@@ -125,16 +135,15 @@ func run() error {
|
||||
OutputStyle: flags.Output,
|
||||
TaskSorter: taskSorter,
|
||||
}
|
||||
|
||||
listOptions := task.NewListOptions(flags.List, flags.ListAll, flags.ListJson, flags.NoStatus)
|
||||
if err := listOptions.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.Setup(); err != nil {
|
||||
err := e.Setup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if experiments.AnyVariables.Enabled {
|
||||
logger.Warnf("The 'Any Variables' experiment flag is no longer required to use non-map variable types. If you wish to use map variables, please use 'TASK_X_MAP_VARIABLES' instead. See https://github.com/go-task/task/issues/1585\n")
|
||||
}
|
||||
@@ -145,6 +154,14 @@ func run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if flags.ClearCache {
|
||||
cache, err := taskfile.NewCache(e.TempDir.Remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cache.Clear()
|
||||
}
|
||||
|
||||
if (listOptions.ShouldListTasks()) && flags.Silent {
|
||||
return e.ListTaskNames(flags.ListAll)
|
||||
}
|
||||
@@ -179,6 +196,9 @@ func run() error {
|
||||
|
||||
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})
|
||||
e.Taskfile.Vars.Merge(globals, nil)
|
||||
|
||||
if !flags.Watch {
|
||||
|
||||
34
completion.go
Normal file
34
completion.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//go:embed completion/bash/task.bash
|
||||
var completionBash string
|
||||
|
||||
//go:embed completion/fish/task.fish
|
||||
var completionFish string
|
||||
|
||||
//go:embed completion/ps/task.ps1
|
||||
var completionPowershell string
|
||||
|
||||
//go:embed completion/zsh/_task
|
||||
var completionZsh string
|
||||
|
||||
func Completion(completion string) (string, error) {
|
||||
// Get the file extension for the selected shell
|
||||
switch completion {
|
||||
case "bash":
|
||||
return completionBash, nil
|
||||
case "fish":
|
||||
return completionFish, nil
|
||||
case "powershell":
|
||||
return completionPowershell, nil
|
||||
case "zsh":
|
||||
return completionZsh, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown shell: %s", completion)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ function __task_get_tasks --description "Prints all available tasks with their d
|
||||
end
|
||||
|
||||
# Grab names and descriptions (if any) of the tasks
|
||||
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):\s*\(.*\)\s*(aliases.*/\1\t\2/' -e 's/\* \(.*\):\s*\(.*\)/\1\t\2/'| string split0)
|
||||
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)
|
||||
if test $output
|
||||
echo $output
|
||||
end
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#compdef task
|
||||
|
||||
local context state state_descr line
|
||||
compdef _task task
|
||||
typeset -A opt_args
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"
|
||||
@@ -39,26 +38,33 @@ function __task_list() {
|
||||
_describe 'Task to run' scripts
|
||||
}
|
||||
|
||||
_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'
|
||||
_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'
|
||||
}
|
||||
|
||||
# don't run the completion function when being source-ed or eval-ed
|
||||
if [ "$funcstack[1]" = "_task" ]; then
|
||||
_task "$@"
|
||||
fi
|
||||
|
||||
179
errors/error_taskfile_decode.go
Normal file
179
errors/error_taskfile_decode.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/quick"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
"github.com/fatih/color"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//go:embed themes/*.xml
|
||||
var embedded embed.FS
|
||||
|
||||
var typeErrorRegex = regexp.MustCompile(`line \d+: (.*)`)
|
||||
|
||||
func init() {
|
||||
r, err := embedded.Open("themes/task.xml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
style, err := chroma.NewXMLStyle(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
styles.Register(style)
|
||||
}
|
||||
|
||||
type (
|
||||
TaskfileDecodeError struct {
|
||||
Message string
|
||||
Location string
|
||||
Line int
|
||||
Column int
|
||||
Tag string
|
||||
Snippet TaskfileSnippet
|
||||
Err error
|
||||
}
|
||||
TaskfileSnippet struct {
|
||||
Lines []string
|
||||
StartLine int
|
||||
EndLine int
|
||||
Padding int
|
||||
}
|
||||
)
|
||||
|
||||
func NewTaskfileDecodeError(err error, node *yaml.Node) *TaskfileDecodeError {
|
||||
// If the error is already a DecodeError, return it
|
||||
taskfileInvalidErr := &TaskfileDecodeError{}
|
||||
if errors.As(err, &taskfileInvalidErr) {
|
||||
return taskfileInvalidErr
|
||||
}
|
||||
return &TaskfileDecodeError{
|
||||
Line: node.Line,
|
||||
Column: node.Column,
|
||||
Tag: node.ShortTag(),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) Error() string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// Print the error message
|
||||
if err.Message != "" {
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", err.Message))
|
||||
} else {
|
||||
// Extract the errors from the TypeError
|
||||
te := &yaml.TypeError{}
|
||||
if errors.As(err.Err, &te) {
|
||||
if len(te.Errors) > 1 {
|
||||
fmt.Fprintln(buf, color.RedString("errs:"))
|
||||
for _, message := range te.Errors {
|
||||
fmt.Fprintln(buf, color.RedString("- %s", extractTypeErrorMessage(message)))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", extractTypeErrorMessage(te.Errors[0])))
|
||||
}
|
||||
} else {
|
||||
// Otherwise print the error message normally
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", err.Err))
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(buf, color.RedString("file: %s:%d:%d", err.Location, err.Line, err.Column))
|
||||
|
||||
// Print the snippet
|
||||
maxLineNumberDigits := digits(err.Snippet.EndLine)
|
||||
lineNumberSpacer := strings.Repeat(" ", maxLineNumberDigits)
|
||||
columnSpacer := strings.Repeat(" ", err.Column-1)
|
||||
for i, line := range err.Snippet.Lines {
|
||||
currentLine := err.Snippet.StartLine + i + 1
|
||||
|
||||
lineIndicator := " "
|
||||
if currentLine == err.Line {
|
||||
lineIndicator = ">"
|
||||
}
|
||||
columnIndicator := "^"
|
||||
|
||||
// Print each line
|
||||
lineIndicator = color.RedString(lineIndicator)
|
||||
columnIndicator = color.RedString(columnIndicator)
|
||||
lineNumberFormat := fmt.Sprintf("%%%dd", maxLineNumberDigits)
|
||||
lineNumber := fmt.Sprintf(lineNumberFormat, currentLine)
|
||||
fmt.Fprintf(buf, "%s %s | %s", lineIndicator, lineNumber, line)
|
||||
|
||||
// Print the column indicator
|
||||
if currentLine == err.Line {
|
||||
fmt.Fprintf(buf, "\n %s | %s%s", lineNumberSpacer, columnSpacer, columnIndicator)
|
||||
}
|
||||
|
||||
// If there are more lines to print, add a newline
|
||||
if i < len(err.Snippet.Lines)-1 {
|
||||
fmt.Fprintln(buf)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) Unwrap() error {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) Code() int {
|
||||
return CodeTaskfileDecode
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) WithMessage(format string, a ...any) *TaskfileDecodeError {
|
||||
err.Message = fmt.Sprintf(format, a...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) WithTypeMessage(t string) *TaskfileDecodeError {
|
||||
err.Message = fmt.Sprintf("cannot unmarshal %s into %s", err.Tag, t)
|
||||
return err
|
||||
}
|
||||
|
||||
func (err *TaskfileDecodeError) WithFileInfo(location string, b []byte, padding int) *TaskfileDecodeError {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := quick.Highlight(buf, string(b), "yaml", "terminal", "task"); err != nil {
|
||||
buf.WriteString(string(b))
|
||||
}
|
||||
lines := strings.Split(buf.String(), "\n")
|
||||
start := max(err.Line-1-padding, 0)
|
||||
end := min(err.Line+padding, len(lines)-1)
|
||||
|
||||
err.Location = location
|
||||
err.Snippet = TaskfileSnippet{
|
||||
Lines: lines[start:end],
|
||||
StartLine: start,
|
||||
EndLine: end,
|
||||
Padding: padding,
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func extractTypeErrorMessage(message string) string {
|
||||
matches := typeErrorRegex.FindStringSubmatch(message)
|
||||
if len(matches) == 2 {
|
||||
return matches[1]
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func digits(number int) int {
|
||||
count := 0
|
||||
for number != 0 {
|
||||
number /= 10
|
||||
count += 1
|
||||
}
|
||||
return count
|
||||
}
|
||||
@@ -12,14 +12,14 @@ const (
|
||||
const (
|
||||
CodeTaskfileNotFound int = iota + 100
|
||||
CodeTaskfileAlreadyExists
|
||||
CodeTaskfileInvalid
|
||||
CodeTaskfileDecode
|
||||
CodeTaskfileFetchFailed
|
||||
CodeTaskfileNotTrusted
|
||||
CodeTaskfileNotSecure
|
||||
CodeTaskfileCacheNotFound
|
||||
CodeTaskfileVersionCheckError
|
||||
CodeTaskfileNetworkTimeout
|
||||
_ // CodeTaskfileDuplicateInclude
|
||||
CodeTaskfileInvalid
|
||||
CodeTaskfileCycle
|
||||
)
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
CodeTaskCalledTooManyTimes
|
||||
CodeTaskCancelled
|
||||
CodeTaskMissingRequiredVars
|
||||
CodeTaskNotAllowedVars
|
||||
)
|
||||
|
||||
// TaskError extends the standard error interface with a Code method. This code will
|
||||
@@ -58,3 +59,8 @@ func Is(err, target error) bool {
|
||||
func As(err error, target any) bool {
|
||||
return errors.As(err, target)
|
||||
}
|
||||
|
||||
// Unwrap wraps the standard errors.Unwrap function so that we don't need to alias that package.
|
||||
func Unwrap(err error) error {
|
||||
return errors.Unwrap(err)
|
||||
}
|
||||
|
||||
@@ -80,6 +80,19 @@ func (err *TaskNameConflictError) Code() int {
|
||||
return CodeTaskNameConflict
|
||||
}
|
||||
|
||||
type TaskNameFlattenConflictError struct {
|
||||
TaskName string
|
||||
Include string
|
||||
}
|
||||
|
||||
func (err *TaskNameFlattenConflictError) Error() string {
|
||||
return fmt.Sprintf(`task: Found multiple tasks (%s) included by "%s""`, err.TaskName, err.Include)
|
||||
}
|
||||
|
||||
func (err *TaskNameFlattenConflictError) Code() int {
|
||||
return CodeTaskNameConflict
|
||||
}
|
||||
|
||||
// TaskCalledTooManyTimesError is returned when the maximum task call limit is
|
||||
// exceeded. This is to prevent infinite loops and cyclic dependencies.
|
||||
type TaskCalledTooManyTimesError struct {
|
||||
@@ -145,3 +158,29 @@ func (err *TaskMissingRequiredVars) Error() string {
|
||||
func (err *TaskMissingRequiredVars) Code() int {
|
||||
return CodeTaskMissingRequiredVars
|
||||
}
|
||||
|
||||
type NotAllowedVar struct {
|
||||
Value string
|
||||
Enum []string
|
||||
Name string
|
||||
}
|
||||
|
||||
type TaskNotAllowedVars struct {
|
||||
TaskName string
|
||||
NotAllowedVars []NotAllowedVar
|
||||
}
|
||||
|
||||
func (err *TaskNotAllowedVars) Error() string {
|
||||
var builder strings.Builder
|
||||
|
||||
builder.WriteString(fmt.Sprintf("task: Task %q cancelled because it is missing required variables:\n", err.TaskName))
|
||||
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))
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (err *TaskNotAllowedVars) Code() int {
|
||||
return CodeTaskNotAllowedVars
|
||||
}
|
||||
|
||||
17
errors/themes/task.xml
Normal file
17
errors/themes/task.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<style name="task">
|
||||
<entry type="Background" style="bg:#eee8d5"/>
|
||||
<entry type="Keyword" style="#859900"/>
|
||||
<entry type="KeywordConstant" style=""/>
|
||||
<entry type="KeywordNamespace" style="#dc322f"/>
|
||||
<entry type="KeywordType" style=""/>
|
||||
<entry type="Name" style="#268bd2"/>
|
||||
<entry type="NameBuiltin" style="#cb4b16"/>
|
||||
<entry type="NameClass" style="#cb4b16"/>
|
||||
<entry type="NameTag" style=""/>
|
||||
<entry type="Literal" style="#2aa198"/>
|
||||
<entry type="LiteralNumber" style=""/>
|
||||
<entry type="OperatorWord" style="#859900"/>
|
||||
<entry type="Comment" style="italic #93a1a1"/>
|
||||
<entry type="Generic" style="#d33682"/>
|
||||
<entry type="Text" style="#586e75"/>
|
||||
</style>
|
||||
50
go.mod
50
go.mod
@@ -1,35 +1,61 @@
|
||||
module github.com/go-task/task/v3
|
||||
|
||||
go 1.21.0
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver/v3 v3.2.1
|
||||
github.com/Ladicle/tabwriter v1.0.0
|
||||
github.com/Masterminds/semver/v3 v3.3.1
|
||||
github.com/alecthomas/chroma/v2 v2.14.0
|
||||
github.com/chainguard-dev/git-urls v1.0.2
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dominikbraun/graph v0.23.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/elliotchance/orderedmap/v2 v2.7.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/go-git/go-billy/v5 v5.6.1
|
||||
github.com/go-git/go-git/v5 v5.13.0
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0
|
||||
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90
|
||||
github.com/go-task/template v0.1.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mattn/go-zglob v0.0.4
|
||||
github.com/mattn/go-zglob v0.0.6
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/otiai10/copy v1.14.0
|
||||
github.com/radovskyb/watcher v1.0.7
|
||||
github.com/sajari/fuzzy v1.0.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/term v0.19.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/term v0.27.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/sh/v3 v3.8.0
|
||||
mvdan.cc/sh/v3 v3.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // 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-20210331224755-41bb18bfe9da // 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
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
209
go.sum
209
go.sum
@@ -1,27 +1,94 @@
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
|
||||
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
||||
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/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
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.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
|
||||
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
|
||||
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
|
||||
github.com/cyphar/filepath-securejoin v0.3.6/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.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elliotchance/orderedmap/v2 v2.6.0 h1:Zzo4k/u6hTRSt4NbYVphwOn5fBKlLpcbaV00INfJ1WI=
|
||||
github.com/elliotchance/orderedmap/v2 v2.6.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
|
||||
github.com/elliotchance/orderedmap/v2 v2.7.0 h1:WHuf0DRo63uLnldCPp9ojm3gskYwEdIIfAUVG5KhoOc=
|
||||
github.com/elliotchance/orderedmap/v2 v2.7.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
|
||||
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/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
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.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
||||
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
|
||||
github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA=
|
||||
github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE=
|
||||
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.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E=
|
||||
github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw=
|
||||
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.0.0-20240422130016-8f6b279b1e90 h1:JBbiZ2CXIZ9Upe3O2yI5+3ksWoa7hNVNi4BINs8TIrs=
|
||||
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
|
||||
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-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
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/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=
|
||||
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=
|
||||
@@ -29,44 +96,130 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
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-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM=
|
||||
github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
|
||||
github.com/mattn/go-zglob v0.0.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A=
|
||||
github.com/mattn/go-zglob v0.0.6/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/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.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
||||
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
||||
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
||||
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
||||
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.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20220520151302-bc2c85ada10a/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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.8.0 h1:ZxuJipLZwr/HLbASonmXtcvvC9HXY9d2lXZHnKGjFc8=
|
||||
mvdan.cc/sh/v3 v3.8.0/go.mod h1:w04623xkgBVo7/IUK89E0g8hBykgEpN0vgOj3RJr6MY=
|
||||
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
|
||||
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
|
||||
|
||||
7
hash.go
7
hash.go
@@ -1,6 +1,7 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-task/task/v3/internal/hash"
|
||||
@@ -8,11 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func (e *Executor) GetHash(t *ast.Task) (string, error) {
|
||||
r := t.Run
|
||||
if r == "" {
|
||||
r = e.Taskfile.Run
|
||||
}
|
||||
|
||||
r := cmp.Or(t.Run, e.Taskfile.Run)
|
||||
var h hash.HashFunc
|
||||
switch r {
|
||||
case "always":
|
||||
|
||||
35
help.go
35
help.go
@@ -7,8 +7,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/Ladicle/tabwriter"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/go-task/task/v3/internal/editors"
|
||||
@@ -105,7 +105,8 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
|
||||
for _, task := range tasks {
|
||||
e.Logger.FOutf(w, logger.Yellow, "* ")
|
||||
e.Logger.FOutf(w, logger.Green, task.Task)
|
||||
e.Logger.FOutf(w, logger.Default, ": \t%s", task.Desc)
|
||||
desc := strings.ReplaceAll(task.Desc, "\n", " ")
|
||||
e.Logger.FOutf(w, logger.Default, ": \t%s", desc)
|
||||
if len(task.Aliases) > 0 {
|
||||
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
|
||||
}
|
||||
@@ -159,23 +160,21 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
||||
}
|
||||
var g errgroup.Group
|
||||
for i := range tasks {
|
||||
task := tasks[i]
|
||||
j := i
|
||||
aliases := []string{}
|
||||
if len(task.Aliases) > 0 {
|
||||
aliases = task.Aliases
|
||||
if len(tasks[i].Aliases) > 0 {
|
||||
aliases = tasks[i].Aliases
|
||||
}
|
||||
g.Go(func() error {
|
||||
o.Tasks[j] = editors.Task{
|
||||
Name: task.Name(),
|
||||
Desc: task.Desc,
|
||||
Summary: task.Summary,
|
||||
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: task.Location.Line,
|
||||
Column: task.Location.Column,
|
||||
Taskfile: task.Location.Taskfile,
|
||||
Line: tasks[i].Location.Line,
|
||||
Column: tasks[i].Location.Column,
|
||||
Taskfile: tasks[i].Location.Taskfile,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -185,12 +184,12 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
||||
|
||||
// Get the fingerprinting method to use
|
||||
method := e.Taskfile.Method
|
||||
if task.Method != "" {
|
||||
method = task.Method
|
||||
if tasks[i].Method != "" {
|
||||
method = tasks[i].Method
|
||||
}
|
||||
upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), task,
|
||||
upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), tasks[i],
|
||||
fingerprint.WithMethod(method),
|
||||
fingerprint.WithTempDir(e.TempDir),
|
||||
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
||||
fingerprint.WithDry(e.Dry),
|
||||
fingerprint.WithLogger(e.Logger),
|
||||
)
|
||||
@@ -198,7 +197,7 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
||||
return err
|
||||
}
|
||||
|
||||
o.Tasks[j].UpToDate = upToDate
|
||||
o.Tasks[i].UpToDate = upToDate
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -3,14 +3,13 @@ package compiler
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
@@ -47,14 +46,12 @@ func (c *Compiler) FastGetVariables(t *ast.Task, call *ast.Call) (*ast.Vars, err
|
||||
|
||||
func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool) (*ast.Vars, error) {
|
||||
result := GetEnviron()
|
||||
if t != nil {
|
||||
specialVars, err := c.getSpecialVars(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range specialVars {
|
||||
result.Set(k, ast.Var{Value: v})
|
||||
}
|
||||
specialVars, err := c.getSpecialVars(t, call)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range specialVars {
|
||||
result.Set(k, ast.Var{Value: v})
|
||||
}
|
||||
|
||||
getRangeFunc := func(dir string) func(k string, v ast.Var) error {
|
||||
@@ -77,25 +74,13 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
|
||||
if err := cache.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Evaluate JSON
|
||||
if newVar.Json != "" {
|
||||
if err := json.Unmarshal([]byte(newVar.Json), &newVar.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Evaluate YAML
|
||||
if newVar.Yaml != "" {
|
||||
if err := yaml.Unmarshal([]byte(newVar.Yaml), &newVar.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// If the variable is not dynamic, we can set it and return
|
||||
if newVar.Value != nil || newVar.Sh == "" {
|
||||
// If the variable is already set, we can set it and return
|
||||
if newVar.Value != nil {
|
||||
result.Set(k, ast.Var{Value: newVar.Value})
|
||||
return nil
|
||||
}
|
||||
// If the variable is dynamic, we need to resolve it first
|
||||
static, err := c.HandleDynamicVar(newVar, dir)
|
||||
static, err := c.HandleDynamicVar(newVar, dir, env.GetFromVars(result))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -147,14 +132,19 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) HandleDynamicVar(v ast.Var, dir string) (string, error) {
|
||||
func (c *Compiler) HandleDynamicVar(v ast.Var, dir string, e []string) (string, error) {
|
||||
c.muDynamicCache.Lock()
|
||||
defer c.muDynamicCache.Unlock()
|
||||
|
||||
// If the variable is not dynamic or it is empty, return an empty string
|
||||
if v.Sh == nil || *v.Sh == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if c.dynamicCache == nil {
|
||||
c.dynamicCache = make(map[string]string, 30)
|
||||
}
|
||||
if result, ok := c.dynamicCache[v.Sh]; ok {
|
||||
if result, ok := c.dynamicCache[*v.Sh]; ok {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -165,10 +155,11 @@ func (c *Compiler) HandleDynamicVar(v ast.Var, dir string) (string, error) {
|
||||
|
||||
var stdout bytes.Buffer
|
||||
opts := &execext.RunCommandOptions{
|
||||
Command: v.Sh,
|
||||
Command: *v.Sh,
|
||||
Dir: dir,
|
||||
Stdout: &stdout,
|
||||
Stderr: c.Logger.Stderr,
|
||||
Env: e,
|
||||
}
|
||||
if err := execext.RunCommand(context.Background(), opts); err != nil {
|
||||
return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err)
|
||||
@@ -179,13 +170,13 @@ func (c *Compiler) HandleDynamicVar(v ast.Var, dir string) (string, error) {
|
||||
result := strings.TrimSuffix(stdout.String(), "\r\n")
|
||||
result = strings.TrimSuffix(result, "\n")
|
||||
|
||||
c.dynamicCache[v.Sh] = result
|
||||
c.Logger.VerboseErrf(logger.Magenta, "task: dynamic variable: %q result: %q\n", v.Sh, result)
|
||||
c.dynamicCache[*v.Sh] = result
|
||||
c.Logger.VerboseErrf(logger.Magenta, "task: dynamic variable: %q result: %q\n", *v.Sh, result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ResetCache clear the dymanic variables cache
|
||||
// ResetCache clear the dynamic variables cache
|
||||
func (c *Compiler) ResetCache() {
|
||||
c.muDynamicCache.Lock()
|
||||
defer c.muDynamicCache.Unlock()
|
||||
@@ -193,14 +184,23 @@ func (c *Compiler) ResetCache() {
|
||||
c.dynamicCache = nil
|
||||
}
|
||||
|
||||
func (c *Compiler) getSpecialVars(t *ast.Task) (map[string]string, error) {
|
||||
return map[string]string{
|
||||
"TASK": t.Task,
|
||||
func (c *Compiler) getSpecialVars(t *ast.Task, call *ast.Call) (map[string]string, error) {
|
||||
allVars := map[string]string{
|
||||
"TASK_EXE": filepath.ToSlash(os.Args[0]),
|
||||
"ROOT_TASKFILE": filepathext.SmartJoin(c.Dir, c.Entrypoint),
|
||||
"ROOT_DIR": c.Dir,
|
||||
"TASKFILE": t.Location.Taskfile,
|
||||
"TASKFILE_DIR": filepath.Dir(t.Location.Taskfile),
|
||||
"USER_WORKING_DIR": c.UserWorkingDir,
|
||||
"TASK_VERSION": version.GetVersion(),
|
||||
}, nil
|
||||
}
|
||||
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)
|
||||
}
|
||||
if call != nil {
|
||||
allVars["ALIAS"] = call.Task
|
||||
}
|
||||
|
||||
return allVars, nil
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
// GetEnviron the all return all environment variables encapsulated on a
|
||||
// ast.Vars
|
||||
func GetEnviron() *ast.Vars {
|
||||
m := &ast.Vars{}
|
||||
m := ast.NewVars()
|
||||
for _, e := range os.Environ() {
|
||||
keyVal := strings.SplitN(e, "=", 2)
|
||||
key, val := keyVal[0], keyVal[1]
|
||||
|
||||
@@ -2,6 +2,8 @@ package deepcopy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v2"
|
||||
)
|
||||
|
||||
type Copier[T any] interface {
|
||||
@@ -38,6 +40,21 @@ func Map[K comparable, V any](orig map[K]V) map[K]V {
|
||||
return c
|
||||
}
|
||||
|
||||
func OrderedMap[K comparable, V any](orig *orderedmap.OrderedMap[K, V]) *orderedmap.OrderedMap[K, V] {
|
||||
if orig.Len() == 0 {
|
||||
return orderedmap.NewOrderedMap[K, V]()
|
||||
}
|
||||
c := orderedmap.NewOrderedMap[K, V]()
|
||||
for pair := orig.Front(); pair != nil; pair = pair.Next() {
|
||||
if copyable, ok := any(pair.Value).(Copier[V]); ok {
|
||||
c.Set(pair.Key, copyable.DeepCopy())
|
||||
} else {
|
||||
c.Set(pair.Key, pair.Value)
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// TraverseStringsFunc runs the given function on every string in the given
|
||||
// value by traversing it recursively. If the given value is a string, the
|
||||
// function will run on a copy of the string and return it. If the value is a
|
||||
@@ -85,7 +102,7 @@ func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, erro
|
||||
|
||||
case reflect.Struct:
|
||||
// Loop over each field and call traverseFunc recursively
|
||||
for i := 0; i < v.NumField(); i += 1 {
|
||||
for i := range v.NumField() {
|
||||
if err := traverseFunc(copy.Field(i), v.Field(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -95,7 +112,7 @@ func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, erro
|
||||
// Create an empty copy from the original value's type
|
||||
copy.Set(reflect.MakeSlice(v.Type(), v.Len(), v.Cap()))
|
||||
// Loop over each element and call traverseFunc recursively
|
||||
for i := 0; i < v.Len(); i += 1 {
|
||||
for i := range v.Len() {
|
||||
if err := traverseFunc(copy.Index(i), v.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
15
internal/env/env.go
vendored
15
internal/env/env.go
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -12,14 +13,20 @@ func Get(t *ast.Task) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
return GetFromVars(t.Env)
|
||||
}
|
||||
|
||||
func GetFromVars(env *ast.Vars) []string {
|
||||
environ := os.Environ()
|
||||
for k, v := range t.Env.ToCacheMap() {
|
||||
|
||||
for k, v := range env.ToCacheMap() {
|
||||
if !isTypeAllowed(v) {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, alreadySet := os.LookupEnv(k); alreadySet {
|
||||
continue
|
||||
if !experiments.EnvPrecedence.Enabled {
|
||||
if _, alreadySet := os.LookupEnv(k); alreadySet {
|
||||
continue
|
||||
}
|
||||
}
|
||||
environ = append(environ, fmt.Sprintf("%s=%v", k, v))
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package execext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -14,6 +13,8 @@ import (
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
// RunCommandOptions is the options for the RunCommand func
|
||||
@@ -90,14 +91,6 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
return r.Run(ctx, p)
|
||||
}
|
||||
|
||||
// IsExitError returns true the given error is an exis status error
|
||||
func IsExitError(err error) bool {
|
||||
if _, ok := interp.IsExitStatus(err); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
|
||||
// if available.
|
||||
func Expand(s string) (string, error) {
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/Ladicle/tabwriter"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
@@ -29,6 +29,7 @@ var (
|
||||
RemoteTaskfiles Experiment
|
||||
AnyVariables Experiment
|
||||
MapVariables Experiment
|
||||
EnvPrecedence Experiment
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -37,6 +38,7 @@ func init() {
|
||||
RemoteTaskfiles = New("REMOTE_TASKFILES")
|
||||
AnyVariables = New("ANY_VARIABLES", "1", "2")
|
||||
MapVariables = New("MAP_VARIABLES", "1", "2")
|
||||
EnvPrecedence = New("ENV_PRECEDENCE")
|
||||
}
|
||||
|
||||
func New(xName string, enabledValues ...string) Experiment {
|
||||
@@ -70,6 +72,7 @@ func getEnvFilePath() string {
|
||||
fs := pflag.NewFlagSet("experiments", pflag.ContinueOnError)
|
||||
fs.StringVarP(&dir, "dir", "d", "", "Sets directory of execution.")
|
||||
fs.StringVarP(&taskfile, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml".`)
|
||||
fs.Usage = func() {}
|
||||
_ = fs.Parse(os.Args[1:])
|
||||
// If the directory is set, find a .env file in that directory.
|
||||
if dir != "" {
|
||||
@@ -104,5 +107,6 @@ func List(l *logger.Logger) error {
|
||||
printExperiment(w, l, GentleForce)
|
||||
printExperiment(w, l, RemoteTaskfiles)
|
||||
printExperiment(w, l, MapVariables)
|
||||
printExperiment(w, l, EnvPrecedence)
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func TestNormalizeFilename(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
In, Out string
|
||||
}{
|
||||
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
// | false | true | false |
|
||||
// | false | false | false |
|
||||
func TestIsTaskUpToDate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
task *ast.Task
|
||||
@@ -150,6 +152,8 @@ func TestIsTaskUpToDate(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mockStatusChecker := mocks.NewStatusCheckable(t)
|
||||
if tt.setupMockStatusChecker != nil {
|
||||
tt.setupMockStatusChecker(mockStatusChecker)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"cmp"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
@@ -38,6 +40,7 @@ var (
|
||||
Version bool
|
||||
Help bool
|
||||
Init bool
|
||||
Completion string
|
||||
List bool
|
||||
ListAll bool
|
||||
ListJson bool
|
||||
@@ -65,6 +68,7 @@ var (
|
||||
Experiments bool
|
||||
Download bool
|
||||
Offline bool
|
||||
ClearCache bool
|
||||
Timeout time.Duration
|
||||
)
|
||||
|
||||
@@ -75,10 +79,14 @@ func init() {
|
||||
log.Print(usage)
|
||||
pflag.PrintDefaults()
|
||||
}
|
||||
|
||||
offline, err := strconv.ParseBool(cmp.Or(os.Getenv("TASK_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.")
|
||||
pflag.StringVar(&Completion, "completion", "", "Generates shell completion script.")
|
||||
pflag.BoolVarP(&List, "list", "l", false, "Lists tasks with description of current Taskfile.")
|
||||
pflag.BoolVarP(&ListAll, "list-all", "a", false, "Lists tasks with or without a description.")
|
||||
pflag.BoolVarP(&ListJson, "json", "j", false, "Formats task list as JSON.")
|
||||
@@ -101,7 +109,7 @@ func init() {
|
||||
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 tasks to run concurrently.")
|
||||
pflag.IntVarP(&Concurrency, "concurrency", "C", 0, "Limit number of tasks to run concurrently.")
|
||||
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
|
||||
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.")
|
||||
@@ -117,8 +125,9 @@ 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", false, "Forces Task to only use local or cached Taskfiles.")
|
||||
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(&ClearCache, "clear-cache", false, "Clear the remote cache.")
|
||||
}
|
||||
|
||||
pflag.Parse()
|
||||
@@ -129,6 +138,10 @@ func Validate() error {
|
||||
return errors.New("task: You can't set both --download and --offline flags")
|
||||
}
|
||||
|
||||
if Download && ClearCache {
|
||||
return errors.New("task: You can't set both --download and --clear-cache flags")
|
||||
}
|
||||
|
||||
if Global && Dir != "" {
|
||||
log.Fatal("task: You can't set both --global and --dir")
|
||||
return nil
|
||||
|
||||
@@ -15,7 +15,7 @@ func Empty(*ast.Task) (string, error) {
|
||||
}
|
||||
|
||||
func Name(t *ast.Task) (string, error) {
|
||||
return t.Task, nil
|
||||
return fmt.Sprintf("%s:%s", t.Location.Taskfile, t.LocalName()), nil
|
||||
}
|
||||
|
||||
func Hash(t *ast.Task) (string, error) {
|
||||
|
||||
@@ -52,6 +52,30 @@ func Red() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_RED", color.FgRed)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightBlue() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_BLUE", color.FgHiBlue)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightGreen() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_GREEN", color.FgHiGreen)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightCyan() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_CYAN", color.FgHiCyan)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightYellow() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_YELLOW", color.FgHiYellow)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightMagenta() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_MAGENTA", color.FgHiMagenta)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func BrightRed() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BRIGHT_RED", color.FgHiRed)...).FprintfFunc()
|
||||
}
|
||||
|
||||
func envColor(env string, defaultColor color.Attribute) []color.Attribute {
|
||||
if os.Getenv("FORCE_COLOR") != "" {
|
||||
color.NoColor = false
|
||||
@@ -65,7 +89,7 @@ func envColor(env string, defaultColor color.Attribute) []color.Attribute {
|
||||
// Otherwise, split by semicolons (ANSI color codes) and use them as is.
|
||||
attributeStrs := strings.Split(override, ",")
|
||||
if len(attributeStrs) == 3 {
|
||||
attributeStrs = append([]string{"38", "2"}, attributeStrs...)
|
||||
attributeStrs = slices.Concat([]string{"38", "2"}, attributeStrs)
|
||||
} else {
|
||||
attributeStrs = strings.Split(override, ";")
|
||||
}
|
||||
@@ -156,7 +180,7 @@ func (l *Logger) Prompt(color Color, prompt string, defaultValue string, continu
|
||||
return errors.New("no continue values provided")
|
||||
}
|
||||
|
||||
l.Outf(color, "%s [%s/%s]\n", prompt, strings.ToLower(continueValues[0]), strings.ToUpper(defaultValue))
|
||||
l.Outf(color, "%s [%s/%s]: ", prompt, strings.ToLower(continueValues[0]), strings.ToUpper(defaultValue))
|
||||
|
||||
reader := bufio.NewReader(l.Stdin)
|
||||
input, err := reader.ReadString('\n')
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
package omap
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
"github.com/go-task/task/v3/internal/exp"
|
||||
)
|
||||
|
||||
// An OrderedMap is a wrapper around a regular map that maintains an ordered
|
||||
// list of the map's keys. This allows you to run deterministic and ordered
|
||||
// operations on the map such as printing/serializing/iterating.
|
||||
type OrderedMap[K cmp.Ordered, V any] struct {
|
||||
s []K
|
||||
m map[K]V
|
||||
}
|
||||
|
||||
// New will create a new OrderedMap of the given type and return it.
|
||||
func New[K cmp.Ordered, V any]() OrderedMap[K, V] {
|
||||
return OrderedMap[K, V]{
|
||||
s: make([]K, 0),
|
||||
m: make(map[K]V),
|
||||
}
|
||||
}
|
||||
|
||||
// FromMap will create a new OrderedMap from the given map. Since Golang maps
|
||||
// are unordered, the order of the created OrderedMap will be random.
|
||||
func FromMap[K cmp.Ordered, V any](m map[K]V) OrderedMap[K, V] {
|
||||
om := New[K, V]()
|
||||
om.m = m
|
||||
om.s = exp.Keys(m)
|
||||
return om
|
||||
}
|
||||
|
||||
func FromMapWithOrder[K cmp.Ordered, V any](m map[K]V, order []K) OrderedMap[K, V] {
|
||||
om := New[K, V]()
|
||||
if len(m) != len(order) {
|
||||
panic("length of map and order must be equal")
|
||||
}
|
||||
om.m = m
|
||||
om.s = order
|
||||
for key := range om.m {
|
||||
if !slices.Contains(om.s, key) {
|
||||
panic("order keys must match map keys")
|
||||
}
|
||||
}
|
||||
return om
|
||||
}
|
||||
|
||||
// Len will return the number of items in the map.
|
||||
func (om *OrderedMap[K, V]) Len() int {
|
||||
return len(om.s)
|
||||
}
|
||||
|
||||
// Set will set the value for a given key.
|
||||
func (om *OrderedMap[K, V]) Set(key K, value V) {
|
||||
if om.m == nil {
|
||||
om.m = make(map[K]V)
|
||||
}
|
||||
if _, ok := om.m[key]; !ok {
|
||||
om.s = append(om.s, key)
|
||||
}
|
||||
om.m[key] = value
|
||||
}
|
||||
|
||||
// Get will return the value for a given key.
|
||||
// If the key does not exist, it will return the zero value of the value type.
|
||||
func (om *OrderedMap[K, V]) Get(key K) V {
|
||||
value, ok := om.m[key]
|
||||
if !ok {
|
||||
var zero V
|
||||
return zero
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Exists will return whether or not the given key exists.
|
||||
func (om *OrderedMap[K, V]) Exists(key K) bool {
|
||||
_, ok := om.m[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Sort will sort the map.
|
||||
func (om *OrderedMap[K, V]) Sort() {
|
||||
slices.Sort(om.s)
|
||||
}
|
||||
|
||||
// SortFunc will sort the map using the given function.
|
||||
func (om *OrderedMap[K, V]) SortFunc(less func(i, j K) int) {
|
||||
slices.SortFunc(om.s, less)
|
||||
}
|
||||
|
||||
// Keys will return a slice of the map's keys in order.
|
||||
func (om *OrderedMap[K, V]) Keys() []K {
|
||||
return om.s
|
||||
}
|
||||
|
||||
// Values will return a slice of the map's values in order.
|
||||
func (om *OrderedMap[K, V]) Values() []V {
|
||||
var values []V
|
||||
for _, key := range om.s {
|
||||
values = append(values, om.m[key])
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Range will iterate over the map and call the given function for each key/value.
|
||||
func (om *OrderedMap[K, V]) Range(fn func(key K, value V) error) error {
|
||||
for _, key := range om.s {
|
||||
if err := fn(key, om.m[key]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge merges the given Vars into the caller one
|
||||
func (om *OrderedMap[K, V]) Merge(other OrderedMap[K, V]) {
|
||||
// nolint: errcheck
|
||||
other.Range(func(key K, value V) error {
|
||||
om.Set(key, value)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (om *OrderedMap[K, V]) DeepCopy() OrderedMap[K, V] {
|
||||
return OrderedMap[K, V]{
|
||||
s: deepcopy.Slice(om.s),
|
||||
m: deepcopy.Map(om.m),
|
||||
}
|
||||
}
|
||||
|
||||
func (om *OrderedMap[K, V]) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
// Even numbers contain the keys
|
||||
// Odd numbers contain the values
|
||||
case yaml.MappingNode:
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
// Decode the key
|
||||
keyNode := node.Content[i]
|
||||
var k K
|
||||
if err := keyNode.Decode(&k); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode the value
|
||||
valueNode := node.Content[i+1]
|
||||
var v V
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the key and value
|
||||
om.Set(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into variables", node.Line, node.ShortTag())
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package omap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestFromMap(t *testing.T) {
|
||||
m := map[int]string{3: "three", 1: "one", 2: "two"}
|
||||
om := FromMap(m)
|
||||
assert.Len(t, om.m, 3)
|
||||
assert.Len(t, om.s, 3)
|
||||
assert.ElementsMatch(t, []int{1, 2, 3}, om.s)
|
||||
for key, value := range m {
|
||||
assert.Equal(t, om.Get(key), value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetGetExists(t *testing.T) {
|
||||
om := New[int, string]()
|
||||
assert.False(t, om.Exists(1))
|
||||
assert.Equal(t, "", om.Get(1))
|
||||
om.Set(1, "one")
|
||||
assert.True(t, om.Exists(1))
|
||||
assert.Equal(t, "one", om.Get(1))
|
||||
}
|
||||
|
||||
func TestSort(t *testing.T) {
|
||||
om := New[int, string]()
|
||||
om.Set(3, "three")
|
||||
om.Set(1, "one")
|
||||
om.Set(2, "two")
|
||||
om.Sort()
|
||||
assert.Equal(t, []int{1, 2, 3}, om.s)
|
||||
}
|
||||
|
||||
func TestSortFunc(t *testing.T) {
|
||||
om := New[int, string]()
|
||||
om.Set(3, "three")
|
||||
om.Set(1, "one")
|
||||
om.Set(2, "two")
|
||||
om.SortFunc(func(a, b int) int {
|
||||
return b - a
|
||||
})
|
||||
assert.Equal(t, []int{3, 2, 1}, om.s)
|
||||
}
|
||||
|
||||
func TestKeysValues(t *testing.T) {
|
||||
om := New[int, string]()
|
||||
om.Set(3, "three")
|
||||
om.Set(1, "one")
|
||||
om.Set(2, "two")
|
||||
assert.Equal(t, []int{3, 1, 2}, om.Keys())
|
||||
assert.Equal(t, []string{"three", "one", "two"}, om.Values())
|
||||
}
|
||||
|
||||
func Range(t *testing.T) {
|
||||
om := New[int, string]()
|
||||
om.Set(3, "three")
|
||||
om.Set(1, "one")
|
||||
om.Set(2, "two")
|
||||
|
||||
expectedKeys := []int{3, 1, 2}
|
||||
expectedValues := []string{"three", "one", "two"}
|
||||
|
||||
keys := make([]int, 0, len(expectedKeys))
|
||||
values := make([]string, 0, len(expectedValues))
|
||||
|
||||
err := om.Range(func(key int, value string) error {
|
||||
keys = append(keys, key)
|
||||
values = append(values, value)
|
||||
return nil
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, expectedKeys, keys)
|
||||
assert.ElementsMatch(t, expectedValues, values)
|
||||
}
|
||||
|
||||
func TestOrderedMapMerge(t *testing.T) {
|
||||
om1 := New[string, int]()
|
||||
om1.Set("a", 1)
|
||||
om1.Set("b", 2)
|
||||
|
||||
om2 := New[string, int]()
|
||||
om2.Set("b", 3)
|
||||
om2.Set("c", 4)
|
||||
|
||||
om1.Merge(om2)
|
||||
|
||||
expectedKeys := []string{"a", "b", "c"}
|
||||
expectedValues := []int{1, 3, 4}
|
||||
|
||||
assert.Equal(t, len(expectedKeys), len(om1.s))
|
||||
assert.Equal(t, len(expectedKeys), len(om1.m))
|
||||
|
||||
for i, key := range expectedKeys {
|
||||
assert.True(t, om1.Exists(key))
|
||||
assert.Equal(t, expectedValues[i], om1.Get(key))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalYAML(t *testing.T) {
|
||||
yamlString := `
|
||||
3: three
|
||||
1: one
|
||||
2: two
|
||||
`
|
||||
var om OrderedMap[int, string]
|
||||
err := yaml.Unmarshal([]byte(yamlString), &om)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedKeys := []int{3, 1, 2}
|
||||
expectedValues := []string{"three", "one", "two"}
|
||||
|
||||
assert.Equal(t, expectedKeys, om.Keys())
|
||||
assert.Equal(t, expectedValues, om.Values())
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
@@ -15,7 +16,7 @@ type Output interface {
|
||||
type CloseFunc func(err error) error
|
||||
|
||||
// Build the Output for the requested ast.Output.
|
||||
func BuildFor(o *ast.Output) (Output, error) {
|
||||
func BuildFor(o *ast.Output, logger *logger.Logger) (Output, error) {
|
||||
switch o.Name {
|
||||
case "interleaved", "":
|
||||
if err := checkOutputGroupUnset(o); err != nil {
|
||||
@@ -32,7 +33,7 @@ func BuildFor(o *ast.Output) (Output, error) {
|
||||
if err := checkOutputGroupUnset(o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Prefixed{}, nil
|
||||
return NewPrefixed(logger), nil
|
||||
default:
|
||||
return nil, fmt.Errorf(`task: output style %q not recognized`, o.Name)
|
||||
}
|
||||
|
||||
@@ -7,16 +7,19 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/go-task/task/v3/internal/omap"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func TestInterleaved(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Interleaved{}
|
||||
w, _, _ := o.WrapWriter(&b, io.Discard, "", nil)
|
||||
@@ -28,6 +31,8 @@ func TestInterleaved(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGroup(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Group{}
|
||||
stdOut, stdErr, cleanup := o.WrapWriter(&b, io.Discard, "", nil)
|
||||
@@ -46,12 +51,15 @@ func TestGroup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGroupWithBeginEnd(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmpl := templater.Cache{
|
||||
Vars: &ast.Vars{
|
||||
OrderedMap: omap.FromMap(map[string]ast.Var{
|
||||
"VAR1": {Value: "example-value"},
|
||||
}),
|
||||
},
|
||||
Vars: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "VAR1",
|
||||
Value: ast.Var{Value: "example-value"},
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
var o output.Output = output.Group{
|
||||
@@ -59,6 +67,8 @@ func TestGroupWithBeginEnd(t *testing.T) {
|
||||
End: "::endgroup::",
|
||||
}
|
||||
t.Run("simple", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var b bytes.Buffer
|
||||
w, _, cleanup := o.WrapWriter(&b, io.Discard, "", &tmpl)
|
||||
|
||||
@@ -70,6 +80,8 @@ func TestGroupWithBeginEnd(t *testing.T) {
|
||||
assert.Equal(t, "::group::example-value\nfoo\nbar\nbaz\n::endgroup::\n", b.String())
|
||||
})
|
||||
t.Run("no output", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var b bytes.Buffer
|
||||
_, _, cleanup := o.WrapWriter(&b, io.Discard, "", &tmpl)
|
||||
require.NoError(t, cleanup(nil))
|
||||
@@ -78,6 +90,8 @@ func TestGroupWithBeginEnd(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGroupErrorOnlySwallowsOutputOnNoError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Group{
|
||||
ErrorOnly: true,
|
||||
@@ -92,6 +106,8 @@ func TestGroupErrorOnlySwallowsOutputOnNoError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGroupErrorOnlyShowsOutputOnError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Group{
|
||||
ErrorOnly: true,
|
||||
@@ -105,12 +121,16 @@ func TestGroupErrorOnlyShowsOutputOnError(t *testing.T) {
|
||||
assert.Equal(t, "std-out\nstd-err\n", b.String())
|
||||
}
|
||||
|
||||
func TestPrefixed(t *testing.T) {
|
||||
func TestPrefixed(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Prefixed{}
|
||||
l := &logger.Logger{
|
||||
Color: false,
|
||||
}
|
||||
|
||||
var o output.Output = output.NewPrefixed(l)
|
||||
w, _, cleanup := o.WrapWriter(&b, io.Discard, "prefix", nil)
|
||||
|
||||
t.Run("simple use cases", func(t *testing.T) {
|
||||
t.Run("simple use cases", func(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||
b.Reset()
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
@@ -120,7 +140,7 @@ func TestPrefixed(t *testing.T) {
|
||||
require.NoError(t, cleanup(nil))
|
||||
})
|
||||
|
||||
t.Run("multiple writes for a single line", func(t *testing.T) {
|
||||
t.Run("multiple writes for a single line", func(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||
b.Reset()
|
||||
|
||||
for _, char := range []string{"T", "e", "s", "t", "!"} {
|
||||
@@ -132,3 +152,41 @@ func TestPrefixed(t *testing.T) {
|
||||
assert.Equal(t, "[prefix] Test!\n", b.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrefixedWithColor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
color.NoColor = false
|
||||
|
||||
var b bytes.Buffer
|
||||
l := &logger.Logger{
|
||||
Color: true,
|
||||
}
|
||||
|
||||
var o output.Output = output.NewPrefixed(l)
|
||||
|
||||
writers := make([]io.Writer, 16)
|
||||
for i := range writers {
|
||||
writers[i], _, _ = o.WrapWriter(&b, io.Discard, fmt.Sprintf("prefix-%d", i), nil)
|
||||
}
|
||||
|
||||
t.Run("colors should loop", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for i, w := range writers {
|
||||
b.Reset()
|
||||
|
||||
color := output.PrefixColorSequence[i%len(output.PrefixColorSequence)]
|
||||
|
||||
var prefix bytes.Buffer
|
||||
l.FOutf(&prefix, color, fmt.Sprintf("prefix-%d", i))
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(
|
||||
t,
|
||||
fmt.Sprintf("[%s] foo\n[%s] bar\n", prefix.String(), prefix.String()),
|
||||
b.String(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,21 +5,39 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
)
|
||||
|
||||
type Prefixed struct{}
|
||||
type Prefixed struct {
|
||||
logger *logger.Logger
|
||||
seen map[string]uint
|
||||
counter *uint
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
|
||||
pw := &prefixWriter{writer: stdOut, prefix: prefix}
|
||||
func NewPrefixed(logger *logger.Logger) *Prefixed {
|
||||
var counter uint
|
||||
|
||||
return &Prefixed{
|
||||
seen: make(map[string]uint),
|
||||
counter: &counter,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
|
||||
pw := &prefixWriter{writer: stdOut, prefix: prefix, prefixed: p}
|
||||
return pw, pw, func(error) error { return pw.close() }
|
||||
}
|
||||
|
||||
type prefixWriter struct {
|
||||
writer io.Writer
|
||||
prefix string
|
||||
buff bytes.Buffer
|
||||
writer io.Writer
|
||||
prefixed *Prefixed
|
||||
prefix string
|
||||
buff bytes.Buffer
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) Write(p []byte) (int, error) {
|
||||
@@ -56,6 +74,11 @@ func (pw *prefixWriter) writeOutputLines(force bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
var PrefixColorSequence = []logger.Color{
|
||||
logger.Yellow, logger.Blue, logger.Magenta, logger.Cyan, logger.Green, logger.Red,
|
||||
logger.BrightYellow, logger.BrightBlue, logger.BrightMagenta, logger.BrightCyan, logger.BrightGreen, logger.BrightRed,
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) writeLine(line string) error {
|
||||
if line == "" {
|
||||
return nil
|
||||
@@ -63,6 +86,30 @@ func (pw *prefixWriter) writeLine(line string) error {
|
||||
if !strings.HasSuffix(line, "\n") {
|
||||
line += "\n"
|
||||
}
|
||||
_, err := fmt.Fprintf(pw.writer, "[%s] %s", pw.prefix, line)
|
||||
|
||||
defer pw.prefixed.mutex.Unlock()
|
||||
pw.prefixed.mutex.Lock()
|
||||
|
||||
idx, ok := pw.prefixed.seen[pw.prefix]
|
||||
|
||||
if !ok {
|
||||
idx = *pw.prefixed.counter
|
||||
pw.prefixed.seen[pw.prefix] = idx
|
||||
|
||||
*pw.prefixed.counter++
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprint(pw.writer, "["); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
color := PrefixColorSequence[idx%uint(len(PrefixColorSequence))]
|
||||
pw.prefixed.logger.FOutf(pw.writer, color, pw.prefix)
|
||||
|
||||
if _, err := fmt.Fprint(pw.writer, "] "); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := fmt.Fprint(pw.writer, line)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAlphaNumericWithRootTasksFirst_Sort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
task1 := &ast.Task{Task: "task1"}
|
||||
task2 := &ast.Task{Task: "task2"}
|
||||
task3 := &ast.Task{Task: "ns1:task3"}
|
||||
@@ -40,6 +42,8 @@ func TestAlphaNumericWithRootTasksFirst_Sort(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := &AlphaNumericWithRootTasksFirst{}
|
||||
s.Sort(tt.tasks)
|
||||
assert.Equal(t, tt.want, tt.tasks)
|
||||
@@ -48,6 +52,8 @@ func TestAlphaNumericWithRootTasksFirst_Sort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlphaNumeric_Sort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
task1 := &ast.Task{Task: "task1"}
|
||||
task2 := &ast.Task{Task: "task2"}
|
||||
task3 := &ast.Task{Task: "ns1:task3"}
|
||||
@@ -69,6 +75,8 @@ func TestAlphaNumeric_Sort(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := &AlphaNumeric{}
|
||||
s.Sort(tt.tasks)
|
||||
assert.Equal(t, tt.tasks, tt.want)
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
func PrintTasks(l *logger.Logger, t *ast.Taskfile, c []*ast.Call) {
|
||||
for i, call := range c {
|
||||
PrintSpaceBetweenSummaries(l, i)
|
||||
PrintTask(l, t.Tasks.Get(call.Task))
|
||||
if task, ok := t.Tasks.Get(call.Task); ok {
|
||||
PrintTask(l, task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
)
|
||||
|
||||
func TestPrintsDependenciesIfPresent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buffer, l := createDummyLogger()
|
||||
task := &ast.Task{
|
||||
Deps: []*ast.Dep{
|
||||
@@ -38,6 +40,8 @@ func createDummyLogger() (*bytes.Buffer, logger.Logger) {
|
||||
}
|
||||
|
||||
func TestDoesNotPrintDependenciesIfMissing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buffer, l := createDummyLogger()
|
||||
task := &ast.Task{
|
||||
Deps: []*ast.Dep{},
|
||||
@@ -49,6 +53,8 @@ func TestDoesNotPrintDependenciesIfMissing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPrintTaskName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buffer, l := createDummyLogger()
|
||||
task := &ast.Task{
|
||||
Task: "my-task-name",
|
||||
@@ -60,6 +66,8 @@ func TestPrintTaskName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPrintTaskCommandsIfPresent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buffer, l := createDummyLogger()
|
||||
task := &ast.Task{
|
||||
Cmds: []*ast.Cmd{
|
||||
@@ -78,6 +86,8 @@ func TestPrintTaskCommandsIfPresent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoesNotPrintCommandIfMissing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buffer, l := createDummyLogger()
|
||||
task := &ast.Task{
|
||||
Cmds: []*ast.Cmd{},
|
||||
@@ -89,6 +99,8 @@ func TestDoesNotPrintCommandIfMissing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLayout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buffer, l := createDummyLogger()
|
||||
task := &ast.Task{
|
||||
Task: "sample-task",
|
||||
@@ -123,6 +135,8 @@ commands:
|
||||
}
|
||||
|
||||
func TestPrintDescriptionAsFallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buffer, l := createDummyLogger()
|
||||
taskWithoutSummary := &ast.Task{
|
||||
Desc: "description",
|
||||
@@ -150,13 +164,15 @@ func TestPrintDescriptionAsFallback(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPrintAllWithSpaces(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
buffer, l := createDummyLogger()
|
||||
|
||||
t1 := &ast.Task{Task: "t1"}
|
||||
t2 := &ast.Task{Task: "t2"}
|
||||
t3 := &ast.Task{Task: "t3"}
|
||||
|
||||
tasks := ast.Tasks{}
|
||||
tasks := ast.NewTasks()
|
||||
tasks.Set("t1", t1)
|
||||
tasks.Set("t2", t2)
|
||||
tasks.Set("t3", t3)
|
||||
|
||||
@@ -17,8 +17,9 @@ var templateFuncs template.FuncMap
|
||||
|
||||
func init() {
|
||||
taskFuncs := template.FuncMap{
|
||||
"OS": func() string { return runtime.GOOS },
|
||||
"ARCH": func() string { return runtime.GOARCH },
|
||||
"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", " ")
|
||||
|
||||
@@ -2,6 +2,7 @@ package templater
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
@@ -40,7 +41,15 @@ func ResolveRef(ref string, cache *Cache) any {
|
||||
cache.cacheMap = cache.Vars.ToCacheMap()
|
||||
}
|
||||
|
||||
val, err := template.ResolveRef(ref, cache.cacheMap)
|
||||
if ref == "." {
|
||||
return cache.cacheMap
|
||||
}
|
||||
t, err := template.New("resolver").Funcs(templateFuncs).Parse(fmt.Sprintf("{{%s}}", ref))
|
||||
if err != nil {
|
||||
cache.err = err
|
||||
return nil
|
||||
}
|
||||
val, err := t.Resolve(cache.cacheMap)
|
||||
if err != nil {
|
||||
cache.err = err
|
||||
return nil
|
||||
@@ -119,8 +128,6 @@ func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) ast.Var
|
||||
Live: v.Live,
|
||||
Ref: v.Ref,
|
||||
Dir: v.Dir,
|
||||
Json: ReplaceWithExtra(v.Json, cache, extra),
|
||||
Yaml: ReplaceWithExtra(v.Yaml, cache, extra),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,11 +140,11 @@ func ReplaceVarsWithExtra(vars *ast.Vars, cache *Cache, extra map[string]any) *a
|
||||
return nil
|
||||
}
|
||||
|
||||
var newVars ast.Vars
|
||||
newVars := ast.NewVars()
|
||||
_ = vars.Range(func(k string, v ast.Var) error {
|
||||
newVars.Set(k, ReplaceVarWithExtra(v, cache, extra))
|
||||
return nil
|
||||
})
|
||||
|
||||
return &newVars
|
||||
return newVars
|
||||
}
|
||||
|
||||
@@ -5,21 +5,29 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var version = ""
|
||||
|
||||
func GetVersion() string {
|
||||
if version != "" {
|
||||
return version
|
||||
}
|
||||
var (
|
||||
version = ""
|
||||
sum = ""
|
||||
)
|
||||
|
||||
func init() {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok || info.Main.Version == "" {
|
||||
return "unknown"
|
||||
version = "unknown"
|
||||
} else {
|
||||
if version == "" {
|
||||
version = info.Main.Version
|
||||
}
|
||||
if sum == "" {
|
||||
sum = info.Main.Sum
|
||||
}
|
||||
}
|
||||
|
||||
ver := info.Main.Version
|
||||
if info.Main.Sum != "" {
|
||||
ver += fmt.Sprintf(" (%s)", info.Main.Sum)
|
||||
}
|
||||
return ver
|
||||
}
|
||||
|
||||
func GetVersion() string {
|
||||
return version
|
||||
}
|
||||
|
||||
func GetVersionWithSum() string {
|
||||
return fmt.Sprintf("%s (%s)", version, sum)
|
||||
}
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.37.1",
|
||||
"version": "3.40.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.37.1",
|
||||
"version": "3.40.1",
|
||||
"description": "A task runner / simpler Make alternative written in Go",
|
||||
"scripts": {
|
||||
"postinstall": "go-npm install",
|
||||
@@ -22,7 +22,7 @@
|
||||
"build-tool",
|
||||
"task-runner"
|
||||
],
|
||||
"author": "Andrey Nering",
|
||||
"author": "The Task authors",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/go-task/task/issues"
|
||||
|
||||
@@ -2,8 +2,8 @@ package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
|
||||
31
requires.go
31
requires.go
@@ -1,26 +1,32 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func (e *Executor) areTaskRequiredVarsSet(ctx context.Context, t *ast.Task, call *ast.Call) error {
|
||||
func (e *Executor) areTaskRequiredVarsSet(t *ast.Task) error {
|
||||
if t.Requires == nil || len(t.Requires.Vars) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
vars, err := e.Compiler.GetVariables(t, call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var missingVars []string
|
||||
var notAllowedValuesVars []errors.NotAllowedVar
|
||||
for _, requiredVar := range t.Requires.Vars {
|
||||
if !vars.Exists(requiredVar) {
|
||||
missingVars = append(missingVars, requiredVar)
|
||||
value, ok := t.Vars.Get(requiredVar.Name)
|
||||
if !ok {
|
||||
missingVars = append(missingVars, requiredVar.Name)
|
||||
} else {
|
||||
value, isString := value.Value.(string)
|
||||
if isString && requiredVar.Enum != nil && !slices.Contains(requiredVar.Enum, value) {
|
||||
notAllowedValuesVars = append(notAllowedValuesVars, errors.NotAllowedVar{
|
||||
Value: value,
|
||||
Enum: requiredVar.Enum,
|
||||
Name: requiredVar.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +37,12 @@ func (e *Executor) areTaskRequiredVarsSet(ctx context.Context, t *ast.Task, call
|
||||
}
|
||||
}
|
||||
|
||||
if len(notAllowedValuesVars) > 0 {
|
||||
return &errors.TaskNotAllowedVars{
|
||||
TaskName: t.Name(),
|
||||
NotAllowedVars: notAllowedValuesVars,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
39
setup.go
39
setup.go
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -69,7 +70,7 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
|
||||
e.Download,
|
||||
e.Offline,
|
||||
e.Timeout,
|
||||
e.TempDir,
|
||||
e.TempDir.Remote,
|
||||
e.Logger,
|
||||
)
|
||||
graph, err := reader.Read()
|
||||
@@ -95,7 +96,7 @@ func (e *Executor) setupFuzzyModel() {
|
||||
words = append(words, taskName)
|
||||
|
||||
for _, task := range e.Taskfile.Tasks.Values() {
|
||||
words = append(words, task.Aliases...)
|
||||
words = slices.Concat(words, task.Aliases)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,12 +105,15 @@ func (e *Executor) setupFuzzyModel() {
|
||||
}
|
||||
|
||||
func (e *Executor) setupTempDir() error {
|
||||
if e.TempDir != "" {
|
||||
if e.TempDir != (TempDir{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if os.Getenv("TASK_TEMP_DIR") == "" {
|
||||
e.TempDir = filepathext.SmartJoin(e.Dir, ".task")
|
||||
e.TempDir = TempDir{
|
||||
Remote: filepathext.SmartJoin(e.Dir, ".task"),
|
||||
Fingerprint: filepathext.SmartJoin(e.Dir, ".task"),
|
||||
}
|
||||
} else if filepath.IsAbs(os.Getenv("TASK_TEMP_DIR")) || strings.HasPrefix(os.Getenv("TASK_TEMP_DIR"), "~") {
|
||||
tempDir, err := execext.Expand(os.Getenv("TASK_TEMP_DIR"))
|
||||
if err != nil {
|
||||
@@ -117,9 +121,28 @@ func (e *Executor) setupTempDir() error {
|
||||
}
|
||||
projectDir, _ := filepath.Abs(e.Dir)
|
||||
projectName := filepath.Base(projectDir)
|
||||
e.TempDir = filepathext.SmartJoin(tempDir, projectName)
|
||||
e.TempDir = TempDir{
|
||||
Remote: tempDir,
|
||||
Fingerprint: filepathext.SmartJoin(tempDir, projectName),
|
||||
}
|
||||
|
||||
} else {
|
||||
e.TempDir = filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR"))
|
||||
e.TempDir = TempDir{
|
||||
Remote: filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR")),
|
||||
Fingerprint: filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR")),
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("TASK_REMOTE_DIR") != "" {
|
||||
if filepath.IsAbs(os.Getenv("TASK_REMOTE_DIR")) || strings.HasPrefix(os.Getenv("TASK_REMOTE_DIR"), "~") {
|
||||
remoteTempDir, err := execext.Expand(os.Getenv("TASK_REMOTE_DIR"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.TempDir.Remote = remoteTempDir
|
||||
} else {
|
||||
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -155,7 +178,7 @@ func (e *Executor) setupOutput() error {
|
||||
}
|
||||
|
||||
var err error
|
||||
e.Output, err = output.BuildFor(&e.OutputStyle)
|
||||
e.Output, err = output.BuildFor(&e.OutputStyle, e.Logger)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -190,7 +213,7 @@ func (e *Executor) readDotEnvFiles() error {
|
||||
}
|
||||
|
||||
err = env.Range(func(key string, value ast.Var) error {
|
||||
if ok := e.Taskfile.Env.Exists(key); !ok {
|
||||
if _, ok := e.Taskfile.Env.Get(key); !ok {
|
||||
e.Taskfile.Env.Set(key, value)
|
||||
}
|
||||
return nil
|
||||
|
||||
15
signals.go
15
signals.go
@@ -8,24 +8,25 @@ import (
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
)
|
||||
|
||||
const interruptSignalsCount = 3
|
||||
|
||||
// NOTE(@andreynering): This function intercepts SIGINT and SIGTERM signals
|
||||
// so the Task process is not killed immediately and processes running have
|
||||
// time to do cleanup work.
|
||||
func (e *Executor) InterceptInterruptSignals() {
|
||||
ch := make(chan os.Signal, 3)
|
||||
ch := make(chan os.Signal, interruptSignalsCount)
|
||||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
for i := 1; i <= 3; i++ {
|
||||
for i := range interruptSignalsCount {
|
||||
sig := <-ch
|
||||
|
||||
if i < 3 {
|
||||
e.Logger.Outf(logger.Yellow, "task: Signal received: %q\n", sig)
|
||||
continue
|
||||
if i+1 >= interruptSignalsCount {
|
||||
e.Logger.Errf(logger.Red, "task: Signal received for the third time: %q. Forcing shutdown\n", sig)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
e.Logger.Errf(logger.Red, "task: Signal received for the third time: %q. Forcing shutdown\n", sig)
|
||||
os.Exit(1)
|
||||
e.Logger.Outf(logger.Yellow, "task: Signal received: %q\n", sig)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -20,9 +20,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
SLEEPIT, _ = filepath.Abs("./bin/sleepit")
|
||||
)
|
||||
var SLEEPIT, _ = filepath.Abs("./bin/sleepit")
|
||||
|
||||
func TestSignalSentToProcessGroup(t *testing.T) {
|
||||
task, err := getTaskPath()
|
||||
@@ -147,7 +145,7 @@ func TestSignalSentToProcessGroup(t *testing.T) {
|
||||
// where the negative PID means the corresponding process group. Note that
|
||||
// this negative PID works only as long as the caller of the kill(2) system
|
||||
// call has a different PID, which is the case for this test.
|
||||
for i := 1; i <= tc.sendSigs; i++ {
|
||||
for range tc.sendSigs - 1 {
|
||||
if err := syscall.Kill(-sut.Process.Pid, syscall.SIGINT); err != nil {
|
||||
t.Fatalf("sending INT signal to the process group: %v", err)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func (e *Executor) Status(ctx context.Context, calls ...*ast.Call) error {
|
||||
// Check if the task is up-to-date
|
||||
isUpToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
|
||||
fingerprint.WithMethod(method),
|
||||
fingerprint.WithTempDir(e.TempDir),
|
||||
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
||||
fingerprint.WithDry(e.Dry),
|
||||
fingerprint.WithLogger(e.Logger),
|
||||
)
|
||||
@@ -46,7 +46,7 @@ func (e *Executor) statusOnError(t *ast.Task) error {
|
||||
if method == "" {
|
||||
method = e.Taskfile.Method
|
||||
}
|
||||
checker, err := fingerprint.NewSourcesChecker(method, e.TempDir, e.Dry)
|
||||
checker, err := fingerprint.NewSourcesChecker(method, e.TempDir.Fingerprint, e.Dry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
83
task.go
83
task.go
@@ -11,6 +11,8 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
@@ -34,13 +36,18 @@ const (
|
||||
MaximumTaskCall = 1000
|
||||
)
|
||||
|
||||
type TempDir struct {
|
||||
Remote string
|
||||
Fingerprint string
|
||||
}
|
||||
|
||||
// Executor executes a Taskfile
|
||||
type Executor struct {
|
||||
Taskfile *ast.Taskfile
|
||||
|
||||
Dir string
|
||||
Entrypoint string
|
||||
TempDir string
|
||||
TempDir TempDir
|
||||
Force bool
|
||||
ForceAll bool
|
||||
Insecure bool
|
||||
@@ -169,6 +176,10 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := e.areTaskRequiredVarsSet(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t, err = e.CompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -183,16 +194,6 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
||||
release := e.acquireConcurrencyLimit()
|
||||
defer release()
|
||||
|
||||
if t.Prompt != "" {
|
||||
if err := e.Logger.Prompt(logger.Yellow, t.Prompt, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) {
|
||||
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
|
||||
} else if errors.Is(err, logger.ErrPromptCancelled) {
|
||||
return &errors.TaskCancelledByUserError{TaskName: call.Task}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return 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 {
|
||||
@@ -205,10 +206,6 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.areTaskRequiredVarsSet(ctx, t, call); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preCondMet, err := e.areTaskPreconditionsMet(ctx, t)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -222,7 +219,7 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
||||
|
||||
upToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
|
||||
fingerprint.WithMethod(method),
|
||||
fingerprint.WithTempDir(e.TempDir),
|
||||
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
||||
fingerprint.WithDry(e.Dry),
|
||||
fingerprint.WithLogger(e.Logger),
|
||||
)
|
||||
@@ -238,13 +235,27 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range t.Prompt {
|
||||
if p != "" && !e.Dry {
|
||||
if err := e.Logger.Prompt(logger.Yellow, p, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) {
|
||||
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
|
||||
} else if errors.Is(err, logger.ErrPromptCancelled) {
|
||||
return &errors.TaskCancelledByUserError{TaskName: call.Task}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.mkdir(t); err != nil {
|
||||
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v\n", t.Dir, err)
|
||||
}
|
||||
|
||||
var deferredExitCode uint8
|
||||
|
||||
for i := range t.Cmds {
|
||||
if t.Cmds[i].Defer {
|
||||
defer e.runDeferred(t, call, i)
|
||||
defer e.runDeferred(t, call, i, &deferredExitCode)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -253,9 +264,13 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v\n", err2)
|
||||
}
|
||||
|
||||
if execext.IsExitError(err) && t.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
|
||||
continue
|
||||
exitCode, isExitError := interp.IsExitStatus(err)
|
||||
if isExitError {
|
||||
if t.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
|
||||
continue
|
||||
}
|
||||
deferredExitCode = exitCode
|
||||
}
|
||||
|
||||
if call.Indirect {
|
||||
@@ -307,10 +322,26 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int) {
|
||||
func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int, 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{}
|
||||
|
||||
if deferredExitCode != nil && *deferredExitCode > 0 {
|
||||
extra["EXIT_CODE"] = fmt.Sprintf("%d", *deferredExitCode)
|
||||
}
|
||||
|
||||
cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
|
||||
|
||||
if err := e.runCommand(ctx, t, call, i); err != nil {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: ignored error in deferred cmd: %s\n", err.Error())
|
||||
}
|
||||
@@ -367,7 +398,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *ast.Call,
|
||||
if closeErr := close(err); closeErr != nil {
|
||||
e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr)
|
||||
}
|
||||
if execext.IsExitError(err) && cmd.IgnoreError {
|
||||
if _, isExitError := interp.IsExitStatus(err); isExitError && cmd.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v\n", t.Name(), err)
|
||||
return nil
|
||||
}
|
||||
@@ -420,7 +451,7 @@ func (e *Executor) GetTask(call *ast.Call) (*ast.Task, error) {
|
||||
case 0: // Carry on
|
||||
case 1:
|
||||
if call.Vars == nil {
|
||||
call.Vars = &ast.Vars{}
|
||||
call.Vars = ast.NewVars()
|
||||
}
|
||||
call.Vars.Set("MATCH", ast.Var{Value: matchingTasks[0].Wildcards})
|
||||
return matchingTasks[0].Task, nil
|
||||
@@ -489,14 +520,12 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) {
|
||||
|
||||
// Compile the list of tasks
|
||||
for i := range tasks {
|
||||
idx := i
|
||||
task := tasks[idx]
|
||||
g.Go(func() error {
|
||||
compiledTask, err := e.FastCompiledTask(&ast.Call{Task: task.Task})
|
||||
compiledTask, err := e.FastCompiledTask(&ast.Call{Task: tasks[i].Task})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tasks[idx] = compiledTask
|
||||
tasks[i] = compiledTask
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
959
task_test.go
959
task_test.go
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,9 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
)
|
||||
|
||||
@@ -46,7 +45,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var cmd string
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
c.Cmd = cmd
|
||||
return nil
|
||||
@@ -76,11 +75,13 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
|
||||
// A deferred command
|
||||
var deferredCmd struct {
|
||||
Defer string
|
||||
Defer string
|
||||
Silent bool
|
||||
}
|
||||
if err := node.Decode(&deferredCmd); err == nil && deferredCmd.Defer != "" {
|
||||
c.Defer = true
|
||||
c.Cmd = deferredCmd.Defer
|
||||
c.Silent = deferredCmd.Silent
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -92,6 +93,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
c.Defer = true
|
||||
c.Task = deferredCall.Defer.Task
|
||||
c.Vars = deferredCall.Defer.Vars
|
||||
c.Silent = deferredCall.Defer.Silent
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -110,8 +112,8 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: invalid keys in command", node.Line)
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in command")
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into command", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("command")
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
// Dep is a task dependency
|
||||
@@ -32,7 +32,7 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var task string
|
||||
if err := node.Decode(&task); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
d.Task = task
|
||||
return nil
|
||||
@@ -45,7 +45,7 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
|
||||
Silent bool
|
||||
}
|
||||
if err := node.Decode(&taskCall); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
d.Task = taskCall.Task
|
||||
d.For = taskCall.For
|
||||
@@ -54,5 +54,5 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot unmarshal %s into dependency", node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("dependency")
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
)
|
||||
|
||||
type For struct {
|
||||
From string
|
||||
List []any
|
||||
Var string
|
||||
Split string
|
||||
As string
|
||||
From string
|
||||
List []any
|
||||
Matrix *Matrix
|
||||
Var string
|
||||
Split string
|
||||
As string
|
||||
}
|
||||
|
||||
func (f *For) UnmarshalYAML(node *yaml.Node) error {
|
||||
@@ -22,7 +22,7 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var from string
|
||||
if err := node.Decode(&from); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
f.From = from
|
||||
return nil
|
||||
@@ -30,28 +30,35 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.SequenceNode:
|
||||
var list []any
|
||||
if err := node.Decode(&list); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
f.List = list
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var forStruct struct {
|
||||
Var string
|
||||
Split string
|
||||
As string
|
||||
Matrix *Matrix
|
||||
Var string
|
||||
Split string
|
||||
As string
|
||||
}
|
||||
if err := node.Decode(&forStruct); err == nil && forStruct.Var != "" {
|
||||
f.Var = forStruct.Var
|
||||
f.Split = forStruct.Split
|
||||
f.As = forStruct.As
|
||||
return nil
|
||||
if err := node.Decode(&forStruct); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: invalid keys in for", node.Line)
|
||||
if forStruct.Var == "" && forStruct.Matrix.Len() == 0 {
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in for")
|
||||
}
|
||||
if forStruct.Var != "" && forStruct.Matrix.Len() != 0 {
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage("cannot use both var and matrix in for")
|
||||
}
|
||||
f.Matrix = forStruct.Matrix
|
||||
f.Var = forStruct.Var
|
||||
f.Split = forStruct.Split
|
||||
f.As = forStruct.As
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into for", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("for")
|
||||
}
|
||||
|
||||
func (f *For) DeepCopy() *For {
|
||||
@@ -59,10 +66,11 @@ func (f *For) DeepCopy() *For {
|
||||
return nil
|
||||
}
|
||||
return &For{
|
||||
From: f.From,
|
||||
List: deepcopy.Slice(f.List),
|
||||
Var: f.Var,
|
||||
Split: f.Split,
|
||||
As: f.As,
|
||||
From: f.From,
|
||||
List: deepcopy.Slice(f.List),
|
||||
Matrix: f.Matrix.DeepCopy(),
|
||||
Var: f.Var,
|
||||
Split: f.Split,
|
||||
As: f.As,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
type Glob struct {
|
||||
@@ -13,20 +13,22 @@ type Glob struct {
|
||||
|
||||
func (g *Glob) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.ScalarNode:
|
||||
g.Glob = node.Value
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var glob struct {
|
||||
Exclude string
|
||||
}
|
||||
if err := node.Decode(&glob); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
g.Glob = glob.Exclude
|
||||
g.Negate = true
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("glob")
|
||||
}
|
||||
|
||||
@@ -1,68 +1,134 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
omap "github.com/go-task/task/v3/internal/omap"
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
)
|
||||
|
||||
// Include represents information about included taskfiles
|
||||
type Include struct {
|
||||
Namespace string
|
||||
Taskfile string
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Aliases []string
|
||||
AdvancedImport bool
|
||||
Vars *Vars
|
||||
type (
|
||||
// Include represents information about included taskfiles
|
||||
Include struct {
|
||||
Namespace string
|
||||
Taskfile string
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Aliases []string
|
||||
Excludes []string
|
||||
AdvancedImport bool
|
||||
Vars *Vars
|
||||
Flatten bool
|
||||
}
|
||||
// Includes is an ordered map of namespaces to includes.
|
||||
Includes struct {
|
||||
om *orderedmap.OrderedMap[string, *Include]
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
// An IncludeElement is a key-value pair that is used for initializing an
|
||||
// Includes structure.
|
||||
IncludeElement orderedmap.Element[string, *Include]
|
||||
)
|
||||
|
||||
// NewIncludes creates a new instance of Includes and initializes it with the
|
||||
// provided set of elements, if any. The elements are added in the order they
|
||||
// are passed.
|
||||
func NewIncludes(els ...*IncludeElement) *Includes {
|
||||
includes := &Includes{
|
||||
om: orderedmap.NewOrderedMap[string, *Include](),
|
||||
}
|
||||
for _, el := range els {
|
||||
includes.Set(el.Key, el.Value)
|
||||
}
|
||||
return includes
|
||||
}
|
||||
|
||||
// Includes represents information about included tasksfiles
|
||||
type Includes struct {
|
||||
omap.OrderedMap[string, *Include]
|
||||
// Len returns the number of includes in the Includes map.
|
||||
func (includes *Includes) Len() int {
|
||||
if includes == nil || includes.om == nil {
|
||||
return 0
|
||||
}
|
||||
defer includes.mutex.RUnlock()
|
||||
includes.mutex.RLock()
|
||||
return includes.om.Len()
|
||||
}
|
||||
|
||||
// Get returns the value the the include with the provided key and a boolean
|
||||
// that indicates if the value was found or not. If the value is not found, the
|
||||
// returned include is a zero value and the bool is false.
|
||||
func (includes *Includes) Get(key string) (*Include, bool) {
|
||||
if includes == nil || includes.om == nil {
|
||||
return &Include{}, false
|
||||
}
|
||||
defer includes.mutex.RUnlock()
|
||||
includes.mutex.RLock()
|
||||
return includes.om.Get(key)
|
||||
}
|
||||
|
||||
// Set sets the value of the include with the provided key to the provided
|
||||
// value. If the include already exists, its value is updated. If the include
|
||||
// does not exist, it is created.
|
||||
func (includes *Includes) Set(key string, value *Include) bool {
|
||||
if includes == nil {
|
||||
includes = NewIncludes()
|
||||
}
|
||||
if includes.om == nil {
|
||||
includes.om = orderedmap.NewOrderedMap[string, *Include]()
|
||||
}
|
||||
defer includes.mutex.Unlock()
|
||||
includes.mutex.Lock()
|
||||
return includes.om.Set(key, value)
|
||||
}
|
||||
|
||||
// Range calls the provided function for each include in the map. The function
|
||||
// receives the include's key and value as arguments. If the function returns
|
||||
// an error, the iteration stops and the error is returned.
|
||||
func (includes *Includes) Range(f func(k string, v *Include) error) error {
|
||||
if includes == nil || includes.om == nil {
|
||||
return nil
|
||||
}
|
||||
for pair := includes.om.Front(); pair != nil; pair = pair.Next() {
|
||||
if err := f(pair.Key, pair.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
|
||||
if includes == nil || includes.om == nil {
|
||||
*includes = *NewIncludes()
|
||||
}
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
// NOTE(@andreynering): on this style of custom unmarshalling,
|
||||
// even number contains the keys, while odd numbers contains
|
||||
// the values.
|
||||
// NOTE: orderedmap does not have an unmarshaler, so we have to decode
|
||||
// the map manually. We increment over 2 values at a time and assign
|
||||
// them as a key-value pair.
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valueNode := node.Content[i+1]
|
||||
|
||||
// Decode the value node into an Include struct
|
||||
var v Include
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
|
||||
// Set the include namespace
|
||||
v.Namespace = keyNode.Value
|
||||
|
||||
// Add the include to the ordered map
|
||||
includes.Set(keyNode.Value, &v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfiles", node.Line, node.ShortTag())
|
||||
}
|
||||
|
||||
// Len returns the length of the map
|
||||
func (includes *Includes) Len() int {
|
||||
if includes == nil {
|
||||
return 0
|
||||
}
|
||||
return includes.OrderedMap.Len()
|
||||
}
|
||||
|
||||
// Wrapper around OrderedMap.Set to ensure we don't get nil pointer errors
|
||||
func (includes *Includes) Range(f func(k string, v *Include) error) error {
|
||||
if includes == nil {
|
||||
return nil
|
||||
}
|
||||
return includes.OrderedMap.Range(f)
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("includes")
|
||||
}
|
||||
|
||||
func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
||||
@@ -71,7 +137,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var str string
|
||||
if err := node.Decode(&str); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
include.Taskfile = str
|
||||
return nil
|
||||
@@ -82,23 +148,27 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Flatten bool
|
||||
Aliases []string
|
||||
Excludes []string
|
||||
Vars *Vars
|
||||
}
|
||||
if err := node.Decode(&includedTaskfile); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
include.Taskfile = includedTaskfile.Taskfile
|
||||
include.Dir = includedTaskfile.Dir
|
||||
include.Optional = includedTaskfile.Optional
|
||||
include.Internal = includedTaskfile.Internal
|
||||
include.Aliases = includedTaskfile.Aliases
|
||||
include.Excludes = includedTaskfile.Excludes
|
||||
include.AdvancedImport = true
|
||||
include.Vars = includedTaskfile.Vars
|
||||
include.Flatten = includedTaskfile.Flatten
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfile", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("include")
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of IncludedTaskfile and copies
|
||||
@@ -113,7 +183,9 @@ func (include *Include) DeepCopy() *Include {
|
||||
Dir: include.Dir,
|
||||
Optional: include.Optional,
|
||||
Internal: include.Internal,
|
||||
Excludes: deepcopy.Slice(include.Excludes),
|
||||
AdvancedImport: include.AdvancedImport,
|
||||
Vars: include.Vars.DeepCopy(),
|
||||
Flatten: include.Flatten,
|
||||
}
|
||||
}
|
||||
|
||||
95
taskfile/ast/matrix.go
Normal file
95
taskfile/ast/matrix.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"github.com/elliotchance/orderedmap/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
)
|
||||
|
||||
type Matrix struct {
|
||||
om *orderedmap.OrderedMap[string, []any]
|
||||
}
|
||||
|
||||
type MatrixElement orderedmap.Element[string, []any]
|
||||
|
||||
func NewMatrix(els ...*MatrixElement) *Matrix {
|
||||
matrix := &Matrix{
|
||||
om: orderedmap.NewOrderedMap[string, []any](),
|
||||
}
|
||||
for _, el := range els {
|
||||
matrix.Set(el.Key, el.Value)
|
||||
}
|
||||
return matrix
|
||||
}
|
||||
|
||||
func (matrix *Matrix) Len() int {
|
||||
if matrix == nil || matrix.om == nil {
|
||||
return 0
|
||||
}
|
||||
return matrix.om.Len()
|
||||
}
|
||||
|
||||
func (matrix *Matrix) Get(key string) ([]any, bool) {
|
||||
if matrix == nil || matrix.om == nil {
|
||||
return nil, false
|
||||
}
|
||||
return matrix.om.Get(key)
|
||||
}
|
||||
|
||||
func (matrix *Matrix) Set(key string, value []any) bool {
|
||||
if matrix == nil {
|
||||
matrix = NewMatrix()
|
||||
}
|
||||
if matrix.om == nil {
|
||||
matrix.om = orderedmap.NewOrderedMap[string, []any]()
|
||||
}
|
||||
return matrix.om.Set(key, value)
|
||||
}
|
||||
|
||||
func (matrix *Matrix) Range(f func(k string, v []any) error) error {
|
||||
if matrix == nil || matrix.om == nil {
|
||||
return nil
|
||||
}
|
||||
for pair := matrix.om.Front(); pair != nil; pair = pair.Next() {
|
||||
if err := f(pair.Key, pair.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (matrix *Matrix) DeepCopy() *Matrix {
|
||||
if matrix == nil {
|
||||
return nil
|
||||
}
|
||||
return &Matrix{
|
||||
om: deepcopy.OrderedMap(matrix.om),
|
||||
}
|
||||
}
|
||||
|
||||
func (matrix *Matrix) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
// NOTE: orderedmap does not have an unmarshaler, so we have to decode
|
||||
// the map manually. We increment over 2 values at a time and assign
|
||||
// them as a key-value pair.
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valueNode := node.Content[i+1]
|
||||
|
||||
// Decode the value node into a Matrix struct
|
||||
var v []any
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
|
||||
// Add the task to the ordered map
|
||||
matrix.Set(keyNode.Value, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("matrix")
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
// Output of the Task output
|
||||
@@ -25,7 +25,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var name string
|
||||
if err := node.Decode(&name); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
s.Name = name
|
||||
return nil
|
||||
@@ -35,10 +35,10 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
|
||||
Group *OutputGroup
|
||||
}
|
||||
if err := node.Decode(&tmp); err != nil {
|
||||
return fmt.Errorf("task: output style must be a string or mapping with a \"group\" key: %w", err)
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
if tmp.Group == nil {
|
||||
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`output style must have the "group" key when in mapping form`)
|
||||
}
|
||||
*s = Output{
|
||||
Name: "group",
|
||||
@@ -47,7 +47,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into output", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("output")
|
||||
}
|
||||
|
||||
// OutputGroup is the style options specific to the Group style.
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/goext"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ type ErrInvalidPlatform struct {
|
||||
}
|
||||
|
||||
func (err *ErrInvalidPlatform) Error() string {
|
||||
return fmt.Sprintf(`task: Invalid platform "%s"`, err.Platform)
|
||||
return fmt.Sprintf(`invalid platform "%s"`, err.Platform)
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
@@ -39,14 +40,14 @@ func (p *Platform) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var platform string
|
||||
if err := node.Decode(&platform); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
if err := p.parsePlatform(platform); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into platform", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("platform")
|
||||
}
|
||||
|
||||
// parsePlatform takes a string representing an OS/Arch combination (or either on their own)
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestPlatformParsing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
Input string
|
||||
ExpectedOS string
|
||||
@@ -26,14 +28,16 @@ func TestPlatformParsing(t *testing.T) {
|
||||
{Input: "windows/amd64", ExpectedOS: "windows", ExpectedArch: "amd64"},
|
||||
{Input: "windows/arm64", ExpectedOS: "windows", ExpectedArch: "arm64"},
|
||||
|
||||
{Input: "invalid", Error: `task: Invalid platform "invalid"`},
|
||||
{Input: "invalid/invalid", Error: `task: Invalid platform "invalid/invalid"`},
|
||||
{Input: "windows/invalid", Error: `task: Invalid platform "windows/invalid"`},
|
||||
{Input: "invalid/amd64", Error: `task: Invalid platform "invalid/amd64"`},
|
||||
{Input: "invalid", Error: `invalid platform "invalid"`},
|
||||
{Input: "invalid/invalid", Error: `invalid platform "invalid/invalid"`},
|
||||
{Input: "windows/invalid", Error: `invalid platform "windows/invalid"`},
|
||||
{Input: "invalid/amd64", Error: `invalid platform "invalid/amd64"`},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Input, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var p Platform
|
||||
err := p.parsePlatform(test.Input)
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// ErrCantUnmarshalPrecondition is returned for invalid precond YAML.
|
||||
var ErrCantUnmarshalPrecondition = errors.New("task: Can't unmarshal precondition value")
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
// Precondition represents a precondition necessary for a task to run
|
||||
type Precondition struct {
|
||||
@@ -33,7 +31,7 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var cmd string
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
p.Sh = cmd
|
||||
p.Msg = fmt.Sprintf("`%s` failed", cmd)
|
||||
@@ -45,7 +43,7 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
|
||||
Msg string
|
||||
}
|
||||
if err := node.Decode(&sh); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
p.Sh = sh.Sh
|
||||
p.Msg = sh.Msg
|
||||
@@ -55,5 +53,5 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into precondition", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("precondition")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func TestPreconditionParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
content string
|
||||
v any
|
||||
|
||||
29
taskfile/ast/prompt.go
Normal file
29
taskfile/ast/prompt.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
type Prompt []string
|
||||
|
||||
func (p *Prompt) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
case yaml.ScalarNode:
|
||||
var str string
|
||||
if err := node.Decode(&str); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
*p = []string{str}
|
||||
return nil
|
||||
case yaml.SequenceNode:
|
||||
var list []string
|
||||
if err := node.Decode(&list); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
*p = list
|
||||
return nil
|
||||
}
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("prompt")
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
package ast
|
||||
|
||||
import "github.com/go-task/task/v3/internal/deepcopy"
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
)
|
||||
|
||||
// Requires represents a set of required variables necessary for a task to run
|
||||
type Requires struct {
|
||||
Vars []string
|
||||
Vars []*VarsWithValidation
|
||||
}
|
||||
|
||||
func (r *Requires) DeepCopy() *Requires {
|
||||
@@ -16,3 +21,47 @@ func (r *Requires) DeepCopy() *Requires {
|
||||
Vars: deepcopy.Slice(r.Vars),
|
||||
}
|
||||
}
|
||||
|
||||
type VarsWithValidation struct {
|
||||
Name string
|
||||
Enum []string
|
||||
}
|
||||
|
||||
func (v *VarsWithValidation) DeepCopy() *VarsWithValidation {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return &VarsWithValidation{
|
||||
Name: v.Name,
|
||||
Enum: v.Enum,
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
||||
func (v *VarsWithValidation) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.ScalarNode:
|
||||
var cmd string
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Name = cmd
|
||||
v.Enum = nil
|
||||
return nil
|
||||
|
||||
case yaml.MappingNode:
|
||||
var vv struct {
|
||||
Name string
|
||||
Enum []string
|
||||
}
|
||||
if err := node.Decode(&vv); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Name = vv.Name
|
||||
v.Enum = vv.Enum
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("requires")
|
||||
}
|
||||
|
||||
@@ -7,42 +7,45 @@ import (
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
)
|
||||
|
||||
// Task represents a task
|
||||
type Task struct {
|
||||
Task string
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Label string
|
||||
Desc string
|
||||
Prompt string
|
||||
Summary string
|
||||
Requires *Requires
|
||||
Aliases []string
|
||||
Sources []*Glob
|
||||
Generates []*Glob
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool
|
||||
Run string
|
||||
Task string
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Label string
|
||||
Desc string
|
||||
Prompt Prompt
|
||||
Summary string
|
||||
Requires *Requires
|
||||
Aliases []string
|
||||
Sources []*Glob
|
||||
Generates []*Glob
|
||||
Status []string
|
||||
Preconditions []*Precondition
|
||||
Dir string
|
||||
Set []string
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
Watch bool
|
||||
Location *Location
|
||||
// Populated during merging
|
||||
Namespace string
|
||||
IncludeVars *Vars
|
||||
IncludedTaskfileVars *Vars
|
||||
Platforms []*Platform
|
||||
Location *Location
|
||||
Watch bool
|
||||
}
|
||||
|
||||
func (t *Task) Name() string {
|
||||
@@ -52,6 +55,13 @@ func (t *Task) Name() string {
|
||||
return t.Task
|
||||
}
|
||||
|
||||
func (t *Task) LocalName() string {
|
||||
name := t.Task
|
||||
name = strings.TrimPrefix(name, t.Namespace)
|
||||
name = strings.TrimPrefix(name, ":")
|
||||
return name
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -83,7 +93,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.ScalarNode:
|
||||
var cmd Cmd
|
||||
if err := node.Decode(&cmd); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
t.Cmds = append(t.Cmds, &cmd)
|
||||
return nil
|
||||
@@ -92,7 +102,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.SequenceNode:
|
||||
var cmds []*Cmd
|
||||
if err := node.Decode(&cmds); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
t.Cmds = cmds
|
||||
return nil
|
||||
@@ -105,7 +115,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
Deps []*Dep
|
||||
Label string
|
||||
Desc string
|
||||
Prompt string
|
||||
Prompt Prompt
|
||||
Summary string
|
||||
Aliases []string
|
||||
Sources []*Glob
|
||||
@@ -130,11 +140,11 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
Watch bool
|
||||
}
|
||||
if err := node.Decode(&task); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
if task.Cmd != nil {
|
||||
if task.Cmds != nil {
|
||||
return fmt.Errorf("yaml: line %d: task cannot have both cmd and cmds", node.Line)
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage("task cannot have both cmd and cmds")
|
||||
}
|
||||
t.Cmds = []*Cmd{task.Cmd}
|
||||
} else {
|
||||
@@ -169,7 +179,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("task")
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of Task and copies
|
||||
@@ -209,6 +219,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
Platforms: deepcopy.Slice(t.Platforms),
|
||||
Location: t.Location.DeepCopy(),
|
||||
Requires: t.Requires.DeepCopy(),
|
||||
Namespace: t.Namespace,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
// NamespaceSeparator contains the character that separates namespaces
|
||||
@@ -28,7 +29,7 @@ type Taskfile struct {
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks Tasks
|
||||
Tasks *Tasks
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
@@ -46,16 +47,21 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
||||
if t2.Output.IsSet() {
|
||||
t1.Output = t2.Output
|
||||
}
|
||||
if t1.Includes == nil {
|
||||
t1.Includes = NewIncludes()
|
||||
}
|
||||
if t1.Vars == nil {
|
||||
t1.Vars = &Vars{}
|
||||
t1.Vars = NewVars()
|
||||
}
|
||||
if t1.Env == nil {
|
||||
t1.Env = &Vars{}
|
||||
t1.Env = NewVars()
|
||||
}
|
||||
if t1.Tasks == nil {
|
||||
t1.Tasks = NewTasks()
|
||||
}
|
||||
t1.Vars.Merge(t2.Vars, include)
|
||||
t1.Env.Merge(t2.Env, include)
|
||||
t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
|
||||
return nil
|
||||
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
|
||||
}
|
||||
|
||||
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
@@ -70,14 +76,14 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
Shopt []string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Tasks Tasks
|
||||
Tasks *Tasks
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval time.Duration
|
||||
}
|
||||
if err := node.Decode(&taskfile); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
tf.Version = taskfile.Version
|
||||
tf.Output = taskfile.Output
|
||||
@@ -92,14 +98,20 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||
tf.Dotenv = taskfile.Dotenv
|
||||
tf.Run = taskfile.Run
|
||||
tf.Interval = taskfile.Interval
|
||||
if tf.Includes == nil {
|
||||
tf.Includes = NewIncludes()
|
||||
}
|
||||
if tf.Vars == nil {
|
||||
tf.Vars = &Vars{}
|
||||
tf.Vars = NewVars()
|
||||
}
|
||||
if tf.Env == nil {
|
||||
tf.Env = &Vars{}
|
||||
tf.Env = NewVars()
|
||||
}
|
||||
if tf.Tasks == nil {
|
||||
tf.Tasks = NewTasks()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into taskfile", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("taskfile")
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/internal/omap"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func TestCmdParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
yamlCmd = `echo "a string command"`
|
||||
yamlDep = `"task-name"`
|
||||
@@ -38,15 +39,21 @@ vars:
|
||||
yamlTaskCall,
|
||||
&ast.Cmd{},
|
||||
&ast.Cmd{
|
||||
Task: "another-task", Vars: &ast.Vars{
|
||||
OrderedMap: omap.FromMapWithOrder(
|
||||
map[string]ast.Var{
|
||||
"PARAM1": {Value: "VALUE1"},
|
||||
"PARAM2": {Value: "VALUE2"},
|
||||
Task: "another-task",
|
||||
Vars: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "PARAM1",
|
||||
Value: ast.Var{
|
||||
Value: "VALUE1",
|
||||
},
|
||||
[]string{"PARAM1", "PARAM2"},
|
||||
),
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "PARAM2",
|
||||
Value: ast.Var{
|
||||
Value: "VALUE2",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -58,14 +65,15 @@ vars:
|
||||
yamlDeferredCall,
|
||||
&ast.Cmd{},
|
||||
&ast.Cmd{
|
||||
Task: "some_task", Vars: &ast.Vars{
|
||||
OrderedMap: omap.FromMapWithOrder(
|
||||
map[string]ast.Var{
|
||||
"PARAM1": {Value: "var"},
|
||||
Task: "some_task",
|
||||
Vars: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "PARAM1",
|
||||
Value: ast.Var{
|
||||
Value: "var",
|
||||
},
|
||||
[]string{"PARAM1"},
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
Defer: true,
|
||||
},
|
||||
},
|
||||
@@ -78,15 +86,21 @@ vars:
|
||||
yamlTaskCall,
|
||||
&ast.Dep{},
|
||||
&ast.Dep{
|
||||
Task: "another-task", Vars: &ast.Vars{
|
||||
OrderedMap: omap.FromMapWithOrder(
|
||||
map[string]ast.Var{
|
||||
"PARAM1": {Value: "VALUE1"},
|
||||
"PARAM2": {Value: "VALUE2"},
|
||||
Task: "another-task",
|
||||
Vars: ast.NewVars(
|
||||
&ast.VarElement{
|
||||
Key: "PARAM1",
|
||||
Value: ast.Var{
|
||||
Value: "VALUE1",
|
||||
},
|
||||
[]string{"PARAM1", "PARAM2"},
|
||||
),
|
||||
},
|
||||
},
|
||||
&ast.VarElement{
|
||||
Key: "PARAM2",
|
||||
Value: ast.Var{
|
||||
Value: "VALUE2",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,32 +2,137 @@ package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/omap"
|
||||
)
|
||||
|
||||
// Tasks represents a group of tasks
|
||||
type Tasks struct {
|
||||
omap.OrderedMap[string, *Task]
|
||||
type (
|
||||
// Tasks is an ordered map of task names to Tasks.
|
||||
Tasks struct {
|
||||
om *orderedmap.OrderedMap[string, *Task]
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
// A TaskElement is a key-value pair that is used for initializing a Tasks
|
||||
// structure.
|
||||
TaskElement orderedmap.Element[string, *Task]
|
||||
// MatchingTask represents a task that matches a given call. It includes the
|
||||
// task itself and a list of wildcards that were matched.
|
||||
MatchingTask struct {
|
||||
Task *Task
|
||||
Wildcards []string
|
||||
}
|
||||
)
|
||||
|
||||
// NewTasks creates a new instance of Tasks and initializes it with the provided
|
||||
// set of elements, if any. The elements are added in the order they are passed.
|
||||
func NewTasks(els ...*TaskElement) *Tasks {
|
||||
tasks := &Tasks{
|
||||
om: orderedmap.NewOrderedMap[string, *Task](),
|
||||
}
|
||||
for _, el := range els {
|
||||
tasks.Set(el.Key, el.Value)
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
type MatchingTask struct {
|
||||
Task *Task
|
||||
Wildcards []string
|
||||
// Len returns the number of variables in the Tasks map.
|
||||
func (tasks *Tasks) Len() int {
|
||||
if tasks == nil || tasks.om == nil {
|
||||
return 0
|
||||
}
|
||||
defer tasks.mutex.RUnlock()
|
||||
tasks.mutex.RLock()
|
||||
return tasks.om.Len()
|
||||
}
|
||||
|
||||
// Get returns the value the the task with the provided key and a boolean that
|
||||
// indicates if the value was found or not. If the value is not found, the
|
||||
// returned task is a zero value and the bool is false.
|
||||
func (tasks *Tasks) Get(key string) (*Task, bool) {
|
||||
if tasks == nil || tasks.om == nil {
|
||||
return &Task{}, false
|
||||
}
|
||||
defer tasks.mutex.RUnlock()
|
||||
tasks.mutex.RLock()
|
||||
return tasks.om.Get(key)
|
||||
}
|
||||
|
||||
// Set sets the value of the task with the provided key to the provided value.
|
||||
// If the task already exists, its value is updated. If the task does not exist,
|
||||
// it is created.
|
||||
func (tasks *Tasks) Set(key string, value *Task) bool {
|
||||
if tasks == nil {
|
||||
tasks = NewTasks()
|
||||
}
|
||||
if tasks.om == nil {
|
||||
tasks.om = orderedmap.NewOrderedMap[string, *Task]()
|
||||
}
|
||||
defer tasks.mutex.Unlock()
|
||||
tasks.mutex.Lock()
|
||||
return tasks.om.Set(key, value)
|
||||
}
|
||||
|
||||
// Range calls the provided function for each task in the map. The function
|
||||
// receives the task's key and value as arguments. If the function returns an
|
||||
// error, the iteration stops and the error is returned.
|
||||
func (tasks *Tasks) Range(f func(k string, v *Task) error) error {
|
||||
if tasks == nil || tasks.om == nil {
|
||||
return nil
|
||||
}
|
||||
for pair := tasks.om.Front(); pair != nil; pair = pair.Next() {
|
||||
if err := f(pair.Key, pair.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Keys returns a slice of all the keys in the Tasks map.
|
||||
func (tasks *Tasks) Keys() []string {
|
||||
if tasks == nil {
|
||||
return nil
|
||||
}
|
||||
defer tasks.mutex.RUnlock()
|
||||
tasks.mutex.RLock()
|
||||
var keys []string
|
||||
for pair := tasks.om.Front(); pair != nil; pair = pair.Next() {
|
||||
keys = append(keys, pair.Key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns a slice of all the values in the Tasks map.
|
||||
func (tasks *Tasks) Values() []*Task {
|
||||
if tasks == nil {
|
||||
return nil
|
||||
}
|
||||
defer tasks.mutex.RUnlock()
|
||||
tasks.mutex.RLock()
|
||||
var values []*Task
|
||||
for pair := tasks.om.Front(); pair != nil; pair = pair.Next() {
|
||||
values = append(values, pair.Value)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// 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
|
||||
// a wildcard pattern. The function returns a list of MatchingTask structs, each
|
||||
// containing a task and a list of wildcards that were matched.
|
||||
func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
if call == nil {
|
||||
return nil
|
||||
}
|
||||
var task *Task
|
||||
var matchingTasks []*MatchingTask
|
||||
// If there is a direct match, return it
|
||||
if task = t.OrderedMap.Get(call.Task); task != nil {
|
||||
if task, ok := t.Get(call.Task); ok {
|
||||
matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil})
|
||||
return matchingTasks
|
||||
}
|
||||
@@ -45,112 +150,131 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
|
||||
return matchingTasks
|
||||
}
|
||||
|
||||
func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) {
|
||||
_ = t2.Range(func(k string, v *Task) error {
|
||||
func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars) error {
|
||||
defer t2.mutex.RUnlock()
|
||||
t2.mutex.RLock()
|
||||
err := t2.Range(func(name string, v *Task) error {
|
||||
// We do a deep copy of the task struct here to ensure that no data can
|
||||
// be changed elsewhere once the taskfile is merged.
|
||||
task := v.DeepCopy()
|
||||
|
||||
// Set the task to internal if EITHER the included task or the included
|
||||
// taskfile are marked as internal
|
||||
task.Internal = task.Internal || (include != nil && include.Internal)
|
||||
taskName := name
|
||||
|
||||
// Add namespaces to task dependencies
|
||||
for _, dep := range task.Deps {
|
||||
if dep != nil && dep.Task != "" {
|
||||
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
|
||||
}
|
||||
// if the task is in the exclude list, don't add it to the merged taskfile and early return
|
||||
if slices.Contains(include.Excludes, name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add namespaces to task commands
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd != nil && cmd.Task != "" {
|
||||
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
// Add namespaces to task aliases
|
||||
for i, alias := range task.Aliases {
|
||||
task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
|
||||
}
|
||||
|
||||
// Add namespace aliases
|
||||
if include != nil {
|
||||
for _, namespaceAlias := range include.Aliases {
|
||||
task.Aliases = append(task.Aliases, taskNameWithNamespace(task.Task, namespaceAlias))
|
||||
for _, alias := range v.Aliases {
|
||||
task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias))
|
||||
if !include.Flatten {
|
||||
// Add namespaces to task dependencies
|
||||
for _, dep := range task.Deps {
|
||||
if dep != nil && dep.Task != "" {
|
||||
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
// Add namespaces to task commands
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd != nil && cmd.Task != "" {
|
||||
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
// Add namespaces to task aliases
|
||||
for i, alias := range task.Aliases {
|
||||
task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
|
||||
}
|
||||
|
||||
// Add namespace aliases
|
||||
if include != nil {
|
||||
for _, namespaceAlias := range include.Aliases {
|
||||
task.Aliases = append(task.Aliases, taskNameWithNamespace(task.Task, namespaceAlias))
|
||||
for _, alias := range v.Aliases {
|
||||
task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
taskName = taskNameWithNamespace(name, include.Namespace)
|
||||
task.Namespace = include.Namespace
|
||||
task.Task = taskName
|
||||
}
|
||||
|
||||
if include.AdvancedImport {
|
||||
task.Dir = filepathext.SmartJoin(include.Dir, task.Dir)
|
||||
if task.IncludeVars == nil {
|
||||
task.IncludeVars = &Vars{}
|
||||
task.IncludeVars = NewVars()
|
||||
}
|
||||
task.IncludeVars.Merge(include.Vars, nil)
|
||||
task.IncludedTaskfileVars = includedTaskfileVars
|
||||
task.IncludedTaskfileVars = includedTaskfileVars.DeepCopy()
|
||||
}
|
||||
|
||||
if _, ok := t1.Get(taskName); ok {
|
||||
return &errors.TaskNameFlattenConflictError{
|
||||
TaskName: taskName,
|
||||
Include: include.Namespace,
|
||||
}
|
||||
}
|
||||
// Add the task to the merged taskfile
|
||||
taskNameWithNamespace := taskNameWithNamespace(k, include.Namespace)
|
||||
task.Task = taskNameWithNamespace
|
||||
t1.Set(taskNameWithNamespace, task)
|
||||
t1.Set(taskName, task)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// If the included Taskfile has a default task and the parent namespace has
|
||||
// no task with a matching name, we can add an alias so that the user can
|
||||
// run the included Taskfile's default task without specifying its full
|
||||
// name. If the parent namespace has aliases, we add another alias for each
|
||||
// of them.
|
||||
if t2.Get("default") != nil && t1.Get(include.Namespace) == nil {
|
||||
// If the included Taskfile has a default task, is not flattened and the
|
||||
// parent namespace has no task with a matching name, we can add an alias so
|
||||
// that the user can run the included Taskfile's default task without
|
||||
// specifying its full name. If the parent namespace has aliases, we add
|
||||
// another alias for each of them.
|
||||
_, t2DefaultExists := t2.Get("default")
|
||||
_, t1NamespaceExists := t1.Get(include.Namespace)
|
||||
if t2DefaultExists && !t1NamespaceExists && !include.Flatten {
|
||||
defaultTaskName := fmt.Sprintf("%s:default", include.Namespace)
|
||||
t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Namespace)
|
||||
t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Aliases...)
|
||||
t1DefaultTask, ok := t1.Get(defaultTaskName)
|
||||
if ok {
|
||||
t1DefaultTask.Aliases = append(t1DefaultTask.Aliases, include.Namespace)
|
||||
t1DefaultTask.Aliases = slices.Concat(t1DefaultTask.Aliases, include.Aliases)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
|
||||
if t == nil || t.om == nil {
|
||||
*t = *NewTasks()
|
||||
}
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
tasks := omap.New[string, *Task]()
|
||||
if err := node.Decode(&tasks); err != nil {
|
||||
return err
|
||||
}
|
||||
// NOTE: orderedmap does not have an unmarshaler, so we have to decode
|
||||
// the map manually. We increment over 2 values at a time and assign
|
||||
// them as a key-value pair.
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valueNode := node.Content[i+1]
|
||||
|
||||
// nolint: errcheck
|
||||
tasks.Range(func(name string, task *Task) error {
|
||||
// Set the task's name
|
||||
if task == nil {
|
||||
task = &Task{
|
||||
Task: name,
|
||||
}
|
||||
// Decode the value node into a Task struct
|
||||
var v Task
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
task.Task = name
|
||||
|
||||
// Set the task's location
|
||||
for _, keys := range node.Content {
|
||||
if keys.Value == name {
|
||||
task.Location = &Location{
|
||||
Line: keys.Line,
|
||||
Column: keys.Column,
|
||||
}
|
||||
}
|
||||
// Set the task name and location
|
||||
v.Task = keyNode.Value
|
||||
v.Location = &Location{
|
||||
Line: keyNode.Line,
|
||||
Column: keyNode.Column,
|
||||
}
|
||||
tasks.Set(name, task)
|
||||
return nil
|
||||
})
|
||||
|
||||
*t = Tasks{
|
||||
OrderedMap: tasks,
|
||||
// Add the task to the ordered map
|
||||
t.Set(keyNode.Value, &v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into tasks", node.Line, node.ShortTag())
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("tasks")
|
||||
}
|
||||
|
||||
func taskNameWithNamespace(taskName string, namespace string) string {
|
||||
|
||||
@@ -1,90 +1,176 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/internal/omap"
|
||||
)
|
||||
|
||||
// Vars is a string[string] variables map.
|
||||
type Vars struct {
|
||||
omap.OrderedMap[string, Var]
|
||||
type (
|
||||
// Vars is an ordered map of variable names to values.
|
||||
Vars struct {
|
||||
om *orderedmap.OrderedMap[string, Var]
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
// A VarElement is a key-value pair that is used for initializing a Vars
|
||||
// structure.
|
||||
VarElement orderedmap.Element[string, Var]
|
||||
)
|
||||
|
||||
// NewVars creates a new instance of Vars and initializes it with the provided
|
||||
// set of elements, if any. The elements are added in the order they are passed.
|
||||
func NewVars(els ...*VarElement) *Vars {
|
||||
vars := &Vars{
|
||||
om: orderedmap.NewOrderedMap[string, Var](),
|
||||
}
|
||||
for _, el := range els {
|
||||
vars.Set(el.Key, el.Value)
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
// ToCacheMap converts Vars to a map containing only the static
|
||||
// Len returns the number of variables in the Vars map.
|
||||
func (vars *Vars) Len() int {
|
||||
if vars == nil || vars.om == nil {
|
||||
return 0
|
||||
}
|
||||
defer vars.mutex.RUnlock()
|
||||
vars.mutex.RLock()
|
||||
return vars.om.Len()
|
||||
}
|
||||
|
||||
// Get returns the value the the variable with the provided key and a boolean
|
||||
// that indicates if the value was found or not. If the value is not found, the
|
||||
// returned variable is a zero value and the bool is false.
|
||||
func (vars *Vars) Get(key string) (Var, bool) {
|
||||
if vars == nil || vars.om == nil {
|
||||
return Var{}, false
|
||||
}
|
||||
defer vars.mutex.RUnlock()
|
||||
vars.mutex.RLock()
|
||||
return vars.om.Get(key)
|
||||
}
|
||||
|
||||
// Set sets the value of the variable with the provided key to the provided
|
||||
// value. If the variable already exists, its value is updated. If the variable
|
||||
// does not exist, it is created.
|
||||
func (vars *Vars) Set(key string, value Var) bool {
|
||||
if vars == nil {
|
||||
vars = NewVars()
|
||||
}
|
||||
if vars.om == nil {
|
||||
vars.om = orderedmap.NewOrderedMap[string, Var]()
|
||||
}
|
||||
defer vars.mutex.Unlock()
|
||||
vars.mutex.Lock()
|
||||
return vars.om.Set(key, value)
|
||||
}
|
||||
|
||||
// Range calls the provided function for each variable in the map. The function
|
||||
// receives the variable's key and value as arguments. If the function returns
|
||||
// an error, the iteration stops and the error is returned.
|
||||
func (vars *Vars) Range(f func(k string, v Var) error) error {
|
||||
if vars == nil || vars.om == nil {
|
||||
return nil
|
||||
}
|
||||
for pair := vars.om.Front(); pair != nil; pair = pair.Next() {
|
||||
if err := f(pair.Key, pair.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToCacheMap converts Vars to an unordered map containing only the static
|
||||
// variables
|
||||
func (vs *Vars) ToCacheMap() (m map[string]any) {
|
||||
m = make(map[string]any, vs.Len())
|
||||
_ = vs.Range(func(k string, v Var) error {
|
||||
if v.Sh != "" {
|
||||
func (vars *Vars) ToCacheMap() (m map[string]any) {
|
||||
defer vars.mutex.RUnlock()
|
||||
vars.mutex.RLock()
|
||||
m = make(map[string]any, vars.Len())
|
||||
for pair := vars.om.Front(); pair != nil; pair = pair.Next() {
|
||||
if pair.Value.Sh != nil && *pair.Value.Sh != "" {
|
||||
// Dynamic variable is not yet resolved; trigger
|
||||
// <no value> to be used in templates.
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.Live != nil {
|
||||
m[k] = v.Live
|
||||
if pair.Value.Live != nil {
|
||||
m[pair.Key] = pair.Value.Live
|
||||
} else {
|
||||
m[k] = v.Value
|
||||
m[pair.Key] = pair.Value.Value
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Wrapper around OrderedMap.Set to ensure we don't get nil pointer errors
|
||||
func (vs *Vars) Range(f func(k string, v Var) error) error {
|
||||
if vs == nil {
|
||||
return nil
|
||||
}
|
||||
return vs.OrderedMap.Range(f)
|
||||
}
|
||||
|
||||
// Wrapper around OrderedMap.Merge to ensure we don't get nil pointer errors
|
||||
func (vs *Vars) Merge(other *Vars, include *Include) {
|
||||
if vs == nil || other == nil {
|
||||
// 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.
|
||||
func (vars *Vars) Merge(other *Vars, include *Include) {
|
||||
if vars == nil || vars.om == nil || other == nil {
|
||||
return
|
||||
}
|
||||
_ = other.Range(func(key string, value Var) error {
|
||||
defer other.mutex.RUnlock()
|
||||
other.mutex.RLock()
|
||||
for pair := other.om.Front(); pair != nil; pair = pair.Next() {
|
||||
if include != nil && include.AdvancedImport {
|
||||
value.Dir = include.Dir
|
||||
pair.Value.Dir = include.Dir
|
||||
}
|
||||
vs.Set(key, value)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Wrapper around OrderedMap.Len to ensure we don't get nil pointer errors
|
||||
func (vs *Vars) Len() int {
|
||||
if vs == nil {
|
||||
return 0
|
||||
vars.om.Set(pair.Key, pair.Value)
|
||||
}
|
||||
return vs.OrderedMap.Len()
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of Vars and copies
|
||||
// data by value from the source struct.
|
||||
func (vs *Vars) DeepCopy() *Vars {
|
||||
if vs == nil {
|
||||
return nil
|
||||
}
|
||||
defer vs.mutex.RUnlock()
|
||||
vs.mutex.RLock()
|
||||
return &Vars{
|
||||
OrderedMap: vs.OrderedMap.DeepCopy(),
|
||||
om: deepcopy.OrderedMap(vs.om),
|
||||
}
|
||||
}
|
||||
|
||||
func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
|
||||
if vs == nil || vs.om == nil {
|
||||
*vs = *NewVars()
|
||||
}
|
||||
vs.om = orderedmap.NewOrderedMap[string, Var]()
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
// NOTE: orderedmap does not have an unmarshaler, so we have to decode
|
||||
// the map manually. We increment over 2 values at a time and assign
|
||||
// them as a key-value pair.
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
keyNode := node.Content[i]
|
||||
valueNode := node.Content[i+1]
|
||||
|
||||
// Decode the value node into a Task struct
|
||||
var v Var
|
||||
if err := valueNode.Decode(&v); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
|
||||
// Add the task to the ordered map
|
||||
vs.Set(keyNode.Value, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("vars")
|
||||
}
|
||||
|
||||
// Var represents either a static or dynamic variable.
|
||||
type Var struct {
|
||||
Value any
|
||||
Live any
|
||||
Sh string
|
||||
Sh *string
|
||||
Ref string
|
||||
Json string
|
||||
Yaml string
|
||||
Dir string
|
||||
}
|
||||
|
||||
@@ -95,12 +181,16 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
if experiments.MapVariables.Value == "1" {
|
||||
var value any
|
||||
if err := node.Decode(&value); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
// If the value is a string and it starts with $, then it's a shell command
|
||||
if str, ok := value.(string); ok {
|
||||
if str, ok = strings.CutPrefix(str, "$"); ok {
|
||||
v.Sh = str
|
||||
v.Sh = &str
|
||||
return nil
|
||||
}
|
||||
if str, ok = strings.CutPrefix(str, "#"); ok {
|
||||
v.Ref = str
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -114,30 +204,26 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
case yaml.MappingNode:
|
||||
key := node.Content[0].Value
|
||||
switch key {
|
||||
case "sh", "ref", "map", "json", "yaml":
|
||||
case "sh", "ref", "map":
|
||||
var m struct {
|
||||
Sh string
|
||||
Ref string
|
||||
Map any
|
||||
Json string
|
||||
Yaml string
|
||||
Sh *string
|
||||
Ref string
|
||||
Map any
|
||||
}
|
||||
if err := node.Decode(&m); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Sh = m.Sh
|
||||
v.Ref = m.Ref
|
||||
v.Value = m.Map
|
||||
v.Json = m.Json
|
||||
v.Yaml = m.Yaml
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf(`yaml: line %d: %q is not a valid variable type. Try "sh", "ref", "map", "json", "yaml" or using a scalar value`, node.Line, key)
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map" or using a scalar value`, key)
|
||||
}
|
||||
default:
|
||||
var value any
|
||||
if err := node.Decode(&value); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Value = value
|
||||
return nil
|
||||
@@ -148,22 +234,27 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
|
||||
case yaml.MappingNode:
|
||||
if len(node.Content) > 2 || node.Content[0].Value != "sh" {
|
||||
return fmt.Errorf(`task: line %d: maps cannot be assigned to variables`, node.Line)
|
||||
key := node.Content[0].Value
|
||||
switch key {
|
||||
case "sh", "ref":
|
||||
var m struct {
|
||||
Sh *string
|
||||
Ref string
|
||||
}
|
||||
if err := node.Decode(&m); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Sh = m.Sh
|
||||
v.Ref = m.Ref
|
||||
return nil
|
||||
default:
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage("maps cannot be assigned to variables")
|
||||
}
|
||||
var sh struct {
|
||||
Sh string
|
||||
}
|
||||
if err := node.Decode(&sh); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Sh = sh.Sh
|
||||
return nil
|
||||
|
||||
default:
|
||||
var value any
|
||||
if err := node.Decode(&value); err != nil {
|
||||
return err
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Value = value
|
||||
return nil
|
||||
|
||||
@@ -50,9 +50,23 @@ func (c *Cache) key(node Node) string {
|
||||
}
|
||||
|
||||
func (c *Cache) cacheFilePath(node Node) string {
|
||||
return filepath.Join(c.dir, fmt.Sprintf("%s.yaml", c.key(node)))
|
||||
return c.filePath(node, "yaml")
|
||||
}
|
||||
|
||||
func (c *Cache) checksumFilePath(node Node) string {
|
||||
return filepath.Join(c.dir, fmt.Sprintf("%s.checksum", c.key(node)))
|
||||
return c.filePath(node, "checksum")
|
||||
}
|
||||
|
||||
func (c *Cache) filePath(node Node, suffix string) string {
|
||||
lastDir, filename := node.FilenameAndLastDir()
|
||||
prefix := filename
|
||||
// Means it's not "", nor "." nor "/", so it's a valid directory
|
||||
if len(lastDir) > 1 {
|
||||
prefix = fmt.Sprintf("%s-%s", lastDir, filename)
|
||||
}
|
||||
return filepath.Join(c.dir, fmt.Sprintf("%s.%s.%s", prefix, c.key(node), suffix))
|
||||
}
|
||||
|
||||
func (c *Cache) Clear() error {
|
||||
return os.RemoveAll(c.dir)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
@@ -21,7 +22,7 @@ func Dotenv(c *compiler.Compiler, tf *ast.Taskfile, dir string) (*ast.Vars, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
env := &ast.Vars{}
|
||||
env := ast.NewVars()
|
||||
cache := &templater.Cache{Vars: vars}
|
||||
|
||||
for _, dotEnvPath := range tf.Dotenv {
|
||||
@@ -37,10 +38,10 @@ func Dotenv(c *compiler.Compiler, tf *ast.Taskfile, dir string) (*ast.Vars, erro
|
||||
|
||||
envs, err := godotenv.Read(dotEnvPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error reading env file %s: %w", dotEnvPath, err)
|
||||
}
|
||||
for key, value := range envs {
|
||||
if ok := env.Exists(key); !ok {
|
||||
if _, ok := env.Get(key); !ok {
|
||||
env.Set(key, ast.Var{Value: value})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
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/internal/logger"
|
||||
@@ -20,6 +22,7 @@ type Node interface {
|
||||
Remote() bool
|
||||
ResolveEntrypoint(entrypoint string) (string, error)
|
||||
ResolveDir(dir string) (string, error)
|
||||
FilenameAndLastDir() (string, string)
|
||||
}
|
||||
|
||||
func NewRootNode(
|
||||
@@ -47,24 +50,39 @@ func NewNode(
|
||||
) (Node, error) {
|
||||
var node Node
|
||||
var err error
|
||||
switch getScheme(entrypoint) {
|
||||
scheme, err := getScheme(entrypoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch scheme {
|
||||
case "git":
|
||||
node, err = NewGitNode(entrypoint, dir, insecure, opts...)
|
||||
case "http", "https":
|
||||
node, err = NewHTTPNode(l, entrypoint, dir, insecure, timeout, opts...)
|
||||
default:
|
||||
// If no other scheme matches, we assume it's a file
|
||||
node, err = NewFileNode(l, entrypoint, dir, opts...)
|
||||
|
||||
}
|
||||
|
||||
if node.Remote() && !experiments.RemoteTaskfiles.Enabled {
|
||||
return nil, errors.New("task: Remote taskfiles are not enabled. You can read more about this experiment and how to enable it at https://taskfile.dev/experiments/remote-taskfiles")
|
||||
}
|
||||
return node, err
|
||||
}
|
||||
|
||||
func getScheme(uri string) string {
|
||||
if i := strings.Index(uri, "://"); i != -1 {
|
||||
return uri[:i]
|
||||
func getScheme(uri string) (string, error) {
|
||||
u, err := giturls.Parse(uri)
|
||||
if u == nil {
|
||||
return "", err
|
||||
}
|
||||
return ""
|
||||
if strings.HasSuffix(strings.Split(u.Path, "//")[0], ".git") && (u.Scheme == "git" || u.Scheme == "ssh" || u.Scheme == "https" || u.Scheme == "http") {
|
||||
return "git", nil
|
||||
}
|
||||
|
||||
if i := strings.Index(uri, "://"); i != -1 {
|
||||
return uri[:i], nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func getDefaultDir(entrypoint, dir string) string {
|
||||
|
||||
@@ -81,6 +81,9 @@ func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) {
|
||||
if strings.Contains(entrypoint, "://") {
|
||||
return entrypoint, nil
|
||||
}
|
||||
if strings.HasPrefix(entrypoint, "git") {
|
||||
return entrypoint, nil
|
||||
}
|
||||
|
||||
path, err := execext.Expand(entrypoint)
|
||||
if err != nil {
|
||||
@@ -112,3 +115,7 @@ func (node *FileNode) ResolveDir(dir string) (string, error) {
|
||||
entrypointDir := filepath.Dir(node.Entrypoint)
|
||||
return filepathext.SmartJoin(entrypointDir, path), nil
|
||||
}
|
||||
|
||||
func (node *FileNode) FilenameAndLastDir() (string, string) {
|
||||
return "", filepath.Base(node.Entrypoint)
|
||||
}
|
||||
|
||||
126
taskfile/node_git.go
Normal file
126
taskfile/node_git.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
giturls "github.com/chainguard-dev/git-urls"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
)
|
||||
|
||||
// An GitNode is a node that reads a Taskfile from a remote location via Git.
|
||||
type GitNode struct {
|
||||
*BaseNode
|
||||
URL *url.URL
|
||||
rawUrl string
|
||||
ref string
|
||||
path string
|
||||
}
|
||||
|
||||
func NewGitNode(
|
||||
entrypoint string,
|
||||
dir string,
|
||||
insecure bool,
|
||||
opts ...NodeOption,
|
||||
) (*GitNode, error) {
|
||||
base := NewBaseNode(dir, opts...)
|
||||
u, err := giturls.Parse(entrypoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
basePath, path := func() (string, string) {
|
||||
x := strings.Split(u.Path, "//")
|
||||
return x[0], x[1]
|
||||
}()
|
||||
ref := u.Query().Get("ref")
|
||||
|
||||
rawUrl := u.String()
|
||||
|
||||
u.RawQuery = ""
|
||||
u.Path = basePath
|
||||
|
||||
if u.Scheme == "http" && !insecure {
|
||||
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
|
||||
}
|
||||
return &GitNode{
|
||||
BaseNode: base,
|
||||
URL: u,
|
||||
rawUrl: rawUrl,
|
||||
ref: ref,
|
||||
path: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (node *GitNode) Location() string {
|
||||
return node.rawUrl
|
||||
}
|
||||
|
||||
func (node *GitNode) Remote() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (node *GitNode) Read(_ context.Context) ([]byte, error) {
|
||||
fs := memfs.New()
|
||||
storer := memory.NewStorage()
|
||||
_, err := git.Clone(storer, fs, &git.CloneOptions{
|
||||
URL: node.URL.String(),
|
||||
ReferenceName: plumbing.ReferenceName(node.ref),
|
||||
SingleBranch: true,
|
||||
Depth: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file, err := fs.Open(node.path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Read the entire response body
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) {
|
||||
dir, _ := filepath.Split(node.path)
|
||||
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.URL, filepath.Join(dir, entrypoint))
|
||||
if node.ref != "" {
|
||||
return fmt.Sprintf("%s?ref=%s", resolvedEntrypoint, node.ref), nil
|
||||
}
|
||||
return resolvedEntrypoint, nil
|
||||
}
|
||||
|
||||
func (node *GitNode) ResolveDir(dir string) (string, error) {
|
||||
path, err := execext.Expand(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if filepathext.IsAbs(path) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
|
||||
// This means that files are included relative to one another
|
||||
entrypointDir := filepath.Dir(node.Dir())
|
||||
return filepathext.SmartJoin(entrypointDir, path), nil
|
||||
}
|
||||
|
||||
func (node *GitNode) FilenameAndLastDir() (string, string) {
|
||||
return filepath.Base(node.path), filepath.Base(filepath.Dir(node.path))
|
||||
}
|
||||
85
taskfile/node_git_test.go
Normal file
85
taskfile/node_git_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGitNode_ssh(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := NewGitNode("git@github.com:foo/bar.git//Taskfile.yml?ref=main", "", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "main", node.ref)
|
||||
assert.Equal(t, "Taskfile.yml", node.path)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git//Taskfile.yml?ref=main", node.rawUrl)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git", node.URL.String())
|
||||
entrypoint, err := node.ResolveEntrypoint("common.yml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git//common.yml?ref=main", entrypoint)
|
||||
}
|
||||
|
||||
func TestGitNode_sshWithDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := NewGitNode("git@github.com:foo/bar.git//directory/Taskfile.yml?ref=main", "", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "main", node.ref)
|
||||
assert.Equal(t, "directory/Taskfile.yml", node.path)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git//directory/Taskfile.yml?ref=main", node.rawUrl)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git", node.URL.String())
|
||||
entrypoint, err := node.ResolveEntrypoint("common.yml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git//directory/common.yml?ref=main", entrypoint)
|
||||
}
|
||||
|
||||
func TestGitNode_https(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := NewGitNode("https://github.com/foo/bar.git//Taskfile.yml?ref=main", "", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "main", node.ref)
|
||||
assert.Equal(t, "Taskfile.yml", node.path)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git//Taskfile.yml?ref=main", node.rawUrl)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git", node.URL.String())
|
||||
entrypoint, err := node.ResolveEntrypoint("common.yml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git//common.yml?ref=main", entrypoint)
|
||||
}
|
||||
|
||||
func TestGitNode_httpsWithDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := NewGitNode("https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", "", false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "main", node.ref)
|
||||
assert.Equal(t, "directory/Taskfile.yml", node.path)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", node.rawUrl)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git", node.URL.String())
|
||||
entrypoint, err := node.ResolveEntrypoint("common.yml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git//directory/common.yml?ref=main", entrypoint)
|
||||
}
|
||||
|
||||
func TestGitNode_FilenameAndDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := NewGitNode("https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", "", false)
|
||||
assert.NoError(t, err)
|
||||
filename, dir := node.FilenameAndLastDir()
|
||||
assert.Equal(t, "Taskfile.yml", filename)
|
||||
assert.Equal(t, "directory", dir)
|
||||
|
||||
node, err = NewGitNode("https://github.com/foo/bar.git//Taskfile.yml?ref=main", "", false)
|
||||
assert.NoError(t, err)
|
||||
filename, dir = node.FilenameAndLastDir()
|
||||
assert.Equal(t, "Taskfile.yml", filename)
|
||||
assert.Equal(t, ".", dir)
|
||||
|
||||
node, err = NewGitNode("https://github.com/foo/bar.git//multiple/directory/Taskfile.yml?ref=main", "", false)
|
||||
assert.NoError(t, err)
|
||||
filename, dir = node.FilenameAndLastDir()
|
||||
assert.Equal(t, "Taskfile.yml", filename)
|
||||
assert.Equal(t, "directory", dir)
|
||||
}
|
||||
@@ -17,7 +17,9 @@ import (
|
||||
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
|
||||
type HTTPNode struct {
|
||||
*BaseNode
|
||||
URL *url.URL
|
||||
URL *url.URL
|
||||
logger *logger.Logger
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewHTTPNode(
|
||||
@@ -36,18 +38,12 @@ func NewHTTPNode(
|
||||
if url.Scheme == "http" && !insecure {
|
||||
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
|
||||
}
|
||||
ctx, cf := context.WithTimeout(context.Background(), timeout)
|
||||
defer cf()
|
||||
url, err = RemoteExists(ctx, l, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
return nil, &errors.TaskfileNetworkTimeoutError{URI: url.String(), Timeout: timeout}
|
||||
}
|
||||
|
||||
return &HTTPNode{
|
||||
BaseNode: base,
|
||||
URL: url,
|
||||
timeout: timeout,
|
||||
logger: l,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -60,6 +56,11 @@ func (node *HTTPNode) Remote() bool {
|
||||
}
|
||||
|
||||
func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
|
||||
url, err := RemoteExists(ctx, node.logger, node.URL, node.timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node.URL = url
|
||||
req, err := http.NewRequest("GET", node.URL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
|
||||
@@ -67,10 +68,12 @@ func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
|
||||
|
||||
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.URL.String(), Timeout: node.timeout}
|
||||
}
|
||||
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.TaskfileFetchFailedError{
|
||||
URI: node.URL.String(),
|
||||
@@ -107,6 +110,15 @@ func (node *HTTPNode) ResolveDir(dir string) (string, error) {
|
||||
|
||||
// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
|
||||
// This means that files are included relative to one another
|
||||
entrypointDir := filepath.Dir(node.Dir())
|
||||
return filepathext.SmartJoin(entrypointDir, path), nil
|
||||
parent := node.Dir()
|
||||
if node.Parent() != nil {
|
||||
parent = node.Parent().Dir()
|
||||
}
|
||||
|
||||
return filepathext.SmartJoin(parent, path), nil
|
||||
}
|
||||
|
||||
func (node *HTTPNode) FilenameAndLastDir() (string, string) {
|
||||
dir, filename := filepath.Split(node.URL.Path)
|
||||
return filepath.Base(dir), filename
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user