mirror of
https://github.com/go-task/task.git
synced 2026-06-27 06:34:18 +00:00
Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -8,6 +8,6 @@ charset = utf-8
|
|||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
indent_style = tab
|
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,css,svg,sh,bash,fish}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
|||||||
github: [andreynering, pd93]
|
github: [andreynering, pd93, vmaerten]
|
||||||
open_collective: task
|
open_collective: task
|
||||||
custom: https://taskfile.dev/donate/
|
custom: https://taskfile.dev/donate/
|
||||||
|
|||||||
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:
|
issue-awaiting-response:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GH_PAT}}
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
2
.github/workflows/issue-closed.yml
vendored
2
.github/workflows/issue-closed.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
|||||||
issue-closed:
|
issue-closed:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GH_PAT}}
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
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', ':')
|
if: github.event.label.name == format('experiment{0} proposed', ':')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GH_PAT}}
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
script: |
|
||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
if: github.event.label.name == format('experiment{0} draft', ':')
|
if: github.event.label.name == format('experiment{0} draft', ':')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GH_PAT}}
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
script: |
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
if: github.event.label.name == format('experiment{0} candidate', ':')
|
if: github.event.label.name == format('experiment{0} candidate', ':')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GH_PAT}}
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
script: |
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
if: github.event.label.name == format('experiment{0} stable', ':')
|
if: github.event.label.name == format('experiment{0} stable', ':')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GH_PAT}}
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
script: |
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
if: github.event.label.name == format('experiment{0} released', ':')
|
if: github.event.label.name == format('experiment{0} released', ':')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GH_PAT}}
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
script: |
|
||||||
@@ -85,7 +85,7 @@ jobs:
|
|||||||
if: github.event.label.name == format('experiment{0} abandoned', ':')
|
if: github.event.label.name == format('experiment{0} abandoned', ':')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GH_PAT}}
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
script: |
|
||||||
@@ -105,7 +105,7 @@ jobs:
|
|||||||
if: github.event.label.name == format('experiment{0} superseded', ':')
|
if: github.event.label.name == format('experiment{0} superseded', ':')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GH_PAT}}
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
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:
|
issue-needs-triage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GH_PAT}}
|
github-token: ${{secrets.GH_PAT}}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
19
.github/workflows/lint.yml
vendored
19
.github/workflows/lint.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
name: Lint
|
name: Lint
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x, 1.22.x]
|
go-version: [1.22.x, 1.23.x]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: v1.55.2
|
version: v1.60.1
|
||||||
|
|
||||||
lint-jsonschema:
|
lint-jsonschema:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -41,3 +41,18 @@ jobs:
|
|||||||
|
|
||||||
- name: check-jsonschema (metaschema)
|
- name: check-jsonschema (metaschema)
|
||||||
run: check-jsonschema --check-metaschema website/static/schema.json
|
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.')
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -15,10 +15,10 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.21.x
|
go-version: 1.22.x
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --clean
|
args: release --clean
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
name: Test
|
name: Test
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.21.x, 1.22.x]
|
go-version: [1.22.x, 1.23.x]
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
runs-on: ${{matrix.platform}}
|
runs-on: ${{matrix.platform}}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
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@v4
|
|
||||||
|
|
||||||
- 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@v2
|
|
||||||
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
|
# editors
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/*
|
.vscode/settings.json
|
||||||
!.vscode/*-sample.json
|
|
||||||
.fleet/
|
.fleet/
|
||||||
|
|
||||||
# exuberant ctags
|
# exuberant ctags
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||||
|
version: 2
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- binary: task
|
- binary: task
|
||||||
main: ./cmd/task
|
main: ./cmd/task
|
||||||
@@ -46,7 +49,7 @@ release:
|
|||||||
draft: true
|
draft: true
|
||||||
|
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{.Tag}}"
|
version_template: "{{.Version}}"
|
||||||
|
|
||||||
checksum:
|
checksum:
|
||||||
name_template: "task_checksums.txt"
|
name_template: "task_checksums.txt"
|
||||||
|
|||||||
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"
|
||||||
|
]
|
||||||
|
}
|
||||||
74
CHANGELOG.md
74
CHANGELOG.md
@@ -1,5 +1,79 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
## v3.38.0 - 2024-06-30
|
||||||
|
|
||||||
- Added `TASK_EXE` special variable (#1616, #1624 by @pd93 and @andreynering).
|
- Added `TASK_EXE` special variable (#1616, #1624 by @pd93 and @andreynering).
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ tasks:
|
|||||||
- go install -v ./cmd/task
|
- go install -v ./cmd/task
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
desc: Runs Go generate to create mocks
|
desc: Runs Mockery to create mocks
|
||||||
aliases: [gen, g]
|
aliases: [gen, g]
|
||||||
deps: [install:mockery]
|
deps: [install:mockery]
|
||||||
sources:
|
sources:
|
||||||
@@ -57,6 +57,7 @@ tasks:
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
desc: Cleans temp files and folders
|
desc: Cleans temp files and folders
|
||||||
|
aliases: [clear]
|
||||||
cmds:
|
cmds:
|
||||||
- rm -rf dist/
|
- rm -rf dist/
|
||||||
- rm -rf tmp/
|
- rm -rf tmp/
|
||||||
|
|||||||
@@ -83,6 +83,15 @@ func run() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.Completion != "" {
|
||||||
|
script, err := task.Completion(flags.Completion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(script)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if flags.Global {
|
if flags.Global {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -189,6 +198,7 @@ func run() error {
|
|||||||
globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||||
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||||
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||||
|
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
||||||
e.Taskfile.Vars.Merge(globals, nil)
|
e.Taskfile.Vars.Merge(globals, nil)
|
||||||
|
|
||||||
if !flags.Watch {
|
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
|
end
|
||||||
|
|
||||||
# Grab names and descriptions (if any) of the tasks
|
# 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
|
if test $output
|
||||||
echo $output
|
echo $output
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#compdef task
|
#compdef task
|
||||||
|
compdef _task task
|
||||||
local context state state_descr line
|
|
||||||
typeset -A opt_args
|
typeset -A opt_args
|
||||||
|
|
||||||
_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"
|
_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"
|
||||||
@@ -39,26 +38,33 @@ function __task_list() {
|
|||||||
_describe 'Task to run' scripts
|
_describe 'Task to run' scripts
|
||||||
}
|
}
|
||||||
|
|
||||||
_arguments \
|
_task() {
|
||||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \
|
_arguments \
|
||||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \
|
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \
|
||||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]' \
|
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \
|
||||||
'(-c --color)'{-c,--color}'[colored output]' \
|
'(-f --force)'{-f,--force}'[run even if task is up-to-date]' \
|
||||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \
|
'(-c --color)'{-c,--color}'[colored output]' \
|
||||||
'(--dry)--dry[dry-run mode, compile and print tasks only]' \
|
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \
|
||||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \
|
'(--dry)--dry[dry-run mode, compile and print tasks only]' \
|
||||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \
|
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \
|
||||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: ' \
|
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \
|
||||||
'(-s --silent)'{-s,--silent}'[disable echoing]' \
|
'(--output-group-end)--output-group-end[message template after grouped output]:template text: ' \
|
||||||
'(--status)--status[exit non-zero if supplied tasks not up-to-date]' \
|
'(-s --silent)'{-s,--silent}'[disable echoing]' \
|
||||||
'(--summary)--summary[show summary\: field from tasks instead of running them]' \
|
'(--status)--status[exit non-zero if supplied tasks not up-to-date]' \
|
||||||
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files' \
|
'(--summary)--summary[show summary\: field from tasks instead of running them]' \
|
||||||
'(-v --verbose)'{-v,--verbose}'[verbose mode]' \
|
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files' \
|
||||||
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]' \
|
'(-v --verbose)'{-v,--verbose}'[verbose mode]' \
|
||||||
+ '(operation)' \
|
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]' \
|
||||||
{-l,--list}'[list describable tasks]' \
|
+ '(operation)' \
|
||||||
{-a,--list-all}'[list all tasks]' \
|
{-l,--list}'[list describable tasks]' \
|
||||||
{-i,--init}'[create new Taskfile.yml]' \
|
{-a,--list-all}'[list all tasks]' \
|
||||||
'(-*)'{-h,--help}'[show help]' \
|
{-i,--init}'[create new Taskfile.yml]' \
|
||||||
'(-*)--version[show version and exit]' \
|
'(-*)'{-h,--help}'[show help]' \
|
||||||
'*: :__task_list'
|
'(-*)--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
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ const (
|
|||||||
CodeTaskCalledTooManyTimes
|
CodeTaskCalledTooManyTimes
|
||||||
CodeTaskCancelled
|
CodeTaskCancelled
|
||||||
CodeTaskMissingRequiredVars
|
CodeTaskMissingRequiredVars
|
||||||
|
CodeTaskNotAllowedVars
|
||||||
)
|
)
|
||||||
|
|
||||||
// TaskError extends the standard error interface with a Code method. This code will
|
// TaskError extends the standard error interface with a Code method. This code will
|
||||||
|
|||||||
@@ -80,6 +80,19 @@ func (err *TaskNameConflictError) Code() int {
|
|||||||
return CodeTaskNameConflict
|
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
|
// TaskCalledTooManyTimesError is returned when the maximum task call limit is
|
||||||
// exceeded. This is to prevent infinite loops and cyclic dependencies.
|
// exceeded. This is to prevent infinite loops and cyclic dependencies.
|
||||||
type TaskCalledTooManyTimesError struct {
|
type TaskCalledTooManyTimesError struct {
|
||||||
@@ -145,3 +158,29 @@ func (err *TaskMissingRequiredVars) Error() string {
|
|||||||
func (err *TaskMissingRequiredVars) Code() int {
|
func (err *TaskMissingRequiredVars) Code() int {
|
||||||
return CodeTaskMissingRequiredVars
|
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
|
||||||
|
}
|
||||||
|
|||||||
44
go.mod
44
go.mod
@@ -1,38 +1,60 @@
|
|||||||
module github.com/go-task/task/v3
|
module github.com/go-task/task/v3
|
||||||
|
|
||||||
go 1.21.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Ladicle/tabwriter v1.0.0
|
github.com/Ladicle/tabwriter v1.0.0
|
||||||
github.com/Masterminds/semver/v3 v3.2.1
|
github.com/Masterminds/semver/v3 v3.3.0
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0
|
github.com/alecthomas/chroma/v2 v2.14.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/dominikbraun/graph v0.23.0
|
github.com/dominikbraun/graph v0.23.0
|
||||||
github.com/fatih/color v1.17.0
|
github.com/fatih/color v1.18.0
|
||||||
|
github.com/go-git/go-billy/v5 v5.6.0
|
||||||
|
github.com/go-git/go-git/v5 v5.12.0
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0
|
github.com/go-task/slim-sprig/v3 v3.0.0
|
||||||
github.com/go-task/template v0.0.0-20240602015157-960e6f576656
|
github.com/go-task/template v0.1.0
|
||||||
github.com/joho/godotenv v1.5.1
|
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/mitchellh/hashstructure/v2 v2.0.2
|
||||||
github.com/otiai10/copy v1.14.0
|
github.com/otiai10/copy v1.14.0
|
||||||
github.com/radovskyb/watcher v1.0.7
|
github.com/radovskyb/watcher v1.0.7
|
||||||
github.com/sajari/fuzzy v1.0.0
|
github.com/sajari/fuzzy v1.0.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
|
github.com/whilp/git-urls v1.0.0
|
||||||
github.com/zeebo/xxh3 v1.0.2
|
github.com/zeebo/xxh3 v1.0.2
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/sync v0.8.0
|
||||||
golang.org/x/term v0.21.0
|
golang.org/x/term v0.25.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
mvdan.cc/sh/v3 v3.8.0
|
mvdan.cc/sh/v3 v3.10.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
|
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||||
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
|
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9 // 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-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // 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/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
|
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
golang.org/x/sys v0.21.0 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
|
golang.org/x/mod v0.18.0 // indirect
|
||||||
|
golang.org/x/net v0.27.0 // indirect
|
||||||
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
|
golang.org/x/tools v0.22.0 // indirect
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
177
go.sum
177
go.sum
@@ -1,39 +1,80 @@
|
|||||||
|
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 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
|
||||||
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
|
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
|
||||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
github.com/Masterminds/semver/v3 v3.3.0/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/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
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/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 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
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 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
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/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/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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.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 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
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 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||||
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
||||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
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-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-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 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/go-task/template v0.0.0-20240602015157-960e6f576656 h1:knZZ4zVdTBQnevBz0zSES++4Mr7wr+cHopLvHabIgkA=
|
github.com/go-task/template v0.1.0 h1:ym/r2G937RZA1bsgiWedNnY9e5kxDT+3YcoAnuIetTE=
|
||||||
github.com/go-task/template v0.0.0-20240602015157-960e6f576656/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
|
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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
|
github.com/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 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
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 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
@@ -41,44 +82,128 @@ 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.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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A=
|
||||||
github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
|
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 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
|
github.com/muesli/cancelreader v0.2.2/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 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
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 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
||||||
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
||||||
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
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 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||||
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
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/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
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/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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
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.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
|
||||||
|
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
|
||||||
|
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 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
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 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
|
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/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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
|
golang.org/x/sync v0.8.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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
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.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||||
|
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||||
|
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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
|
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 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
mvdan.cc/sh/v3 v3.8.0 h1:ZxuJipLZwr/HLbASonmXtcvvC9HXY9d2lXZHnKGjFc8=
|
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
|
||||||
mvdan.cc/sh/v3 v3.8.0/go.mod h1:w04623xkgBVo7/IUK89E0g8hBykgEpN0vgOj3RJr6MY=
|
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
|
||||||
|
|||||||
4
hash.go
4
hash.go
@@ -1,15 +1,15 @@
|
|||||||
package task
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/hash"
|
"github.com/go-task/task/v3/internal/hash"
|
||||||
"github.com/go-task/task/v3/internal/slicesext"
|
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e *Executor) GetHash(t *ast.Task) (string, error) {
|
func (e *Executor) GetHash(t *ast.Task) (string, error) {
|
||||||
r := slicesext.FirstNonZero(t.Run, e.Taskfile.Run)
|
r := cmp.Or(t.Run, e.Taskfile.Run)
|
||||||
var h hash.HashFunc
|
var h hash.HashFunc
|
||||||
switch r {
|
switch r {
|
||||||
case "always":
|
case "always":
|
||||||
|
|||||||
28
help.go
28
help.go
@@ -160,23 +160,21 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
|||||||
}
|
}
|
||||||
var g errgroup.Group
|
var g errgroup.Group
|
||||||
for i := range tasks {
|
for i := range tasks {
|
||||||
task := tasks[i]
|
|
||||||
j := i
|
|
||||||
aliases := []string{}
|
aliases := []string{}
|
||||||
if len(task.Aliases) > 0 {
|
if len(tasks[i].Aliases) > 0 {
|
||||||
aliases = task.Aliases
|
aliases = tasks[i].Aliases
|
||||||
}
|
}
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
o.Tasks[j] = editors.Task{
|
o.Tasks[i] = editors.Task{
|
||||||
Name: task.Name(),
|
Name: tasks[i].Name(),
|
||||||
Desc: task.Desc,
|
Desc: tasks[i].Desc,
|
||||||
Summary: task.Summary,
|
Summary: tasks[i].Summary,
|
||||||
Aliases: aliases,
|
Aliases: aliases,
|
||||||
UpToDate: false,
|
UpToDate: false,
|
||||||
Location: &editors.Location{
|
Location: &editors.Location{
|
||||||
Line: task.Location.Line,
|
Line: tasks[i].Location.Line,
|
||||||
Column: task.Location.Column,
|
Column: tasks[i].Location.Column,
|
||||||
Taskfile: task.Location.Taskfile,
|
Taskfile: tasks[i].Location.Taskfile,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,10 +184,10 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
|||||||
|
|
||||||
// Get the fingerprinting method to use
|
// Get the fingerprinting method to use
|
||||||
method := e.Taskfile.Method
|
method := e.Taskfile.Method
|
||||||
if task.Method != "" {
|
if tasks[i].Method != "" {
|
||||||
method = task.Method
|
method = tasks[i].Method
|
||||||
}
|
}
|
||||||
upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), task,
|
upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), tasks[i],
|
||||||
fingerprint.WithMethod(method),
|
fingerprint.WithMethod(method),
|
||||||
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
||||||
fingerprint.WithDry(e.Dry),
|
fingerprint.WithDry(e.Dry),
|
||||||
@@ -199,7 +197,7 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Tasks[j].UpToDate = upToDate
|
o.Tasks[i].UpToDate = upToDate
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -45,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) {
|
func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool) (*ast.Vars, error) {
|
||||||
result := GetEnviron()
|
result := GetEnviron()
|
||||||
if t != nil {
|
specialVars, err := c.getSpecialVars(t, call)
|
||||||
specialVars, err := c.getSpecialVars(t)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
for k, v := range specialVars {
|
||||||
for k, v := range specialVars {
|
result.Set(k, ast.Var{Value: v})
|
||||||
result.Set(k, ast.Var{Value: v})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getRangeFunc := func(dir string) func(k string, v ast.Var) error {
|
getRangeFunc := func(dir string) func(k string, v ast.Var) error {
|
||||||
@@ -75,8 +74,8 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
|
|||||||
if err := cache.Err(); err != nil {
|
if err := cache.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If the variable is not dynamic, we can set it and return
|
// If the variable is already set, we can set it and return
|
||||||
if newVar.Value != nil || newVar.Sh == "" {
|
if newVar.Value != nil {
|
||||||
result.Set(k, ast.Var{Value: newVar.Value})
|
result.Set(k, ast.Var{Value: newVar.Value})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -137,10 +136,15 @@ func (c *Compiler) HandleDynamicVar(v ast.Var, dir string) (string, error) {
|
|||||||
c.muDynamicCache.Lock()
|
c.muDynamicCache.Lock()
|
||||||
defer c.muDynamicCache.Unlock()
|
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 {
|
if c.dynamicCache == nil {
|
||||||
c.dynamicCache = make(map[string]string, 30)
|
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
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +155,7 @@ func (c *Compiler) HandleDynamicVar(v ast.Var, dir string) (string, error) {
|
|||||||
|
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
opts := &execext.RunCommandOptions{
|
opts := &execext.RunCommandOptions{
|
||||||
Command: v.Sh,
|
Command: *v.Sh,
|
||||||
Dir: dir,
|
Dir: dir,
|
||||||
Stdout: &stdout,
|
Stdout: &stdout,
|
||||||
Stderr: c.Logger.Stderr,
|
Stderr: c.Logger.Stderr,
|
||||||
@@ -165,7 +169,7 @@ func (c *Compiler) HandleDynamicVar(v ast.Var, dir string) (string, error) {
|
|||||||
result := strings.TrimSuffix(stdout.String(), "\r\n")
|
result := strings.TrimSuffix(stdout.String(), "\r\n")
|
||||||
result = strings.TrimSuffix(result, "\n")
|
result = strings.TrimSuffix(result, "\n")
|
||||||
|
|
||||||
c.dynamicCache[v.Sh] = result
|
c.dynamicCache[*v.Sh] = result
|
||||||
c.Logger.VerboseErrf(logger.Magenta, "task: dynamic variable: %q result: %q\n", v.Sh, result)
|
c.Logger.VerboseErrf(logger.Magenta, "task: dynamic variable: %q result: %q\n", v.Sh, result)
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -179,15 +183,19 @@ func (c *Compiler) ResetCache() {
|
|||||||
c.dynamicCache = nil
|
c.dynamicCache = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) getSpecialVars(t *ast.Task) (map[string]string, error) {
|
func (c *Compiler) getSpecialVars(t *ast.Task, call *ast.Call) (map[string]string, error) {
|
||||||
return map[string]string{
|
allVars := map[string]string{
|
||||||
"TASK": t.Task,
|
|
||||||
"TASK_EXE": filepath.ToSlash(os.Args[0]),
|
"TASK_EXE": filepath.ToSlash(os.Args[0]),
|
||||||
"ROOT_TASKFILE": filepathext.SmartJoin(c.Dir, c.Entrypoint),
|
"ROOT_TASKFILE": filepathext.SmartJoin(c.Dir, c.Entrypoint),
|
||||||
"ROOT_DIR": c.Dir,
|
"ROOT_DIR": c.Dir,
|
||||||
"TASKFILE": t.Location.Taskfile,
|
|
||||||
"TASKFILE_DIR": filepath.Dir(t.Location.Taskfile),
|
|
||||||
"USER_WORKING_DIR": c.UserWorkingDir,
|
"USER_WORKING_DIR": c.UserWorkingDir,
|
||||||
"TASK_VERSION": version.GetVersion(),
|
"TASK_VERSION": version.GetVersion(),
|
||||||
}, nil
|
}
|
||||||
|
if t != nil {
|
||||||
|
maps.Copy(allVars, map[string]string{"TASK": t.Task, "TASKFILE": t.Location.Taskfile, "TASKFILE_DIR": filepath.Dir(t.Location.Taskfile)})
|
||||||
|
}
|
||||||
|
if call != nil {
|
||||||
|
maps.Copy(allVars, map[string]string{"ALIAS": call.Task})
|
||||||
|
}
|
||||||
|
return allVars, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, erro
|
|||||||
|
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
// Loop over each field and call traverseFunc recursively
|
// 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 {
|
if err := traverseFunc(copy.Field(i), v.Field(i)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,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
|
// Create an empty copy from the original value's type
|
||||||
copy.Set(reflect.MakeSlice(v.Type(), v.Len(), v.Cap()))
|
copy.Set(reflect.MakeSlice(v.Type(), v.Len(), v.Cap()))
|
||||||
// Loop over each element and call traverseFunc recursively
|
// 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 {
|
if err := traverseFunc(copy.Index(i), v.Index(i)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
9
internal/env/env.go
vendored
9
internal/env/env.go
vendored
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/experiments"
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,15 +12,15 @@ func Get(t *ast.Task) []string {
|
|||||||
if t.Env == nil {
|
if t.Env == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
environ := os.Environ()
|
environ := os.Environ()
|
||||||
for k, v := range t.Env.ToCacheMap() {
|
for k, v := range t.Env.ToCacheMap() {
|
||||||
if !isTypeAllowed(v) {
|
if !isTypeAllowed(v) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !experiments.EnvPrecedence.Enabled {
|
||||||
if _, alreadySet := os.LookupEnv(k); alreadySet {
|
if _, alreadySet := os.LookupEnv(k); alreadySet {
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
environ = append(environ, fmt.Sprintf("%s=%v", k, v))
|
environ = append(environ, fmt.Sprintf("%s=%v", k, v))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,14 +90,6 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
|||||||
return r.Run(ctx, p)
|
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
|
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
|
||||||
// if available.
|
// if available.
|
||||||
func Expand(s string) (string, error) {
|
func Expand(s string) (string, error) {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ var (
|
|||||||
RemoteTaskfiles Experiment
|
RemoteTaskfiles Experiment
|
||||||
AnyVariables Experiment
|
AnyVariables Experiment
|
||||||
MapVariables Experiment
|
MapVariables Experiment
|
||||||
|
EnvPrecedence Experiment
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -37,6 +38,7 @@ func init() {
|
|||||||
RemoteTaskfiles = New("REMOTE_TASKFILES")
|
RemoteTaskfiles = New("REMOTE_TASKFILES")
|
||||||
AnyVariables = New("ANY_VARIABLES", "1", "2")
|
AnyVariables = New("ANY_VARIABLES", "1", "2")
|
||||||
MapVariables = New("MAP_VARIABLES", "1", "2")
|
MapVariables = New("MAP_VARIABLES", "1", "2")
|
||||||
|
EnvPrecedence = New("ENV_PRECEDENCE")
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(xName string, enabledValues ...string) Experiment {
|
func New(xName string, enabledValues ...string) Experiment {
|
||||||
@@ -70,6 +72,7 @@ func getEnvFilePath() string {
|
|||||||
fs := pflag.NewFlagSet("experiments", pflag.ContinueOnError)
|
fs := pflag.NewFlagSet("experiments", pflag.ContinueOnError)
|
||||||
fs.StringVarP(&dir, "dir", "d", "", "Sets directory of execution.")
|
fs.StringVarP(&dir, "dir", "d", "", "Sets directory of execution.")
|
||||||
fs.StringVarP(&taskfile, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml".`)
|
fs.StringVarP(&taskfile, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml".`)
|
||||||
|
fs.Usage = func() {}
|
||||||
_ = fs.Parse(os.Args[1:])
|
_ = fs.Parse(os.Args[1:])
|
||||||
// If the directory is set, find a .env file in that directory.
|
// If the directory is set, find a .env file in that directory.
|
||||||
if dir != "" {
|
if dir != "" {
|
||||||
@@ -104,5 +107,6 @@ func List(l *logger.Logger) error {
|
|||||||
printExperiment(w, l, GentleForce)
|
printExperiment(w, l, GentleForce)
|
||||||
printExperiment(w, l, RemoteTaskfiles)
|
printExperiment(w, l, RemoteTaskfiles)
|
||||||
printExperiment(w, l, MapVariables)
|
printExperiment(w, l, MapVariables)
|
||||||
|
printExperiment(w, l, EnvPrecedence)
|
||||||
return w.Flush()
|
return w.Flush()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package flags
|
package flags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -38,6 +40,7 @@ var (
|
|||||||
Version bool
|
Version bool
|
||||||
Help bool
|
Help bool
|
||||||
Init bool
|
Init bool
|
||||||
|
Completion string
|
||||||
List bool
|
List bool
|
||||||
ListAll bool
|
ListAll bool
|
||||||
ListJson bool
|
ListJson bool
|
||||||
@@ -76,10 +79,14 @@ func init() {
|
|||||||
log.Print(usage)
|
log.Print(usage)
|
||||||
pflag.PrintDefaults()
|
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.BoolVar(&Version, "version", false, "Show Task version.")
|
||||||
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
|
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.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(&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(&ListAll, "list-all", "a", false, "Lists tasks with or without a description.")
|
||||||
pflag.BoolVarP(&ListJson, "json", "j", false, "Formats task list as JSON.")
|
pflag.BoolVarP(&ListJson, "json", "j", false, "Formats task list as JSON.")
|
||||||
@@ -102,7 +109,7 @@ func init() {
|
|||||||
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.")
|
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.")
|
||||||
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.")
|
pflag.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.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.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.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.")
|
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
|
||||||
@@ -118,7 +125,7 @@ func init() {
|
|||||||
// Remote Taskfiles experiment will adds the "download" and "offline" flags
|
// Remote Taskfiles experiment will adds the "download" and "offline" flags
|
||||||
if experiments.RemoteTaskfiles.Enabled {
|
if experiments.RemoteTaskfiles.Enabled {
|
||||||
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
|
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.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.")
|
||||||
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
|
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func envColor(env string, defaultColor color.Attribute) []color.Attribute {
|
|||||||
// Otherwise, split by semicolons (ANSI color codes) and use them as is.
|
// Otherwise, split by semicolons (ANSI color codes) and use them as is.
|
||||||
attributeStrs := strings.Split(override, ",")
|
attributeStrs := strings.Split(override, ",")
|
||||||
if len(attributeStrs) == 3 {
|
if len(attributeStrs) == 3 {
|
||||||
attributeStrs = append([]string{"38", "2"}, attributeStrs...)
|
attributeStrs = slices.Concat([]string{"38", "2"}, attributeStrs)
|
||||||
} else {
|
} else {
|
||||||
attributeStrs = strings.Split(override, ";")
|
attributeStrs = strings.Split(override, ";")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,3 @@ func UniqueJoin[T cmp.Ordered](ss ...[]T) []T {
|
|||||||
slices.Sort(r)
|
slices.Sort(r)
|
||||||
return slices.Compact(r)
|
return slices.Compact(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func FirstNonZero[T comparable](values ...T) T {
|
|
||||||
var zero T
|
|
||||||
for _, v := range values {
|
|
||||||
if v != zero {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return zero
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ var templateFuncs template.FuncMap
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
taskFuncs := template.FuncMap{
|
taskFuncs := template.FuncMap{
|
||||||
"OS": func() string { return runtime.GOOS },
|
"OS": func() string { return runtime.GOOS },
|
||||||
"ARCH": func() string { return runtime.GOARCH },
|
"ARCH": func() string { return runtime.GOARCH },
|
||||||
|
"numCPU": func() int { return runtime.NumCPU() },
|
||||||
"catLines": func(s string) string {
|
"catLines": func(s string) string {
|
||||||
s = strings.ReplaceAll(s, "\r\n", " ")
|
s = strings.ReplaceAll(s, "\r\n", " ")
|
||||||
return strings.ReplaceAll(s, "\n", " ")
|
return strings.ReplaceAll(s, "\n", " ")
|
||||||
|
|||||||
@@ -15,8 +15,12 @@ func init() {
|
|||||||
if !ok || info.Main.Version == "" {
|
if !ok || info.Main.Version == "" {
|
||||||
version = "unknown"
|
version = "unknown"
|
||||||
} else {
|
} else {
|
||||||
version = info.Main.Version
|
if version == "" {
|
||||||
sum = info.Main.Sum
|
version = info.Main.Version
|
||||||
|
}
|
||||||
|
if sum == "" {
|
||||||
|
sum = info.Main.Sum
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@go-task/cli",
|
"name": "@go-task/cli",
|
||||||
"version": "3.38.0",
|
"version": "3.40.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@go-task/cli",
|
"name": "@go-task/cli",
|
||||||
"version": "3.38.0",
|
"version": "3.40.0",
|
||||||
"description": "A task runner / simpler Make alternative written in Go",
|
"description": "A task runner / simpler Make alternative written in Go",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "go-npm install",
|
"postinstall": "go-npm install",
|
||||||
|
|||||||
25
requires.go
25
requires.go
@@ -1,13 +1,13 @@
|
|||||||
package task
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"slices"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"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, call *ast.Call) error {
|
||||||
if t.Requires == nil || len(t.Requires.Vars) == 0 {
|
if t.Requires == nil || len(t.Requires.Vars) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -18,9 +18,19 @@ func (e *Executor) areTaskRequiredVarsSet(ctx context.Context, t *ast.Task, call
|
|||||||
}
|
}
|
||||||
|
|
||||||
var missingVars []string
|
var missingVars []string
|
||||||
|
var notAllowedValuesVars []errors.NotAllowedVar
|
||||||
for _, requiredVar := range t.Requires.Vars {
|
for _, requiredVar := range t.Requires.Vars {
|
||||||
if !vars.Exists(requiredVar) {
|
value, isString := vars.Get(requiredVar.Name).Value.(string)
|
||||||
missingVars = append(missingVars, requiredVar)
|
if !vars.Exists(requiredVar.Name) {
|
||||||
|
missingVars = append(missingVars, requiredVar.Name)
|
||||||
|
} else {
|
||||||
|
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 +41,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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
7
setup.go
7
setup.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ func (e *Executor) setupFuzzyModel() {
|
|||||||
words = append(words, taskName)
|
words = append(words, taskName)
|
||||||
|
|
||||||
for _, task := range e.Taskfile.Tasks.Values() {
|
for _, task := range e.Taskfile.Tasks.Values() {
|
||||||
words = append(words, task.Aliases...)
|
words = slices.Concat(words, task.Aliases)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,8 +134,8 @@ func (e *Executor) setupTempDir() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("TASK_REMOTE_DIR") != "" {
|
if os.Getenv("TASK_REMOTE_DIR") != "" {
|
||||||
if filepath.IsAbs(os.Getenv("TASK_TEMP_DIR")) || strings.HasPrefix(os.Getenv("TASK_TEMP_DIR"), "~") {
|
if filepath.IsAbs(os.Getenv("TASK_REMOTE_DIR")) || strings.HasPrefix(os.Getenv("TASK_REMOTE_DIR"), "~") {
|
||||||
remoteTempDir, err := execext.Expand(filepathext.SmartJoin(e.Dir, ".task"))
|
remoteTempDir, err := execext.Expand(os.Getenv("TASK_REMOTE_DIR"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
15
signals.go
15
signals.go
@@ -8,24 +8,25 @@ import (
|
|||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const interruptSignalsCount = 3
|
||||||
|
|
||||||
// NOTE(@andreynering): This function intercepts SIGINT and SIGTERM signals
|
// NOTE(@andreynering): This function intercepts SIGINT and SIGTERM signals
|
||||||
// so the Task process is not killed immediately and processes running have
|
// so the Task process is not killed immediately and processes running have
|
||||||
// time to do cleanup work.
|
// time to do cleanup work.
|
||||||
func (e *Executor) InterceptInterruptSignals() {
|
func (e *Executor) InterceptInterruptSignals() {
|
||||||
ch := make(chan os.Signal, 3)
|
ch := make(chan os.Signal, interruptSignalsCount)
|
||||||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for i := 1; i <= 3; i++ {
|
for i := range interruptSignalsCount {
|
||||||
sig := <-ch
|
sig := <-ch
|
||||||
|
|
||||||
if i < 3 {
|
if i+1 >= interruptSignalsCount {
|
||||||
e.Logger.Outf(logger.Yellow, "task: Signal received: %q\n", sig)
|
e.Logger.Errf(logger.Red, "task: Signal received for the third time: %q. Forcing shutdown\n", sig)
|
||||||
continue
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Logger.Errf(logger.Red, "task: Signal received for the third time: %q. Forcing shutdown\n", sig)
|
e.Logger.Outf(logger.Yellow, "task: Signal received: %q\n", sig)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var SLEEPIT, _ = filepath.Abs("./bin/sleepit")
|
||||||
SLEEPIT, _ = filepath.Abs("./bin/sleepit")
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSignalSentToProcessGroup(t *testing.T) {
|
func TestSignalSentToProcessGroup(t *testing.T) {
|
||||||
task, err := getTaskPath()
|
task, err := getTaskPath()
|
||||||
@@ -147,7 +145,7 @@ func TestSignalSentToProcessGroup(t *testing.T) {
|
|||||||
// where the negative PID means the corresponding process group. Note that
|
// 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
|
// 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.
|
// 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 {
|
if err := syscall.Kill(-sut.Process.Pid, syscall.SIGINT); err != nil {
|
||||||
t.Fatalf("sending INT signal to the process group: %v", err)
|
t.Fatalf("sending INT signal to the process group: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
60
task.go
60
task.go
@@ -11,6 +11,8 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/compiler"
|
"github.com/go-task/task/v3/internal/compiler"
|
||||||
"github.com/go-task/task/v3/internal/env"
|
"github.com/go-task/task/v3/internal/env"
|
||||||
@@ -200,7 +202,7 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.areTaskRequiredVarsSet(ctx, t, call); err != nil {
|
if err := e.areTaskRequiredVarsSet(t, call); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,13 +235,15 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Prompt != "" && !e.Dry {
|
for _, p := range t.Prompt {
|
||||||
if err := e.Logger.Prompt(logger.Yellow, t.Prompt, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) {
|
if p != "" && !e.Dry {
|
||||||
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
|
if err := e.Logger.Prompt(logger.Yellow, p, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) {
|
||||||
} else if errors.Is(err, logger.ErrPromptCancelled) {
|
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
|
||||||
return &errors.TaskCancelledByUserError{TaskName: call.Task}
|
} else if errors.Is(err, logger.ErrPromptCancelled) {
|
||||||
} else if err != nil {
|
return &errors.TaskCancelledByUserError{TaskName: call.Task}
|
||||||
return err
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,9 +251,11 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
|
|||||||
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v\n", t.Dir, err)
|
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v\n", t.Dir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var deferredExitCode uint8
|
||||||
|
|
||||||
for i := range t.Cmds {
|
for i := range t.Cmds {
|
||||||
if t.Cmds[i].Defer {
|
if t.Cmds[i].Defer {
|
||||||
defer e.runDeferred(t, call, i)
|
defer e.runDeferred(t, call, i, &deferredExitCode)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,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)
|
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v\n", err2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if execext.IsExitError(err) && t.IgnoreError {
|
exitCode, isExitError := interp.IsExitStatus(err)
|
||||||
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
|
if isExitError {
|
||||||
continue
|
if t.IgnoreError {
|
||||||
|
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
deferredExitCode = exitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if call.Indirect {
|
if call.Indirect {
|
||||||
@@ -312,10 +322,26 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
|
|||||||
return g.Wait()
|
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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
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 {
|
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())
|
e.Logger.VerboseErrf(logger.Yellow, "task: ignored error in deferred cmd: %s\n", err.Error())
|
||||||
}
|
}
|
||||||
@@ -372,7 +398,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *ast.Call,
|
|||||||
if closeErr := close(err); closeErr != nil {
|
if closeErr := close(err); closeErr != nil {
|
||||||
e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr)
|
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)
|
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v\n", t.Name(), err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -494,14 +520,12 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) {
|
|||||||
|
|
||||||
// Compile the list of tasks
|
// Compile the list of tasks
|
||||||
for i := range tasks {
|
for i := range tasks {
|
||||||
idx := i
|
|
||||||
task := tasks[idx]
|
|
||||||
g.Go(func() error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tasks[idx] = compiledTask
|
tasks[i] = compiledTask
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
353
task_test.go
353
task_test.go
@@ -5,6 +5,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
rand "math/rand/v2"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -12,6 +16,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -19,7 +24,9 @@ import (
|
|||||||
|
|
||||||
"github.com/go-task/task/v3"
|
"github.com/go-task/task/v3"
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
|
"github.com/go-task/task/v3/internal/experiments"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,7 +67,6 @@ func (fct fileContentTest) Run(t *testing.T) {
|
|||||||
for f := range fct.Files {
|
for f := range fct.Files {
|
||||||
_ = os.Remove(filepathext.SmartJoin(fct.Dir, f))
|
_ = os.Remove(filepathext.SmartJoin(fct.Dir, f))
|
||||||
}
|
}
|
||||||
|
|
||||||
e := &task.Executor{
|
e := &task.Executor{
|
||||||
Dir: fct.Dir,
|
Dir: fct.Dir,
|
||||||
TempDir: task.TempDir{
|
TempDir: task.TempDir{
|
||||||
@@ -71,9 +77,9 @@ func (fct fileContentTest) Run(t *testing.T) {
|
|||||||
Stdout: io.Discard,
|
Stdout: io.Discard,
|
||||||
Stderr: io.Discard,
|
Stderr: io.Discard,
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, e.Setup(), "e.Setup()")
|
require.NoError(t, e.Setup(), "e.Setup()")
|
||||||
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: fct.Target}), "e.Run(target)")
|
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: fct.Target}), "e.Run(target)")
|
||||||
|
|
||||||
for name, expectContent := range fct.Files {
|
for name, expectContent := range fct.Files {
|
||||||
t.Run(fct.name(name), func(t *testing.T) {
|
t.Run(fct.name(name), func(t *testing.T) {
|
||||||
path := filepathext.SmartJoin(e.Dir, name)
|
path := filepathext.SmartJoin(e.Dir, name)
|
||||||
@@ -108,6 +114,7 @@ func TestEmptyTaskfile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEnv(t *testing.T) {
|
func TestEnv(t *testing.T) {
|
||||||
|
t.Setenv("QUX", "from_os")
|
||||||
tt := fileContentTest{
|
tt := fileContentTest{
|
||||||
Dir: "testdata/env",
|
Dir: "testdata/env",
|
||||||
Target: "default",
|
Target: "default",
|
||||||
@@ -116,9 +123,21 @@ func TestEnv(t *testing.T) {
|
|||||||
"local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
"local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
|
||||||
"global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
|
"global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
|
||||||
"multiple_type.txt": "FOO='1' BAR='true' BAZ='1.1'\n",
|
"multiple_type.txt": "FOO='1' BAR='true' BAZ='1.1'\n",
|
||||||
|
"not-overriden.txt": "QUX='from_os'\n",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
|
t.Setenv("TASK_X_ENV_PRECEDENCE", "1")
|
||||||
|
experiments.EnvPrecedence = experiments.New("ENV_PRECEDENCE")
|
||||||
|
ttt := fileContentTest{
|
||||||
|
Dir: "testdata/env",
|
||||||
|
Target: "overriden",
|
||||||
|
TrimSpace: false,
|
||||||
|
Files: map[string]string{
|
||||||
|
"overriden.txt": "QUX='from_taskfile'\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ttt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVars(t *testing.T) {
|
func TestVars(t *testing.T) {
|
||||||
@@ -136,6 +155,39 @@ func TestVars(t *testing.T) {
|
|||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRequires(t *testing.T) {
|
||||||
|
const dir = "testdata/requires"
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
require.ErrorContains(t, e.Run(context.Background(), &ast.Call{Task: "missing-var"}), "task: Task \"missing-var\" cancelled because it is missing required variables: foo")
|
||||||
|
buff.Reset()
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
vars := &ast.Vars{}
|
||||||
|
vars.Set("foo", ast.Var{Value: "bar"})
|
||||||
|
require.NoError(t, e.Run(context.Background(), &ast.Call{
|
||||||
|
Task: "missing-var",
|
||||||
|
Vars: vars,
|
||||||
|
}))
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
require.ErrorContains(t, e.Run(context.Background(), &ast.Call{Task: "validation-var", Vars: vars}), "task: Task \"validation-var\" cancelled because it is missing required variables:\n - foo has an invalid value : 'bar' (allowed values : [one two])")
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
vars.Set("foo", ast.Var{Value: "one"})
|
||||||
|
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "validation-var", Vars: vars}))
|
||||||
|
buff.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
func TestSpecialVars(t *testing.T) {
|
func TestSpecialVars(t *testing.T) {
|
||||||
const dir = "testdata/special_vars"
|
const dir = "testdata/special_vars"
|
||||||
const subdir = "testdata/special_vars/subdir"
|
const subdir = "testdata/special_vars/subdir"
|
||||||
@@ -812,7 +864,8 @@ func TestListDescInterpolation(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Contains(t, buff.String(), "bar")
|
assert.Contains(t, buff.String(), "foo-var")
|
||||||
|
assert.Contains(t, buff.String(), "bar-var")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStatusVariables(t *testing.T) {
|
func TestStatusVariables(t *testing.T) {
|
||||||
@@ -1028,6 +1081,107 @@ func TestIncludesMultiLevel(t *testing.T) {
|
|||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIncludesRemote(t *testing.T) {
|
||||||
|
enableExperimentForTest(t, &experiments.RemoteTaskfiles, "1")
|
||||||
|
|
||||||
|
dir := "testdata/includes_remote"
|
||||||
|
|
||||||
|
srv := httptest.NewServer(http.FileServer(http.Dir(dir)))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
tcs := []struct {
|
||||||
|
firstRemote string
|
||||||
|
secondRemote string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
firstRemote: srv.URL + "/first/Taskfile.yml",
|
||||||
|
secondRemote: srv.URL + "/first/second/Taskfile.yml",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
firstRemote: srv.URL + "/first/Taskfile.yml",
|
||||||
|
secondRemote: "./second/Taskfile.yml",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks := []string{
|
||||||
|
"first:write-file",
|
||||||
|
"first:second:write-file",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range tcs {
|
||||||
|
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||||
|
t.Setenv("FIRST_REMOTE_URL", tc.firstRemote)
|
||||||
|
t.Setenv("SECOND_REMOTE_URL", tc.secondRemote)
|
||||||
|
|
||||||
|
var buff SyncBuffer
|
||||||
|
|
||||||
|
executors := []struct {
|
||||||
|
name string
|
||||||
|
executor *task.Executor
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "online, always download",
|
||||||
|
executor: &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
Insecure: true,
|
||||||
|
Logger: &logger.Logger{Stdout: &buff, Stderr: &buff, Verbose: true},
|
||||||
|
|
||||||
|
// Without caching
|
||||||
|
AssumeYes: true,
|
||||||
|
Download: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "offline, use cache",
|
||||||
|
executor: &task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
Insecure: true,
|
||||||
|
Logger: &logger.Logger{Stdout: &buff, Stderr: &buff, Verbose: true},
|
||||||
|
|
||||||
|
// With caching
|
||||||
|
AssumeYes: false,
|
||||||
|
Download: false,
|
||||||
|
Offline: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for j, e := range executors {
|
||||||
|
t.Run(fmt.Sprint(j), func(t *testing.T) {
|
||||||
|
require.NoError(t, e.executor.Setup())
|
||||||
|
|
||||||
|
for k, task := range tasks {
|
||||||
|
t.Run(task, func(t *testing.T) {
|
||||||
|
expectedContent := fmt.Sprint(rand.Int64())
|
||||||
|
t.Setenv("CONTENT", expectedContent)
|
||||||
|
|
||||||
|
outputFile := fmt.Sprintf("%d.%d.txt", i, k)
|
||||||
|
t.Setenv("OUTPUT_FILE", outputFile)
|
||||||
|
|
||||||
|
path := filepath.Join(dir, outputFile)
|
||||||
|
require.NoError(t, os.RemoveAll(path))
|
||||||
|
|
||||||
|
require.NoError(t, e.executor.Run(context.Background(), &ast.Call{Task: task}))
|
||||||
|
|
||||||
|
actualContent, err := os.ReadFile(path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedContent, strings.TrimSpace(string(actualContent)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("\noutput:\n", buff.buf.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIncludeCycle(t *testing.T) {
|
func TestIncludeCycle(t *testing.T) {
|
||||||
const dir = "testdata/includes_cycle"
|
const dir = "testdata/includes_cycle"
|
||||||
|
|
||||||
@@ -1072,6 +1226,88 @@ func TestIncludesEmptyMain(t *testing.T) {
|
|||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIncludesHttp(t *testing.T) {
|
||||||
|
enableExperimentForTest(t, &experiments.RemoteTaskfiles, "1")
|
||||||
|
|
||||||
|
dir, err := filepath.Abs("testdata/includes_http")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
srv := httptest.NewServer(http.FileServer(http.Dir(dir)))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
// This test fills the .task/remote directory with cache entries because the include URL
|
||||||
|
// is different on every test due to the dynamic nature of the TCP port in srv.URL
|
||||||
|
if err := os.RemoveAll(filepath.Join(dir, ".task")); err != nil {
|
||||||
|
t.Logf("error cleaning up: %s", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
taskfiles, err := fs.Glob(os.DirFS(dir), "root-taskfile-*.yml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
remotes := []struct {
|
||||||
|
name string
|
||||||
|
root string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "local",
|
||||||
|
root: ".",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-remote",
|
||||||
|
root: srv.URL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, taskfile := range taskfiles {
|
||||||
|
t.Run(taskfile, func(t *testing.T) {
|
||||||
|
for _, remote := range remotes {
|
||||||
|
t.Run(remote.name, func(t *testing.T) {
|
||||||
|
t.Setenv("INCLUDE_ROOT", remote.root)
|
||||||
|
entrypoint := filepath.Join(dir, taskfile)
|
||||||
|
|
||||||
|
var buff SyncBuffer
|
||||||
|
e := task.Executor{
|
||||||
|
Entrypoint: entrypoint,
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
Insecure: true,
|
||||||
|
Download: true,
|
||||||
|
AssumeYes: true,
|
||||||
|
Logger: &logger.Logger{Stdout: &buff, Stderr: &buff, Verbose: true},
|
||||||
|
Timeout: time.Minute,
|
||||||
|
}
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
defer func() { t.Log("output:", buff.buf.String()) }()
|
||||||
|
|
||||||
|
tcs := []struct {
|
||||||
|
name, dir string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "second-with-dir-1:third-with-dir-1:default",
|
||||||
|
dir: filepath.Join(dir, "dir-1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "second-with-dir-1:third-with-dir-2:default",
|
||||||
|
dir: filepath.Join(dir, "dir-2"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
task, err := e.CompiledTask(&ast.Call{Task: tc.name})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.dir, task.Dir)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIncludesDependencies(t *testing.T) {
|
func TestIncludesDependencies(t *testing.T) {
|
||||||
tt := fileContentTest{
|
tt := fileContentTest{
|
||||||
Dir: "testdata/includes_deps",
|
Dir: "testdata/includes_deps",
|
||||||
@@ -1216,6 +1452,45 @@ func TestIncludesInternal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIncludesFlatten(t *testing.T) {
|
||||||
|
const dir = "testdata/includes_flatten"
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
taskfile string
|
||||||
|
task string
|
||||||
|
expectedErr bool
|
||||||
|
expectedOutput string
|
||||||
|
}{
|
||||||
|
{name: "included flatten", taskfile: "Taskfile.yml", task: "gen", expectedOutput: "gen from included\n"},
|
||||||
|
{name: "included flatten with default", taskfile: "Taskfile.yml", task: "default", expectedOutput: "default from included flatten\n"},
|
||||||
|
{name: "included flatten can call entrypoint tasks", taskfile: "Taskfile.yml", task: "from_entrypoint", expectedOutput: "from entrypoint\n"},
|
||||||
|
{name: "included flatten with deps", taskfile: "Taskfile.yml", task: "with_deps", expectedOutput: "gen from included\nwith_deps from included\n"},
|
||||||
|
{name: "included flatten nested", taskfile: "Taskfile.yml", task: "from_nested", expectedOutput: "from nested\n"},
|
||||||
|
{name: "included flatten multiple same task", taskfile: "Taskfile.multiple.yml", task: "gen", expectedErr: true, expectedOutput: "task: Found multiple tasks (gen) included by \"included\"\""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Entrypoint: dir + "/" + test.taskfile,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
Silent: true,
|
||||||
|
}
|
||||||
|
err := e.Setup()
|
||||||
|
if test.expectedErr {
|
||||||
|
assert.EqualError(t, err, test.expectedOutput)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
_ = e.Run(context.Background(), &ast.Call{Task: test.task})
|
||||||
|
assert.Equal(t, test.expectedOutput, buff.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIncludesInterpolation(t *testing.T) {
|
func TestIncludesInterpolation(t *testing.T) {
|
||||||
const dir = "testdata/includes_interpolation"
|
const dir = "testdata/includes_interpolation"
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -1604,6 +1879,18 @@ func TestDotenvHasEnvVarInPath(t *testing.T) {
|
|||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTaskDotenvParseErrorMessage(t *testing.T) {
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: "testdata/dotenv/parse_error",
|
||||||
|
}
|
||||||
|
|
||||||
|
path, _ := filepath.Abs(filepath.Join(e.Dir, ".env-with-error"))
|
||||||
|
expected := fmt.Sprintf("error reading env file %s:", path)
|
||||||
|
|
||||||
|
err := e.Setup()
|
||||||
|
require.ErrorContains(t, err, expected)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTaskDotenv(t *testing.T) {
|
func TestTaskDotenv(t *testing.T) {
|
||||||
tt := fileContentTest{
|
tt := fileContentTest{
|
||||||
Dir: "testdata/dotenv_task/default",
|
Dir: "testdata/dotenv_task/default",
|
||||||
@@ -1724,6 +2011,34 @@ task-1 ran successfully
|
|||||||
assert.Contains(t, buff.String(), expectedOutputOrder)
|
assert.Contains(t, buff.String(), expectedOutputOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExitCodeZero(t *testing.T) {
|
||||||
|
const dir = "testdata/exit_code"
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "exit-zero"}))
|
||||||
|
assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=", strings.TrimSpace(buff.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExitCodeOne(t *testing.T) {
|
||||||
|
const dir = "testdata/exit_code"
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "exit-one"}))
|
||||||
|
assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=1", strings.TrimSpace(buff.String()))
|
||||||
|
}
|
||||||
|
|
||||||
func TestIgnoreNilElements(t *testing.T) {
|
func TestIgnoreNilElements(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -2295,6 +2610,10 @@ func TestForCmds(t *testing.T) {
|
|||||||
name: "loop-explicit",
|
name: "loop-explicit",
|
||||||
expectedOutput: "a\nb\nc\n",
|
expectedOutput: "a\nb\nc\n",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "loop-matrix",
|
||||||
|
expectedOutput: "windows/amd64\nwindows/arm64\nlinux/amd64\nlinux/arm64\ndarwin/amd64\ndarwin/arm64\n",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "loop-sources",
|
name: "loop-sources",
|
||||||
expectedOutput: "bar\nfoo\n",
|
expectedOutput: "bar\nfoo\n",
|
||||||
@@ -2352,6 +2671,17 @@ func TestForDeps(t *testing.T) {
|
|||||||
name: "loop-explicit",
|
name: "loop-explicit",
|
||||||
expectedOutputContains: []string{"a\n", "b\n", "c\n"},
|
expectedOutputContains: []string{"a\n", "b\n", "c\n"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "loop-matrix",
|
||||||
|
expectedOutputContains: []string{
|
||||||
|
"windows/amd64\n",
|
||||||
|
"windows/arm64\n",
|
||||||
|
"linux/amd64\n",
|
||||||
|
"linux/arm64\n",
|
||||||
|
"darwin/amd64\n",
|
||||||
|
"darwin/arm64\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "loop-sources",
|
name: "loop-sources",
|
||||||
expectedOutputContains: []string{"bar\n", "foo\n"},
|
expectedOutputContains: []string{"bar\n", "foo\n"},
|
||||||
@@ -2392,6 +2722,8 @@ func TestForDeps(t *testing.T) {
|
|||||||
Stderr: &buff,
|
Stderr: &buff,
|
||||||
Silent: true,
|
Silent: true,
|
||||||
Force: true,
|
Force: true,
|
||||||
|
// Force output of each dep to be grouped together to prevent interleaving
|
||||||
|
OutputStyle: ast.Output{Name: "group"},
|
||||||
}
|
}
|
||||||
require.NoError(t, e.Setup())
|
require.NoError(t, e.Setup())
|
||||||
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.name}))
|
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.name}))
|
||||||
@@ -2506,3 +2838,18 @@ func TestReference(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enableExperimentForTest enables the experiment behind pointer e for the duration of test t and sub-tests,
|
||||||
|
// with the experiment being restored to its previous state when tests complete.
|
||||||
|
//
|
||||||
|
// Typically experiments are controlled via TASK_X_ env vars, but we cannot use those in tests
|
||||||
|
// because the experiment settings are parsed during experiments.init(), before any tests run.
|
||||||
|
func enableExperimentForTest(t *testing.T, e *experiments.Experiment, val string) {
|
||||||
|
prev := *e
|
||||||
|
*e = experiments.Experiment{
|
||||||
|
Name: prev.Name,
|
||||||
|
Enabled: true,
|
||||||
|
Value: val,
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { *e = prev })
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ import (
|
|||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/deepcopy"
|
"github.com/go-task/task/v3/internal/deepcopy"
|
||||||
|
"github.com/go-task/task/v3/internal/omap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type For struct {
|
type For struct {
|
||||||
From string
|
From string
|
||||||
List []any
|
List []any
|
||||||
Var string
|
Matrix omap.OrderedMap[string, []any]
|
||||||
Split string
|
Var string
|
||||||
As string
|
Split string
|
||||||
|
As string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *For) UnmarshalYAML(node *yaml.Node) error {
|
func (f *For) UnmarshalYAML(node *yaml.Node) error {
|
||||||
@@ -36,16 +38,21 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
|
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
var forStruct struct {
|
var forStruct struct {
|
||||||
Var string
|
Matrix omap.OrderedMap[string, []any]
|
||||||
Split string
|
Var string
|
||||||
As string
|
Split string
|
||||||
|
As string
|
||||||
}
|
}
|
||||||
if err := node.Decode(&forStruct); err != nil {
|
if err := node.Decode(&forStruct); err != nil {
|
||||||
return errors.NewTaskfileDecodeError(err, node)
|
return errors.NewTaskfileDecodeError(err, node)
|
||||||
}
|
}
|
||||||
if forStruct.Var == "" {
|
if forStruct.Var == "" && forStruct.Matrix.Len() == 0 {
|
||||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in for")
|
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.Var = forStruct.Var
|
||||||
f.Split = forStruct.Split
|
f.Split = forStruct.Split
|
||||||
f.As = forStruct.As
|
f.As = forStruct.As
|
||||||
@@ -60,10 +67,11 @@ func (f *For) DeepCopy() *For {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &For{
|
return &For{
|
||||||
From: f.From,
|
From: f.From,
|
||||||
List: deepcopy.Slice(f.List),
|
List: deepcopy.Slice(f.List),
|
||||||
Var: f.Var,
|
Matrix: f.Matrix.DeepCopy(),
|
||||||
Split: f.Split,
|
Var: f.Var,
|
||||||
As: f.As,
|
Split: f.Split,
|
||||||
|
As: f.As,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type Include struct {
|
|||||||
Aliases []string
|
Aliases []string
|
||||||
AdvancedImport bool
|
AdvancedImport bool
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
|
Flatten bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Includes represents information about included tasksfiles
|
// Includes represents information about included tasksfiles
|
||||||
@@ -81,6 +82,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
Dir string
|
Dir string
|
||||||
Optional bool
|
Optional bool
|
||||||
Internal bool
|
Internal bool
|
||||||
|
Flatten bool
|
||||||
Aliases []string
|
Aliases []string
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
}
|
}
|
||||||
@@ -94,6 +96,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
include.Aliases = includedTaskfile.Aliases
|
include.Aliases = includedTaskfile.Aliases
|
||||||
include.AdvancedImport = true
|
include.AdvancedImport = true
|
||||||
include.Vars = includedTaskfile.Vars
|
include.Vars = includedTaskfile.Vars
|
||||||
|
include.Flatten = includedTaskfile.Flatten
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,5 +117,6 @@ func (include *Include) DeepCopy() *Include {
|
|||||||
Internal: include.Internal,
|
Internal: include.Internal,
|
||||||
AdvancedImport: include.AdvancedImport,
|
AdvancedImport: include.AdvancedImport,
|
||||||
Vars: include.Vars.DeepCopy(),
|
Vars: include.Vars.DeepCopy(),
|
||||||
|
Flatten: include.Flatten,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
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
|
// Requires represents a set of required variables necessary for a task to run
|
||||||
type Requires struct {
|
type Requires struct {
|
||||||
Vars []string
|
Vars []*VarsWithValidation
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Requires) DeepCopy() *Requires {
|
func (r *Requires) DeepCopy() *Requires {
|
||||||
@@ -16,3 +21,47 @@ func (r *Requires) DeepCopy() *Requires {
|
|||||||
Vars: deepcopy.Slice(r.Vars),
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type Task struct {
|
|||||||
Deps []*Dep
|
Deps []*Dep
|
||||||
Label string
|
Label string
|
||||||
Desc string
|
Desc string
|
||||||
Prompt string
|
Prompt Prompt
|
||||||
Summary string
|
Summary string
|
||||||
Requires *Requires
|
Requires *Requires
|
||||||
Aliases []string
|
Aliases []string
|
||||||
@@ -115,7 +115,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
Deps []*Dep
|
Deps []*Dep
|
||||||
Label string
|
Label string
|
||||||
Desc string
|
Desc string
|
||||||
Prompt string
|
Prompt Prompt
|
||||||
Summary string
|
Summary string
|
||||||
Aliases []string
|
Aliases []string
|
||||||
Sources []*Glob
|
Sources []*Glob
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
|||||||
}
|
}
|
||||||
t1.Vars.Merge(t2.Vars, include)
|
t1.Vars.Merge(t2.Vars, include)
|
||||||
t1.Env.Merge(t2.Env, include)
|
t1.Env.Merge(t2.Env, include)
|
||||||
t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
|
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ast
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
@@ -46,43 +47,48 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
|
|||||||
return matchingTasks
|
return matchingTasks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) {
|
func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) error {
|
||||||
_ = t2.Range(func(name string, v *Task) error {
|
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
|
// We do a deep copy of the task struct here to ensure that no data can
|
||||||
// be changed elsewhere once the taskfile is merged.
|
// be changed elsewhere once the taskfile is merged.
|
||||||
task := v.DeepCopy()
|
task := v.DeepCopy()
|
||||||
|
|
||||||
// Set the task to internal if EITHER the included task or the included
|
// Set the task to internal if EITHER the included task or the included
|
||||||
// taskfile are marked as internal
|
// taskfile are marked as internal
|
||||||
task.Internal = task.Internal || (include != nil && include.Internal)
|
task.Internal = task.Internal || (include != nil && include.Internal)
|
||||||
|
taskName := name
|
||||||
// Add namespaces to task dependencies
|
if !include.Flatten {
|
||||||
for _, dep := range task.Deps {
|
// Add namespaces to task dependencies
|
||||||
if dep != nil && dep.Task != "" {
|
for _, dep := range task.Deps {
|
||||||
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
|
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
if include.AdvancedImport {
|
||||||
@@ -94,25 +100,29 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) {
|
|||||||
task.IncludedTaskfileVars = includedTaskfileVars.DeepCopy()
|
task.IncludedTaskfileVars = includedTaskfileVars.DeepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t1.Get(taskName) != nil {
|
||||||
|
return &errors.TaskNameFlattenConflictError{
|
||||||
|
TaskName: taskName,
|
||||||
|
Include: include.Namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
// Add the task to the merged taskfile
|
// Add the task to the merged taskfile
|
||||||
taskNameWithNamespace := taskNameWithNamespace(name, include.Namespace)
|
t1.Set(taskName, task)
|
||||||
task.Namespace = include.Namespace
|
|
||||||
task.Task = taskNameWithNamespace
|
|
||||||
t1.Set(taskNameWithNamespace, task)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
// If the included Taskfile has a default task and the parent namespace has
|
// If the included Taskfile has a default task, being not flattened and the parent namespace has
|
||||||
// no task with a matching name, we can add an alias so that the user can
|
// 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
|
// run the included Taskfile's default task without specifying its full
|
||||||
// name. If the parent namespace has aliases, we add another alias for each
|
// name. If the parent namespace has aliases, we add another alias for each
|
||||||
// of them.
|
// of them.
|
||||||
if t2.Get("default") != nil && t1.Get(include.Namespace) == nil {
|
if t2.Get("default") != nil && t1.Get(include.Namespace) == nil && !include.Flatten {
|
||||||
defaultTaskName := fmt.Sprintf("%s:default", include.Namespace)
|
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.Namespace)
|
||||||
t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Aliases...)
|
t1.Get(defaultTaskName).Aliases = slices.Concat(t1.Get(defaultTaskName).Aliases, include.Aliases)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
|
func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type Vars struct {
|
|||||||
func (vs *Vars) ToCacheMap() (m map[string]any) {
|
func (vs *Vars) ToCacheMap() (m map[string]any) {
|
||||||
m = make(map[string]any, vs.Len())
|
m = make(map[string]any, vs.Len())
|
||||||
_ = vs.Range(func(k string, v Var) error {
|
_ = vs.Range(func(k string, v Var) error {
|
||||||
if v.Sh != "" {
|
if v.Sh != nil && *v.Sh != "" {
|
||||||
// Dynamic variable is not yet resolved; trigger
|
// Dynamic variable is not yet resolved; trigger
|
||||||
// <no value> to be used in templates.
|
// <no value> to be used in templates.
|
||||||
return nil
|
return nil
|
||||||
@@ -81,7 +81,7 @@ func (vs *Vars) DeepCopy() *Vars {
|
|||||||
type Var struct {
|
type Var struct {
|
||||||
Value any
|
Value any
|
||||||
Live any
|
Live any
|
||||||
Sh string
|
Sh *string
|
||||||
Ref string
|
Ref string
|
||||||
Dir string
|
Dir string
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
// If the value is a string and it starts with $, then it's a shell command
|
// 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 := value.(string); ok {
|
||||||
if str, ok = strings.CutPrefix(str, "$"); ok {
|
if str, ok = strings.CutPrefix(str, "$"); ok {
|
||||||
v.Sh = str
|
v.Sh = &str
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if str, ok = strings.CutPrefix(str, "#"); ok {
|
if str, ok = strings.CutPrefix(str, "#"); ok {
|
||||||
@@ -118,7 +118,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
switch key {
|
switch key {
|
||||||
case "sh", "ref", "map":
|
case "sh", "ref", "map":
|
||||||
var m struct {
|
var m struct {
|
||||||
Sh string
|
Sh *string
|
||||||
Ref string
|
Ref string
|
||||||
Map any
|
Map any
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
switch key {
|
switch key {
|
||||||
case "sh", "ref":
|
case "sh", "ref":
|
||||||
var m struct {
|
var m struct {
|
||||||
Sh string
|
Sh *string
|
||||||
Ref string
|
Ref string
|
||||||
}
|
}
|
||||||
if err := node.Decode(&m); err != nil {
|
if err := node.Decode(&m); err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package taskfile
|
package taskfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
@@ -37,7 +38,7 @@ func Dotenv(c *compiler.Compiler, tf *ast.Taskfile, dir string) (*ast.Vars, erro
|
|||||||
|
|
||||||
envs, err := godotenv.Read(dotEnvPath)
|
envs, err := godotenv.Read(dotEnvPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("error reading env file %s: %w", dotEnvPath, err)
|
||||||
}
|
}
|
||||||
for key, value := range envs {
|
for key, value := range envs {
|
||||||
if ok := env.Exists(key); !ok {
|
if ok := env.Exists(key); !ok {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
giturls "github.com/whilp/git-urls"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/experiments"
|
"github.com/go-task/task/v3/internal/experiments"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
@@ -48,24 +50,39 @@ func NewNode(
|
|||||||
) (Node, error) {
|
) (Node, error) {
|
||||||
var node Node
|
var node Node
|
||||||
var err error
|
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":
|
case "http", "https":
|
||||||
node, err = NewHTTPNode(l, entrypoint, dir, insecure, timeout, opts...)
|
node, err = NewHTTPNode(l, entrypoint, dir, insecure, timeout, opts...)
|
||||||
default:
|
default:
|
||||||
// If no other scheme matches, we assume it's a file
|
|
||||||
node, err = NewFileNode(l, entrypoint, dir, opts...)
|
node, err = NewFileNode(l, entrypoint, dir, opts...)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Remote() && !experiments.RemoteTaskfiles.Enabled {
|
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 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
|
return node, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getScheme(uri string) string {
|
func getScheme(uri string) (string, error) {
|
||||||
if i := strings.Index(uri, "://"); i != -1 {
|
u, err := giturls.Parse(uri)
|
||||||
return uri[:i]
|
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 {
|
func getDefaultDir(entrypoint, dir string) string {
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) {
|
|||||||
if strings.Contains(entrypoint, "://") {
|
if strings.Contains(entrypoint, "://") {
|
||||||
return entrypoint, nil
|
return entrypoint, nil
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(entrypoint, "git") {
|
||||||
|
return entrypoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
path, err := execext.Expand(entrypoint)
|
path, err := execext.Expand(entrypoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
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"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
giturls "github.com/whilp/git-urls"
|
||||||
|
|
||||||
|
"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))
|
||||||
|
}
|
||||||
75
taskfile/node_git_test.go
Normal file
75
taskfile/node_git_test.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package taskfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGitNode_ssh(t *testing.T) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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.
|
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
|
||||||
type HTTPNode struct {
|
type HTTPNode struct {
|
||||||
*BaseNode
|
*BaseNode
|
||||||
URL *url.URL
|
URL *url.URL
|
||||||
|
logger *logger.Logger
|
||||||
|
timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPNode(
|
func NewHTTPNode(
|
||||||
@@ -36,18 +38,12 @@ func NewHTTPNode(
|
|||||||
if url.Scheme == "http" && !insecure {
|
if url.Scheme == "http" && !insecure {
|
||||||
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
|
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{
|
return &HTTPNode{
|
||||||
BaseNode: base,
|
BaseNode: base,
|
||||||
URL: url,
|
URL: url,
|
||||||
|
timeout: timeout,
|
||||||
|
logger: l,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +56,11 @@ func (node *HTTPNode) Remote() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
|
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)
|
req, err := http.NewRequest("GET", node.URL.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
|
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))
|
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||||
if err != nil {
|
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()}
|
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, errors.TaskfileFetchFailedError{
|
return nil, errors.TaskfileFetchFailedError{
|
||||||
URI: node.URL.String(),
|
URI: node.URL.String(),
|
||||||
@@ -107,8 +110,12 @@ func (node *HTTPNode) ResolveDir(dir string) (string, error) {
|
|||||||
|
|
||||||
// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
|
// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
|
||||||
// This means that files are included relative to one another
|
// This means that files are included relative to one another
|
||||||
entrypointDir := filepath.Dir(node.Dir())
|
parent := node.Dir()
|
||||||
return filepathext.SmartJoin(entrypointDir, path), nil
|
if node.Parent() != nil {
|
||||||
|
parent = node.Parent().Dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepathext.SmartJoin(parent, path), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *HTTPNode) FilenameAndLastDir() (string, string) {
|
func (node *HTTPNode) FilenameAndLastDir() (string, string) {
|
||||||
|
|||||||
22
taskfile/node_test.go
Normal file
22
taskfile/node_test.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package taskfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScheme(t *testing.T) {
|
||||||
|
scheme, err := getScheme("https://github.com/foo/bar.git")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "git", scheme)
|
||||||
|
scheme, err = getScheme("https://github.com/foo/bar.git?ref=v1//taskfile/common.yml")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "git", scheme)
|
||||||
|
scheme, err = getScheme("git@github.com:foo/bar.git?ref=main//Taskfile.yml")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "git", scheme)
|
||||||
|
scheme, err = getScheme("https://github.com/foo/common.yml")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "https", scheme)
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dominikbraun/graph"
|
"github.com/dominikbraun/graph"
|
||||||
@@ -30,14 +31,15 @@ Continue?`
|
|||||||
// A Reader will recursively read Taskfiles from a given source using a directed
|
// A Reader will recursively read Taskfiles from a given source using a directed
|
||||||
// acyclic graph (DAG).
|
// acyclic graph (DAG).
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
graph *ast.TaskfileGraph
|
graph *ast.TaskfileGraph
|
||||||
node Node
|
node Node
|
||||||
insecure bool
|
insecure bool
|
||||||
download bool
|
download bool
|
||||||
offline bool
|
offline bool
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
tempDir string
|
tempDir string
|
||||||
logger *logger.Logger
|
logger *logger.Logger
|
||||||
|
promptMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReader(
|
func NewReader(
|
||||||
@@ -50,14 +52,15 @@ func NewReader(
|
|||||||
logger *logger.Logger,
|
logger *logger.Logger,
|
||||||
) *Reader {
|
) *Reader {
|
||||||
return &Reader{
|
return &Reader{
|
||||||
graph: ast.NewTaskfileGraph(),
|
graph: ast.NewTaskfileGraph(),
|
||||||
node: node,
|
node: node,
|
||||||
insecure: insecure,
|
insecure: insecure,
|
||||||
download: download,
|
download: download,
|
||||||
offline: offline,
|
offline: offline,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
tempDir: tempDir,
|
tempDir: tempDir,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
promptMutex: sync.Mutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +112,7 @@ func (r *Reader) include(node Node) error {
|
|||||||
Dir: templater.Replace(include.Dir, cache),
|
Dir: templater.Replace(include.Dir, cache),
|
||||||
Optional: include.Optional,
|
Optional: include.Optional,
|
||||||
Internal: include.Internal,
|
Internal: include.Internal,
|
||||||
|
Flatten: include.Flatten,
|
||||||
Aliases: include.Aliases,
|
Aliases: include.Aliases,
|
||||||
AdvancedImport: include.AdvancedImport,
|
AdvancedImport: include.AdvancedImport,
|
||||||
Vars: include.Vars,
|
Vars: include.Vars,
|
||||||
@@ -180,87 +184,9 @@ func (r *Reader) include(node Node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
|
func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
|
||||||
var b []byte
|
b, err := r.loadNodeContent(node)
|
||||||
var err error
|
if err != nil {
|
||||||
var cache *Cache
|
return nil, err
|
||||||
|
|
||||||
if node.Remote() {
|
|
||||||
cache, err = NewCache(r.tempDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the file is remote and we're in offline mode, check if we have a cached copy
|
|
||||||
if node.Remote() && r.offline {
|
|
||||||
if b, err = cache.read(node); errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil, &errors.TaskfileCacheNotFoundError{URI: node.Location()}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Fetched cached copy\n", node.Location())
|
|
||||||
} else {
|
|
||||||
|
|
||||||
downloaded := false
|
|
||||||
ctx, cf := context.WithTimeout(context.Background(), r.timeout)
|
|
||||||
defer cf()
|
|
||||||
|
|
||||||
// Read the file
|
|
||||||
b, err = node.Read(ctx)
|
|
||||||
// If we timed out then we likely have a network issue
|
|
||||||
if node.Remote() && errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
|
||||||
// If a download was requested, then we can't use a cached copy
|
|
||||||
if r.download {
|
|
||||||
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: r.timeout}
|
|
||||||
}
|
|
||||||
// Search for any cached copies
|
|
||||||
if b, err = cache.read(node); errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: r.timeout, CheckedCache: true}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Network timeout. Fetched cached copy\n", node.Location())
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
downloaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the node was remote, we need to check the checksum
|
|
||||||
if node.Remote() && downloaded {
|
|
||||||
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Fetched remote copy\n", node.Location())
|
|
||||||
|
|
||||||
// Get the checksums
|
|
||||||
checksum := checksum(b)
|
|
||||||
cachedChecksum := cache.readChecksum(node)
|
|
||||||
|
|
||||||
var prompt string
|
|
||||||
if cachedChecksum == "" {
|
|
||||||
// If the checksum doesn't exist, prompt the user to continue
|
|
||||||
prompt = fmt.Sprintf(taskfileUntrustedPrompt, node.Location())
|
|
||||||
} else if checksum != cachedChecksum {
|
|
||||||
// If there is a cached hash, but it doesn't match the expected hash, prompt the user to continue
|
|
||||||
prompt = fmt.Sprintf(taskfileChangedPrompt, node.Location())
|
|
||||||
}
|
|
||||||
if prompt != "" {
|
|
||||||
if err := r.logger.Prompt(logger.Yellow, prompt, "n", "y", "yes"); err != nil {
|
|
||||||
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the hash has changed (or is new)
|
|
||||||
if checksum != cachedChecksum {
|
|
||||||
// Store the checksum
|
|
||||||
if err := cache.writeChecksum(node, checksum); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Cache the file
|
|
||||||
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Caching downloaded file\n", node.Location())
|
|
||||||
if err = cache.write(node, b); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tf ast.Taskfile
|
var tf ast.Taskfile
|
||||||
@@ -293,3 +219,93 @@ func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
|
|||||||
|
|
||||||
return &tf, nil
|
return &tf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Reader) loadNodeContent(node Node) ([]byte, error) {
|
||||||
|
if !node.Remote() {
|
||||||
|
ctx, cf := context.WithTimeout(context.Background(), r.timeout)
|
||||||
|
defer cf()
|
||||||
|
return node.Read(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
cache, err := NewCache(r.tempDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.offline {
|
||||||
|
// In offline mode try to use cached copy
|
||||||
|
cached, err := cache.read(node)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, &errors.TaskfileCacheNotFoundError{URI: node.Location()}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Fetched cached copy\n", node.Location())
|
||||||
|
|
||||||
|
return cached, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cf := context.WithTimeout(context.Background(), r.timeout)
|
||||||
|
defer cf()
|
||||||
|
|
||||||
|
b, err := node.Read(ctx)
|
||||||
|
if errors.Is(err, &errors.TaskfileNetworkTimeoutError{}) {
|
||||||
|
// If we timed out then we likely have a network issue
|
||||||
|
|
||||||
|
// If a download was requested, then we can't use a cached copy
|
||||||
|
if r.download {
|
||||||
|
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: r.timeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for any cached copies
|
||||||
|
cached, err := cache.read(node)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: r.timeout, CheckedCache: true}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Network timeout. Fetched cached copy\n", node.Location())
|
||||||
|
|
||||||
|
return cached, nil
|
||||||
|
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Fetched remote copy\n", node.Location())
|
||||||
|
|
||||||
|
// Get the checksums
|
||||||
|
checksum := checksum(b)
|
||||||
|
cachedChecksum := cache.readChecksum(node)
|
||||||
|
|
||||||
|
var prompt string
|
||||||
|
if cachedChecksum == "" {
|
||||||
|
// If the checksum doesn't exist, prompt the user to continue
|
||||||
|
prompt = fmt.Sprintf(taskfileUntrustedPrompt, node.Location())
|
||||||
|
} else if checksum != cachedChecksum {
|
||||||
|
// If there is a cached hash, but it doesn't match the expected hash, prompt the user to continue
|
||||||
|
prompt = fmt.Sprintf(taskfileChangedPrompt, node.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
if prompt != "" {
|
||||||
|
if err := func() error {
|
||||||
|
r.promptMutex.Lock()
|
||||||
|
defer r.promptMutex.Unlock()
|
||||||
|
return r.logger.Prompt(logger.Yellow, prompt, "n", "y", "yes")
|
||||||
|
}(); err != nil {
|
||||||
|
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the checksum
|
||||||
|
if err := cache.writeChecksum(node, checksum); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the file
|
||||||
|
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Caching downloaded file\n", node.Location())
|
||||||
|
if err = cache.write(node, b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
@@ -40,7 +41,7 @@ var (
|
|||||||
// at the given URL with any of the default Taskfile files names. If any of
|
// at the given URL with any of the default Taskfile files names. If any of
|
||||||
// these match a file, the first matching path will be returned. If no files are
|
// these match a file, the first matching path will be returned. If no files are
|
||||||
// found, an error will be returned.
|
// found, an error will be returned.
|
||||||
func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL) (*url.URL, error) {
|
func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL, timeout time.Duration) (*url.URL, error) {
|
||||||
// Create a new HEAD request for the given URL to check if the resource exists
|
// Create a new HEAD request for the given URL to check if the resource exists
|
||||||
req, err := http.NewRequest("HEAD", u.String(), nil)
|
req, err := http.NewRequest("HEAD", u.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -50,6 +51,9 @@ func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL) (*url.URL,
|
|||||||
// Request the given URL
|
// Request the given URL
|
||||||
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||||
|
return nil, &errors.TaskfileNetworkTimeoutError{URI: u.String(), Timeout: timeout}
|
||||||
|
}
|
||||||
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
|
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|||||||
2
testdata/dotenv/parse_error/.env-with-error
vendored
Normal file
2
testdata/dotenv/parse_error/.env-with-error
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#intentional parse error
|
||||||
|
SOME_VAR
|
||||||
8
testdata/dotenv/parse_error/Taskfile.yml
vendored
Normal file
8
testdata/dotenv/parse_error/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
dotenv: ['.env-with-error']
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmd: "true"
|
||||||
|
|
||||||
10
testdata/env/Taskfile.yml
vendored
10
testdata/env/Taskfile.yml
vendored
@@ -8,12 +8,14 @@ env:
|
|||||||
FOO: foo
|
FOO: foo
|
||||||
BAR: bar
|
BAR: bar
|
||||||
BAZ: "{{.BAZ}}"
|
BAZ: "{{.BAZ}}"
|
||||||
|
QUX: from_taskfile
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
default:
|
default:
|
||||||
cmds:
|
cmds:
|
||||||
- task: local
|
- task: local
|
||||||
- task: global
|
- task: global
|
||||||
|
- task: not-overriden
|
||||||
- task: multiple_type
|
- task: multiple_type
|
||||||
|
|
||||||
local:
|
local:
|
||||||
@@ -40,3 +42,11 @@ tasks:
|
|||||||
BAZ: 1.1
|
BAZ: 1.1
|
||||||
cmds:
|
cmds:
|
||||||
- echo "FOO='$FOO' BAR='$BAR' BAZ='$BAZ'" > multiple_type.txt
|
- echo "FOO='$FOO' BAR='$BAR' BAZ='$BAZ'" > multiple_type.txt
|
||||||
|
|
||||||
|
not-overriden:
|
||||||
|
cmds:
|
||||||
|
- echo "QUX='$QUX'" > not-overriden.txt
|
||||||
|
|
||||||
|
overriden:
|
||||||
|
cmds:
|
||||||
|
- echo "QUX='$QUX'" > overriden.txt
|
||||||
|
|||||||
25
testdata/exit_code/Taskfile.yml
vendored
Normal file
25
testdata/exit_code/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
silent: true
|
||||||
|
|
||||||
|
vars:
|
||||||
|
PREFIX: EXIT_CODE=
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
exit-zero:
|
||||||
|
vars:
|
||||||
|
FOO: bar
|
||||||
|
DYNAMIC_FOO:
|
||||||
|
sh: echo 'bar'
|
||||||
|
cmds:
|
||||||
|
- defer: echo FOO={{.FOO}} - DYNAMIC_FOO={{.DYNAMIC_FOO}} - {{.PREFIX}}{{.EXIT_CODE}}
|
||||||
|
- exit 0
|
||||||
|
|
||||||
|
exit-one:
|
||||||
|
vars:
|
||||||
|
FOO: bar
|
||||||
|
DYNAMIC_FOO:
|
||||||
|
sh: echo 'bar'
|
||||||
|
cmds:
|
||||||
|
- defer: echo FOO={{.FOO}} - DYNAMIC_FOO={{.DYNAMIC_FOO}} - {{.PREFIX}}{{.EXIT_CODE}}
|
||||||
|
- exit 1
|
||||||
8
testdata/for/cmds/Taskfile.yml
vendored
8
testdata/for/cmds/Taskfile.yml
vendored
@@ -7,6 +7,14 @@ tasks:
|
|||||||
- for: ["a", "b", "c"]
|
- for: ["a", "b", "c"]
|
||||||
cmd: echo "{{.ITEM}}"
|
cmd: echo "{{.ITEM}}"
|
||||||
|
|
||||||
|
loop-matrix:
|
||||||
|
cmds:
|
||||||
|
- for:
|
||||||
|
matrix:
|
||||||
|
OS: ["windows", "linux", "darwin"]
|
||||||
|
ARCH: ["amd64", "arm64"]
|
||||||
|
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
|
||||||
|
|
||||||
# Loop over the task's sources
|
# Loop over the task's sources
|
||||||
loop-sources:
|
loop-sources:
|
||||||
sources:
|
sources:
|
||||||
|
|||||||
10
testdata/for/deps/Taskfile.yml
vendored
10
testdata/for/deps/Taskfile.yml
vendored
@@ -9,6 +9,16 @@ tasks:
|
|||||||
vars:
|
vars:
|
||||||
TEXT: "{{.ITEM}}"
|
TEXT: "{{.ITEM}}"
|
||||||
|
|
||||||
|
loop-matrix:
|
||||||
|
deps:
|
||||||
|
- for:
|
||||||
|
matrix:
|
||||||
|
OS: ["windows", "linux", "darwin"]
|
||||||
|
ARCH: ["amd64", "arm64"]
|
||||||
|
task: echo
|
||||||
|
vars:
|
||||||
|
TEXT: "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
|
||||||
|
|
||||||
# Loop over the task's sources
|
# Loop over the task's sources
|
||||||
loop-sources:
|
loop-sources:
|
||||||
sources:
|
sources:
|
||||||
|
|||||||
1
testdata/includes_flatten/.gitignore
vendored
Normal file
1
testdata/includes_flatten/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.txt
|
||||||
12
testdata/includes_flatten/Taskfile.multiple.yml
vendored
Normal file
12
testdata/includes_flatten/Taskfile.multiple.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
included:
|
||||||
|
taskfile: ./included
|
||||||
|
flatten: true
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
gen:
|
||||||
|
cmds:
|
||||||
|
- echo "gen multiple"
|
||||||
|
|
||||||
3
testdata/includes_flatten/Taskfile.with_default.yml
vendored
Normal file
3
testdata/includes_flatten/Taskfile.with_default.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version: '3'
|
||||||
|
tasks:
|
||||||
|
default: echo "default from included flatten"
|
||||||
15
testdata/includes_flatten/Taskfile.yml
vendored
Normal file
15
testdata/includes_flatten/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
included:
|
||||||
|
taskfile: ./included
|
||||||
|
dir: ./included
|
||||||
|
flatten: true
|
||||||
|
with_default:
|
||||||
|
taskfile: ./Taskfile.with_default.yml
|
||||||
|
flatten: true
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
from_entrypoint: echo "from entrypoint"
|
||||||
|
|
||||||
|
|
||||||
23
testdata/includes_flatten/included/Taskfile.yml
vendored
Normal file
23
testdata/includes_flatten/included/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
nested:
|
||||||
|
taskfile: ../nested
|
||||||
|
flatten: true
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
gen:
|
||||||
|
cmds:
|
||||||
|
- echo "gen from included"
|
||||||
|
|
||||||
|
with_deps:
|
||||||
|
deps:
|
||||||
|
- gen
|
||||||
|
cmds:
|
||||||
|
- echo "with_deps from included"
|
||||||
|
|
||||||
|
|
||||||
|
pwd:
|
||||||
|
desc: Print working directory
|
||||||
|
cmds:
|
||||||
|
- pwd
|
||||||
6
testdata/includes_flatten/nested/Taskfile.yml
vendored
Normal file
6
testdata/includes_flatten/nested/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
from_nested:
|
||||||
|
cmds:
|
||||||
|
- echo "from nested"
|
||||||
9
testdata/includes_http/child-taskfile2.yml
vendored
Normal file
9
testdata/includes_http/child-taskfile2.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
third-with-dir-1:
|
||||||
|
taskfile: "{{.INCLUDE_ROOT}}/child-taskfile3.yml"
|
||||||
|
dir: ./dir-1
|
||||||
|
third-with-dir-2:
|
||||||
|
taskfile: "{{.INCLUDE_ROOT}}/child-taskfile3.yml"
|
||||||
|
dir: ./dir-2
|
||||||
4
testdata/includes_http/child-taskfile3.yml
vendored
Normal file
4
testdata/includes_http/child-taskfile3.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default: "true"
|
||||||
8
testdata/includes_http/root-taskfile-remotefile-empty-dir-1st.yml
vendored
Normal file
8
testdata/includes_http/root-taskfile-remotefile-empty-dir-1st.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
second-no-dir:
|
||||||
|
taskfile: "{{.INCLUDE_ROOT}}/child-taskfile2.yml"
|
||||||
|
second-with-dir-1:
|
||||||
|
taskfile: "{{.INCLUDE_ROOT}}/child-taskfile2.yml"
|
||||||
|
dir: ./dir-1
|
||||||
8
testdata/includes_http/root-taskfile-remotefile-empty-dir-2nd.yml
vendored
Normal file
8
testdata/includes_http/root-taskfile-remotefile-empty-dir-2nd.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
second-with-dir-1:
|
||||||
|
taskfile: "{{.INCLUDE_ROOT}}/child-taskfile2.yml"
|
||||||
|
dir: ./dir-1
|
||||||
|
second-no-dir:
|
||||||
|
taskfile: "{{.INCLUDE_ROOT}}/child-taskfile2.yml"
|
||||||
1
testdata/includes_remote/.gitignore
vendored
Normal file
1
testdata/includes_remote/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.txt
|
||||||
4
testdata/includes_remote/Taskfile.yml
vendored
Normal file
4
testdata/includes_remote/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
first: "{{.FIRST_REMOTE_URL}}"
|
||||||
11
testdata/includes_remote/first/Taskfile.yml
vendored
Normal file
11
testdata/includes_remote/first/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
second: "{{.SECOND_REMOTE_URL}}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
write-file:
|
||||||
|
requires:
|
||||||
|
vars: [CONTENT, OUTPUT_FILE]
|
||||||
|
cmd: |
|
||||||
|
echo "{{.CONTENT}}" > "{{.OUTPUT_FILE}}"
|
||||||
8
testdata/includes_remote/first/second/Taskfile.yml
vendored
Normal file
8
testdata/includes_remote/first/second/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
write-file:
|
||||||
|
requires:
|
||||||
|
vars: [CONTENT, OUTPUT_FILE]
|
||||||
|
cmd: |
|
||||||
|
echo "{{.CONTENT}}" > "{{.OUTPUT_FILE}}"
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
FOO: bar
|
FOO: foo
|
||||||
|
BAR: bar
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
foo:
|
foo:
|
||||||
desc: "task has desc with {{.FOO}} var"
|
desc: "task has desc with {{.FOO}}-var"
|
||||||
|
|
||||||
|
bar:
|
||||||
|
desc: "task has desc with {{.BAR}}-var"
|
||||||
|
|||||||
7
testdata/prompt/Taskfile.yml
vendored
7
testdata/prompt/Taskfile.yml
vendored
@@ -14,3 +14,10 @@ tasks:
|
|||||||
prompt: Do you want to continue?
|
prompt: Do you want to continue?
|
||||||
cmds:
|
cmds:
|
||||||
- echo 'show-prompt'
|
- echo 'show-prompt'
|
||||||
|
|
||||||
|
multi-prompt:
|
||||||
|
prompt:
|
||||||
|
- Do you want to continue?
|
||||||
|
- Are you sure?
|
||||||
|
cmds:
|
||||||
|
- echo 'multi-prompt'
|
||||||
|
|||||||
18
testdata/requires/Taskfile.yml
vendored
Normal file
18
testdata/requires/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
- task: missing-var
|
||||||
|
|
||||||
|
missing-var:
|
||||||
|
requires:
|
||||||
|
vars:
|
||||||
|
- foo
|
||||||
|
cmd: echo "{{.foo}}"
|
||||||
|
|
||||||
|
|
||||||
|
validation-var:
|
||||||
|
requires:
|
||||||
|
vars:
|
||||||
|
- name: foo
|
||||||
|
enum: ['one', 'two']
|
||||||
10
testdata/special_vars/Taskfile.yml
vendored
10
testdata/special_vars/Taskfile.yml
vendored
@@ -6,8 +6,16 @@ includes:
|
|||||||
dir: ./included
|
dir: ./included
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
print-task: echo {{.TASK}}
|
print-task:
|
||||||
|
aliases: [echo-task]
|
||||||
|
cmds:
|
||||||
|
- echo {{.TASK}}
|
||||||
print-root-dir: echo {{.ROOT_DIR}}
|
print-root-dir: echo {{.ROOT_DIR}}
|
||||||
print-taskfile: echo {{.TASKFILE}}
|
print-taskfile: echo {{.TASKFILE}}
|
||||||
print-taskfile-dir: echo {{.TASKFILE_DIR}}
|
print-taskfile-dir: echo {{.TASKFILE_DIR}}
|
||||||
print-task-version: echo {{.TASK_VERSION}}
|
print-task-version: echo {{.TASK_VERSION}}
|
||||||
|
print-task-alias:
|
||||||
|
aliases: [echo-task-alias]
|
||||||
|
cmds:
|
||||||
|
- echo "{{.ALIAS}}"
|
||||||
|
print-task-alias-default: echo "{{.ALIAS}}"
|
||||||
|
|||||||
10
testdata/special_vars/included/Taskfile.yml
vendored
10
testdata/special_vars/included/Taskfile.yml
vendored
@@ -1,8 +1,16 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
print-task: echo {{.TASK}}
|
print-task:
|
||||||
|
aliases: [echo-task]
|
||||||
|
cmds:
|
||||||
|
- echo {{.TASK}}
|
||||||
print-root-dir: echo {{.ROOT_DIR}}
|
print-root-dir: echo {{.ROOT_DIR}}
|
||||||
print-taskfile: echo {{.TASKFILE}}
|
print-taskfile: echo {{.TASKFILE}}
|
||||||
print-taskfile-dir: echo {{.TASKFILE_DIR}}
|
print-taskfile-dir: echo {{.TASKFILE_DIR}}
|
||||||
print-task-version: echo {{.TASK_VERSION}}
|
print-task-version: echo {{.TASK_VERSION}}
|
||||||
|
print-task-alias:
|
||||||
|
aliases: [echo-task-alias]
|
||||||
|
cmds:
|
||||||
|
- echo "{{.ALIAS}}"
|
||||||
|
print-task-alias-default: echo "{{.ALIAS}}"
|
||||||
|
|||||||
59
variables.go
59
variables.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/go-task/task/v3/internal/execext"
|
"github.com/go-task/task/v3/internal/execext"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
"github.com/go-task/task/v3/internal/fingerprint"
|
"github.com/go-task/task/v3/internal/fingerprint"
|
||||||
|
"github.com/go-task/task/v3/internal/omap"
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
@@ -111,7 +112,7 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
if evaluateShVars {
|
if evaluateShVars {
|
||||||
err = new.Env.Range(func(k string, v ast.Var) error {
|
err = new.Env.Range(func(k string, v ast.Var) error {
|
||||||
// If the variable is not dynamic, we can set it and return
|
// If the variable is not dynamic, we can set it and return
|
||||||
if v.Value != nil || v.Sh == "" {
|
if v.Value != nil || v.Sh == nil {
|
||||||
new.Env.Set(k, ast.Var{Value: v.Value})
|
new.Env.Set(k, ast.Var{Value: v.Value})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -161,6 +162,12 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Defer commands are replaced in a lazy manner because
|
||||||
|
// we need to include EXIT_CODE.
|
||||||
|
if cmd.Defer {
|
||||||
|
new.Cmds = append(new.Cmds, cmd.DeepCopy())
|
||||||
|
continue
|
||||||
|
}
|
||||||
newCmd := cmd.DeepCopy()
|
newCmd := cmd.DeepCopy()
|
||||||
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
|
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
|
||||||
newCmd.Task = templater.Replace(cmd.Task, cache)
|
newCmd.Task = templater.Replace(cmd.Task, cache)
|
||||||
@@ -265,9 +272,13 @@ func itemsFromFor(
|
|||||||
) ([]any, []string, error) {
|
) ([]any, []string, error) {
|
||||||
var keys []string // The list of keys to loop over (only if looping over a map)
|
var keys []string // The list of keys to loop over (only if looping over a map)
|
||||||
var values []any // The list of values to loop over
|
var values []any // The list of values to loop over
|
||||||
|
// Get the list from a matrix
|
||||||
|
if f.Matrix.Len() != 0 {
|
||||||
|
return asAnySlice(product(f.Matrix)), nil, nil
|
||||||
|
}
|
||||||
// Get the list from the explicit for list
|
// Get the list from the explicit for list
|
||||||
if f.List != nil && len(f.List) > 0 {
|
if len(f.List) > 0 {
|
||||||
values = f.List
|
return f.List, nil, nil
|
||||||
}
|
}
|
||||||
// Get the list from the task sources
|
// Get the list from the task sources
|
||||||
if f.From == "sources" {
|
if f.From == "sources" {
|
||||||
@@ -290,7 +301,7 @@ func itemsFromFor(
|
|||||||
// If the variable is dynamic, then it hasn't been resolved yet
|
// If the variable is dynamic, then it hasn't been resolved yet
|
||||||
// and we can't use it as a list. This happens when fast compiling a task
|
// and we can't use it as a list. This happens when fast compiling a task
|
||||||
// for use in --list or --list-all etc.
|
// for use in --list or --list-all etc.
|
||||||
if v.Value != nil && v.Sh == "" {
|
if v.Value != nil && v.Sh == nil {
|
||||||
switch value := v.Value.(type) {
|
switch value := v.Value.(type) {
|
||||||
case string:
|
case string:
|
||||||
if f.Split != "" {
|
if f.Split != "" {
|
||||||
@@ -298,6 +309,10 @@ func itemsFromFor(
|
|||||||
} else {
|
} else {
|
||||||
values = asAnySlice(strings.Fields(value))
|
values = asAnySlice(strings.Fields(value))
|
||||||
}
|
}
|
||||||
|
case []string:
|
||||||
|
values = asAnySlice(value)
|
||||||
|
case []int:
|
||||||
|
values = asAnySlice(value)
|
||||||
case []any:
|
case []any:
|
||||||
values = value
|
values = value
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
@@ -316,3 +331,39 @@ func itemsFromFor(
|
|||||||
}
|
}
|
||||||
return values, keys, nil
|
return values, keys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// product generates the cartesian product of the input map of slices.
|
||||||
|
func product(inputMap omap.OrderedMap[string, []any]) []map[string]any {
|
||||||
|
if inputMap.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with an empty product result
|
||||||
|
result := []map[string]any{{}}
|
||||||
|
|
||||||
|
// Iterate over each slice in the slices
|
||||||
|
_ = inputMap.Range(func(key string, slice []any) error {
|
||||||
|
var newResult []map[string]any
|
||||||
|
|
||||||
|
// For each combination in the current result
|
||||||
|
for _, combination := range result {
|
||||||
|
// Append each element from the current slice to the combinations
|
||||||
|
for _, item := range slice {
|
||||||
|
newComb := make(map[string]any, len(combination))
|
||||||
|
// Copy the existing combination
|
||||||
|
for k, v := range combination {
|
||||||
|
newComb[k] = v
|
||||||
|
}
|
||||||
|
// Add the current item with the corresponding key
|
||||||
|
newComb[key] = item
|
||||||
|
newResult = append(newResult, newComb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update result with the new combinations
|
||||||
|
result = newResult
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,15 +50,3 @@ tasks:
|
|||||||
desc: Upgrade Docusaurus
|
desc: Upgrade Docusaurus
|
||||||
cmds:
|
cmds:
|
||||||
- yarn upgrade @docusaurus/core@latest @docusaurus/preset-classic@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest
|
- yarn upgrade @docusaurus/core@latest @docusaurus/preset-classic@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest
|
||||||
|
|
||||||
crowdin:push:
|
|
||||||
desc: Upload source files to a Crowdin project
|
|
||||||
deps: [yarn:install]
|
|
||||||
cmds:
|
|
||||||
- npx crowdin push
|
|
||||||
|
|
||||||
crowdin:pull:
|
|
||||||
desc: Download latest translations from Crowdin to the specified place
|
|
||||||
deps: [yarn:install]
|
|
||||||
cmds:
|
|
||||||
- npx crowdin pull --export-only-approved
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export const GITHUB_URL = 'https://github.com/go-task/task';
|
export const GITHUB_URL = 'https://github.com/go-task/task';
|
||||||
export const TWITTER_URL = 'https://twitter.com/taskfiledev';
|
export const TWITTER_URL = 'https://twitter.com/taskfiledev';
|
||||||
|
export const BLUESKY_URL = 'https://bsky.app/profile/taskfile.dev';
|
||||||
export const MASTODON_URL = 'https://fosstodon.org/@task';
|
export const MASTODON_URL = 'https://fosstodon.org/@task';
|
||||||
export const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
|
export const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
|
||||||
export const STACK_OVERFLOW = 'https://stackoverflow.com/questions/tagged/taskfile';
|
export const STACK_OVERFLOW = 'https://stackoverflow.com/questions/tagged/taskfile';
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
project_id: "574591"
|
|
||||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
|
||||||
preserve_hierarchy: true
|
|
||||||
|
|
||||||
files:
|
|
||||||
- source: /docs/**/*
|
|
||||||
translation: /i18n/%locale%/docusaurus-plugin-content-docs/version-latest/**/%original_file_name%
|
|
||||||
ignore:
|
|
||||||
- /**/*.json
|
|
||||||
|
|
||||||
- source: /blog/**/*
|
|
||||||
translation: /i18n/%locale%/docusaurus-plugin-content-blog/**/%original_file_name%
|
|
||||||
@@ -5,6 +5,80 @@ sidebar_position: 14
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
## v3.38.0 - 2024-06-30
|
||||||
|
|
||||||
- Added `TASK_EXE` special variable (#1616, #1624 by @pd93 and @andreynering).
|
- Added `TASK_EXE` special variable (#1616, #1624 by @pd93 and @andreynering).
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ Some of the work to improve the Task ecosystem is done by the community, be it
|
|||||||
installation methods or integrations with code editor. I (the author) am
|
installation methods or integrations with code editor. I (the author) am
|
||||||
thankful for everyone that helps me to improve the overall experience.
|
thankful for everyone that helps me to improve the overall experience.
|
||||||
|
|
||||||
## Translations
|
|
||||||
|
|
||||||
We use [Crowdin](https://crowdin.com/project/taskfile) to translate our
|
|
||||||
document.
|
|
||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
||||||
Many of our integrations are contributed and maintained by the community. You
|
Many of our integrations are contributed and maintained by the community. You
|
||||||
|
|||||||
25
website/docs/deprecations/completion_scripts.mdx
Normal file
25
website/docs/deprecations/completion_scripts.mdx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
slug: /deprecations/completion-scripts/
|
||||||
|
---
|
||||||
|
|
||||||
|
# Completion Scripts
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
This deprecation breaks the following functionality:
|
||||||
|
|
||||||
|
- Any direct references to the completion scripts in the Task git repository
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Direct use of the completion scripts in the `completion/*` directory of the
|
||||||
|
[github.com/go-task/task][task] Git repository is deprecated. Any shell
|
||||||
|
configuration that directly refers to these scripts will potentially break in
|
||||||
|
the future as the scripts may be moved or deleted entirely. Any configuration
|
||||||
|
should be updated to use the [new method for generating shell
|
||||||
|
completions][completions] instead.
|
||||||
|
|
||||||
|
{/* prettier-ignore-start */}
|
||||||
|
[completions]: ../installation.mdx#setup-completions
|
||||||
|
[task]: https://github.com/go-task/task
|
||||||
|
{/* prettier-ignore-end */}
|
||||||
74
website/docs/experiments/env_precedence.mdx
Normal file
74
website/docs/experiments/env_precedence.mdx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
slug: '/experiments/env-precedence'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Env Precedence (#1038)
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
All experimental features are subject to breaking changes and/or removal _at any
|
||||||
|
time_. We strongly recommend that you do not use these features in a production
|
||||||
|
environment. They are intended for testing and feedback only.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
This experiment breaks the following functionality:
|
||||||
|
|
||||||
|
- environment variable will take precedence over OS environment variables
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
To enable this experiment, set the environment variable:
|
||||||
|
`TASK_X_ENV_PRECEDENCE=1`. Check out [our guide to enabling
|
||||||
|
experiments][enabling-experiments] for more information.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Before this experiment, the OS variable took precedence over the task
|
||||||
|
environment variable. This experiment changes the precedence to make the task
|
||||||
|
environment variable take precedence over the OS variable.
|
||||||
|
|
||||||
|
Consider the following example:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
env:
|
||||||
|
KEY: 'other'
|
||||||
|
cmds:
|
||||||
|
- echo "$KEY"
|
||||||
|
```
|
||||||
|
Running `KEY=some task` before this experiment, the output would be `some`, but
|
||||||
|
after this experiment, the output would be `other`.
|
||||||
|
|
||||||
|
If you still want to get the OS variable, you can use the template function env
|
||||||
|
like follow : `{{env "OS_VAR"}}`.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
env:
|
||||||
|
KEY: 'other'
|
||||||
|
cmds:
|
||||||
|
- echo "$KEY"
|
||||||
|
- echo {{env "KEY"}}
|
||||||
|
```
|
||||||
|
Running `KEY=some task`, the output would be `other` and `some`.
|
||||||
|
|
||||||
|
Like other variables/envs, you can also fall back to a given value using the
|
||||||
|
default template function:
|
||||||
|
```yml
|
||||||
|
MY_ENV: '{{.MY_ENV | default "fallback"}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
{/* prettier-ignore-start */}
|
||||||
|
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
||||||
|
{/* prettier-ignore-end */}
|
||||||
@@ -62,6 +62,16 @@ includes:
|
|||||||
`TOKEN=my-token task my-remote-namespace:hello` will be resolved by Task to
|
`TOKEN=my-token task my-remote-namespace:hello` will be resolved by Task to
|
||||||
`https://my-token@raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml`
|
`https://my-token@raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml`
|
||||||
|
|
||||||
|
## Git nodes
|
||||||
|
|
||||||
|
You can also include a Taskfile from a Git node. We currently support ssh-style and http / https addresses like `git@example.com/foo/bar.git//Taskfiles.yml?ref=v1` and `https://example.com/foo/bar.git//Taskfiles.yml?ref=v1`.
|
||||||
|
|
||||||
|
You need to follow this pattern : `<baseUrl>.git//<path>?ref=<ref>`.
|
||||||
|
The `ref` parameter, optional, can be a branch name or a tag, if not provided it'll pick up the default branch.
|
||||||
|
The `path` is the path to the Taskfile in the repository.
|
||||||
|
|
||||||
|
If you want to use the SSH protocol, you need to make sure that your ssh-agent has your private ssh keys added so that they can be used during authentication.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
Running commands from sources that you do not control is always a potential
|
Running commands from sources that you do not control is always a potential
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ slug: /installation/
|
|||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
Task offers many installation methods. Check out the available methods below.
|
Task offers many installation methods. Check out the available methods below.
|
||||||
@@ -209,7 +212,7 @@ If you want to install Task in GitHub Actions you can try using
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Install Task
|
- name: Install Task
|
||||||
uses: arduino/setup-task@v1
|
uses: arduino/setup-task@v2
|
||||||
with:
|
with:
|
||||||
version: 3.x
|
version: 3.x
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -247,65 +250,76 @@ released binary.
|
|||||||
|
|
||||||
## Setup completions
|
## Setup completions
|
||||||
|
|
||||||
Download the autocompletion file corresponding to your shell.
|
Some installation methods will automatically install completions too, but if
|
||||||
|
this isn't working for you or your chosen method doesn't include them, you can
|
||||||
|
run `task --completion <shell>` to output a completion script for any supported
|
||||||
|
shell. There are a couple of ways these completions can be added to your shell
|
||||||
|
config:
|
||||||
|
|
||||||
[All completions are available on the Task repository](https://github.com/go-task/task/tree/main/completion).
|
### Option 1. Load the completions in your shell's startup config (Recommended)
|
||||||
|
|
||||||
### Bash
|
This method loads the completion script from the currently installed version of
|
||||||
|
task every time you create a new shell. This ensures that your completions are
|
||||||
|
always up-to-date.
|
||||||
|
|
||||||
First, ensure that you installed bash-completion using your package manager.
|
<Tabs values={[ {label: 'bash', value: '1'}, {label: 'zsh', value: '2'},
|
||||||
|
{label: 'fish', value: '3'},
|
||||||
|
{label: 'powershell', value: '4'}
|
||||||
|
]}>
|
||||||
|
|
||||||
Make the completion file executable:
|
<TabItem value="1">
|
||||||
|
```shell title="~/.bashrc"
|
||||||
|
eval "$(task --completion bash)"
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem value="2">
|
||||||
|
```shell title="~/.zshrc"
|
||||||
|
eval "$(task --completion zsh)"
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem value="3">
|
||||||
|
```shell title="~/.config/fish/config.fish"
|
||||||
|
task --completion fish | source
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<TabItem value="4">
|
||||||
|
```powershell title="$PROFILE\Microsoft.PowerShell_profile.ps1"
|
||||||
|
Invoke-Expression (&task --completion powershell | Out-String)
|
||||||
|
```
|
||||||
|
</TabItem></Tabs>
|
||||||
|
|
||||||
|
### Option 2. Copy the script to your shell's completions directory
|
||||||
|
|
||||||
|
This method requires you to manually update the completions whenever Task is
|
||||||
|
updated. However, it is useful if you want to modify the completions yourself.
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
values={[
|
||||||
|
{label: 'bash', value: '1'},
|
||||||
|
{label: 'zsh', value: '2'},
|
||||||
|
{label: 'fish', value: '3'}
|
||||||
|
]}>
|
||||||
|
|
||||||
|
<TabItem value="1">
|
||||||
```shell
|
```shell
|
||||||
chmod +x path/to/task.bash
|
task --completion bash > /etc/bash_completion.d/task
|
||||||
```
|
```
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
After, add this to your `~/.bash_profile`:
|
<TabItem value="2">
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
source path/to/task.bash
|
task --completion zsh > /usr/local/share/zsh/site-functions/_task
|
||||||
```
|
```
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
### ZSH
|
<TabItem value="3">
|
||||||
|
|
||||||
Put the `_task` file somewhere in your `$FPATH`:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
mv path/to/_task /usr/local/share/zsh/site-functions/_task
|
task --completion fish > ~/.config/fish/completions/task.fish
|
||||||
```
|
|
||||||
|
|
||||||
Ensure that the following is present in your `~/.zshrc`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
autoload -U compinit
|
|
||||||
compinit -i
|
|
||||||
```
|
|
||||||
|
|
||||||
ZSH version 5.7 or later is recommended.
|
|
||||||
|
|
||||||
### Fish
|
|
||||||
|
|
||||||
Move the `task.fish` completion script:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
mv path/to/task.fish ~/.config/fish/completions/task.fish
|
|
||||||
```
|
|
||||||
|
|
||||||
### PowerShell
|
|
||||||
|
|
||||||
Open your profile script with:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
mkdir -Path (Split-Path -Parent $profile) -ErrorAction SilentlyContinue
|
|
||||||
notepad $profile
|
|
||||||
```
|
|
||||||
|
|
||||||
Add the line and save the file:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
Invoke-Expression -Command path/to/task.ps1
|
|
||||||
```
|
```
|
||||||
|
</TabItem></Tabs>
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
{/* prettier-ignore-start */}
|
||||||
[go]: https://golang.org/
|
[go]: https://golang.org/
|
||||||
|
|||||||
@@ -53,33 +53,35 @@ If `--` is given, all remaining arguments will be assigned to a special
|
|||||||
## Exit Codes
|
## Exit Codes
|
||||||
|
|
||||||
Task will sometimes exit with specific exit codes. These codes are split into
|
Task will sometimes exit with specific exit codes. These codes are split into
|
||||||
three groups with the following ranges:
|
four groups with the following ranges:
|
||||||
|
|
||||||
- General errors (0-99)
|
- Success (0)
|
||||||
|
- General errors (1-99)
|
||||||
- Taskfile errors (100-199)
|
- Taskfile errors (100-199)
|
||||||
- Task errors (200-299)
|
- Task errors (200-299)
|
||||||
|
|
||||||
A full list of the exit codes and their descriptions can be found below:
|
A full list of the exit codes and their descriptions can be found below:
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| ---- | ------------------------------------------------------------ |
|
|------|---------------------------------------------------------------------|
|
||||||
| 0 | Success |
|
| 0 | Success |
|
||||||
| 1 | An unknown error occurred |
|
| 1 | An unknown error occurred |
|
||||||
| 100 | No Taskfile was found |
|
| 100 | No Taskfile was found |
|
||||||
| 101 | A Taskfile already exists when trying to initialize one |
|
| 101 | A Taskfile already exists when trying to initialize one |
|
||||||
| 102 | The Taskfile is invalid or cannot be parsed |
|
| 102 | The Taskfile is invalid or cannot be parsed |
|
||||||
| 103 | A remote Taskfile could not be downloaded |
|
| 103 | A remote Taskfile could not be downloaded |
|
||||||
| 104 | A remote Taskfile was not trusted by the user |
|
| 104 | A remote Taskfile was not trusted by the user |
|
||||||
| 105 | A remote Taskfile was could not be fetched securely |
|
| 105 | A remote Taskfile was could not be fetched securely |
|
||||||
| 106 | No cache was found for a remote Taskfile in offline mode |
|
| 106 | No cache was found for a remote Taskfile in offline mode |
|
||||||
| 107 | No schema version was defined in the Taskfile |
|
| 107 | No schema version was defined in the Taskfile |
|
||||||
| 200 | The specified task could not be found |
|
| 200 | The specified task could not be found |
|
||||||
| 201 | An error occurred while executing a command inside of a task |
|
| 201 | An error occurred while executing a command inside of a task |
|
||||||
| 202 | The user tried to invoke a task that is internal |
|
| 202 | The user tried to invoke a task that is internal |
|
||||||
| 203 | There a multiple tasks with the same name or alias |
|
| 203 | There a multiple tasks with the same name or alias |
|
||||||
| 204 | A task was called too many times |
|
| 204 | A task was called too many times |
|
||||||
| 205 | A task was cancelled by the user |
|
| 205 | A task was cancelled by the user |
|
||||||
| 206 | A task was not executed due to missing required variables |
|
| 206 | A task was not executed due to missing required variables |
|
||||||
|
| 207 | A task was not executed due to a variable having an incorrect value |
|
||||||
|
|
||||||
These codes can also be found in the repository in
|
These codes can also be found in the repository in
|
||||||
[`errors/errors.go`](https://github.com/go-task/task/blob/main/errors/errors.go).
|
[`errors/errors.go`](https://github.com/go-task/task/blob/main/errors/errors.go).
|
||||||
|
|||||||
@@ -8,16 +8,17 @@ sidebar_position: 4
|
|||||||
Task allows you to configure some behavior using environment variables. This
|
Task allows you to configure some behavior using environment variables. This
|
||||||
page lists all the environment variables that Task supports.
|
page lists all the environment variables that Task supports.
|
||||||
|
|
||||||
| ENV | Default | Description |
|
| ENV | Default | Description |
|
||||||
| ----------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|-------------------|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `TASK_TEMP_DIR` | `.task` | Location of the temp dir. Can relative to the project like `tmp/task` or absolute like `/tmp/.task` or `~/.task`. |
|
| `TASK_TEMP_DIR` | `.task` | Location of the temp dir. Can relative to the project like `tmp/task` or absolute like `/tmp/.task` or `~/.task`. |
|
||||||
| `TASK_REMOTE_DIR` | `TASK_TEMP_DIR` | Location of the remote temp dir (used for caching). Can relative to the project like `tmp/task` or absolute like `/tmp/.task` or `~/.task`. |
|
| `TASK_REMOTE_DIR` | `TASK_TEMP_DIR` | Location of the remote temp dir (used for caching). Can relative to the project like `tmp/task` or absolute like `/tmp/.task` or `~/.task`. |
|
||||||
| `FORCE_COLOR` | | Force color output usage. |
|
| `TASK_OFFLINE` | `false` | Set the `--offline` flag through the environment variable. Only for remote experiment. CLI flag `--offline` takes precedence over the env variable |
|
||||||
|
| `FORCE_COLOR` | | Force color output usage. |
|
||||||
|
|
||||||
## Custom Colors
|
## Custom Colors
|
||||||
|
|
||||||
| ENV | Default | Description |
|
| ENV | Default | Description |
|
||||||
| --------------------------- | ------- | ----------------------- |
|
|-----------------------------|---------|-------------------------|
|
||||||
| `TASK_COLOR_RESET` | `0` | Color used for white. |
|
| `TASK_COLOR_RESET` | `0` | Color used for white. |
|
||||||
| `TASK_COLOR_RED` | `31` | Color used for red. |
|
| `TASK_COLOR_RED` | `31` | Color used for red. |
|
||||||
| `TASK_COLOR_GREEN` | `32` | Color used for green. |
|
| `TASK_COLOR_GREEN` | `32` | Color used for green. |
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ toc_max_heading_level: 5
|
|||||||
# Schema Reference
|
# Schema Reference
|
||||||
|
|
||||||
| Attribute | Type | Default | Description |
|
| Attribute | Type | Default | Description |
|
||||||
| ---------- | ---------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|------------|------------------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `version` | `string` | | Version of the Taskfile. The current version is `3`. |
|
| `version` | `string` | | Version of the Taskfile. The current version is `3`. |
|
||||||
| `output` | `string` | `interleaved` | Output mode. Available options: `interleaved`, `group` and `prefixed`. |
|
| `output` | `string` | `interleaved` | Output mode. Available options: `interleaved`, `group` and `prefixed`. |
|
||||||
| `method` | `string` | `checksum` | Default method in this Taskfile. Can be overridden in a task by task basis. Available options: `checksum`, `timestamp` and `none`. |
|
| `method` | `string` | `checksum` | Default method in this Taskfile. Can be overridden in a task by task basis. Available options: `checksum`, `timestamp` and `none`. |
|
||||||
@@ -26,10 +26,11 @@ toc_max_heading_level: 5
|
|||||||
## Include
|
## Include
|
||||||
|
|
||||||
| Attribute | Type | Default | Description |
|
| Attribute | Type | Default | Description |
|
||||||
| ---------- | --------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|------------|-----------------------|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `taskfile` | `string` | | The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile. |
|
| `taskfile` | `string` | | The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile. |
|
||||||
| `dir` | `string` | The parent Taskfile directory | The working directory of the included tasks when run. |
|
| `dir` | `string` | The parent Taskfile directory | The working directory of the included tasks when run. |
|
||||||
| `optional` | `bool` | `false` | If `true`, no errors will be thrown if the specified file does not exist. |
|
| `optional` | `bool` | `false` | If `true`, no errors will be thrown if the specified file does not exist. |
|
||||||
|
| `flatten` | `bool` | `false` | If `true`, the tasks from the included Taskfile will be available in the including Taskfile without a namespace. If a task with the same name already exists in the including Taskfile, an error will be thrown. |
|
||||||
| `internal` | `bool` | `false` | Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`. |
|
| `internal` | `bool` | `false` | Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`. |
|
||||||
| `aliases` | `[]string` | | Alternative names for the namespace of the included Taskfile. |
|
| `aliases` | `[]string` | | Alternative names for the namespace of the included Taskfile. |
|
||||||
| `vars` | `map[string]Variable` | | A set of variables to apply to the included Taskfile. |
|
| `vars` | `map[string]Variable` | | A set of variables to apply to the included Taskfile. |
|
||||||
@@ -87,7 +88,7 @@ vars:
|
|||||||
| `deps` | [`[]Dependency`](#dependency) | | A list of dependencies of this task. Tasks defined here will run in parallel before this task. |
|
| `deps` | [`[]Dependency`](#dependency) | | A list of dependencies of this task. Tasks defined here will run in parallel before this task. |
|
||||||
| `label` | `string` | | Overrides the name of the task in the output when a task is run. Supports variables. |
|
| `label` | `string` | | Overrides the name of the task in the output when a task is run. Supports variables. |
|
||||||
| `desc` | `string` | | A short description of the task. This is displayed when calling `task --list`. |
|
| `desc` | `string` | | A short description of the task. This is displayed when calling `task --list`. |
|
||||||
| `prompt` | `string` | | A prompt that will be presented before a task is run. Declining will cancel running the current and any subsequent tasks. |
|
| `prompt` | `[]string` | | One or more prompts that will be presented before a task is run. Declining will cancel running the current and any subsequent tasks. |
|
||||||
| `summary` | `string` | | A longer description of the task. This is displayed when calling `task --summary [task]`. |
|
| `summary` | `string` | | A longer description of the task. This is displayed when calling `task --summary [task]`. |
|
||||||
| `aliases` | `[]string` | | A list of alternative names by which the task can be called. |
|
| `aliases` | `[]string` | | A list of alternative names by which the task can be called. |
|
||||||
| `sources` | `[]string` | | A list of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs. |
|
| `sources` | `[]string` | | A list of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs. |
|
||||||
@@ -106,7 +107,7 @@ vars:
|
|||||||
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
|
| `prefix` | `string` | | Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`. |
|
||||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
|
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing commands. |
|
||||||
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
|
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
|
||||||
| `platforms` | `[]string` | All platforms | Specifies which platforms the task should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/go/build/syslist.go). Task will be skipped otherwise. |
|
| `platforms` | `[]string` | All platforms | Specifies which platforms the task should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/internal/syslist/syslist.go). Task will be skipped otherwise. |
|
||||||
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
||||||
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ tasks:
|
|||||||
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
|
| `vars` | [`map[string]Variable`](#variable) | | Optional additional variables to be passed to the referenced task. Only relevant when setting `task` instead of `cmd`. |
|
||||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
|
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the command. |
|
||||||
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
|
| `defer` | `string` | | Alternative to `cmd`, but schedules the command to be executed at the end of this task instead of immediately. This cannot be used together with `cmd`. |
|
||||||
| `platforms` | `[]string` | All platforms | Specifies which platforms the command should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/go/build/syslist.go). Command will be skipped otherwise. |
|
| `platforms` | `[]string` | All platforms | Specifies which platforms the command should be run on. [Valid GOOS and GOARCH values allowed](https://github.com/golang/go/blob/master/src/internal/syslist/syslist.go). Command will be skipped otherwise. |
|
||||||
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
| `set` | `[]string` | | Specify options for the [`set` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html). |
|
||||||
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
| `shopt` | `[]string` | | Specify option for the [`shopt` builtin](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html). |
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user