Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33b6927b79 | ||
|
|
852a176e1f | ||
|
|
7511249514 | ||
|
|
3429cdd8af | ||
|
|
a1cd8eafd8 | ||
|
|
fbfb4ba9c4 | ||
|
|
ba9ba63792 | ||
|
|
460b89ce51 | ||
|
|
a4ec6e5257 | ||
|
|
44aa2ee3b3 | ||
|
|
80b417c4ab | ||
|
|
6d90c781c9 | ||
|
|
c51f04eca8 | ||
|
|
dda2004753 | ||
|
|
297f9eccea | ||
|
|
d2f2cba6d8 | ||
|
|
172d71435a | ||
|
|
bb1aec8a7e | ||
|
|
476d9f5e70 | ||
|
|
99014ad38d | ||
|
|
403456d3dc | ||
|
|
6335878317 | ||
|
|
6bff658af0 | ||
|
|
3e5ee2332a | ||
|
|
66f6998c86 | ||
|
|
f2a8f8ad8f | ||
|
|
540f6ecfdb | ||
|
|
8ec89f1bbd | ||
|
|
d33906b6e4 | ||
|
|
bb79fa1dc3 | ||
|
|
376a6182eb | ||
|
|
81de61d8db | ||
|
|
d2061ec898 | ||
|
|
077efbd2e7 | ||
|
|
8ce1782380 | ||
|
|
c2f20465ab | ||
|
|
fb0e43989d | ||
|
|
754248395c | ||
|
|
6e975ca155 | ||
|
|
79a2bc404e | ||
|
|
42a26e1741 | ||
|
|
695711e124 | ||
|
|
0d5811e502 | ||
|
|
b9d070f76b | ||
|
|
122c3f083e | ||
|
|
5d22cf4327 | ||
|
|
219d3ad193 | ||
|
|
e72157e26a | ||
|
|
50a377a7c4 | ||
|
|
9d7ddff60c | ||
|
|
081d878f86 | ||
|
|
d8dc091267 | ||
|
|
1c44d8049a | ||
|
|
a95191d29e | ||
|
|
111f6e7f18 | ||
|
|
4a5c1e9ec4 | ||
|
|
8f0893b5f7 | ||
|
|
b16e705a6c | ||
|
|
3cad318b70 | ||
|
|
8c6002cae6 | ||
|
|
0355bbaf3b | ||
|
|
2ba083a650 | ||
|
|
c79ea5a257 | ||
|
|
44706f4957 | ||
|
|
a1b3bb03ed | ||
|
|
76caa16909 | ||
|
|
160b788198 | ||
|
|
eada62f62c | ||
|
|
bd9419e6db | ||
|
|
bdd9de3001 | ||
|
|
200ba4ed04 | ||
|
|
1e8939dd58 | ||
|
|
f45dd11e53 | ||
|
|
1a0cc1d64d | ||
|
|
421cb522d9 | ||
|
|
1b18b041d6 | ||
|
|
8788703ac6 | ||
|
|
b6c25e3ad9 | ||
|
|
73eaa68cd1 | ||
|
|
beb927f7b4 | ||
|
|
cdc969cd4e | ||
|
|
2a67499f12 | ||
|
|
6a3cc79daa | ||
|
|
97d4a947ee | ||
|
|
e0e47ad9a0 | ||
|
|
b08eac58e9 | ||
|
|
11409ccf21 | ||
|
|
e3b6c97c3b | ||
|
|
d3da086ebf | ||
|
|
3507fa40f1 | ||
|
|
6f8f1f1409 | ||
|
|
c2148a359d | ||
|
|
c172185a24 | ||
|
|
1140a5c4ae | ||
|
|
3cc378c960 | ||
|
|
9b3a961303 | ||
|
|
d048555149 | ||
|
|
7533858a52 | ||
|
|
c4e10ef0aa | ||
|
|
c20842e7cd | ||
|
|
6cfdb21313 | ||
|
|
e396f4d06f | ||
|
|
47c1bb6a5b | ||
|
|
adfb0b513e | ||
|
|
98d78b9d8a | ||
|
|
a1c32a56ea | ||
|
|
6584bcf87f | ||
|
|
7ac75af622 | ||
|
|
b3c283b282 | ||
|
|
c2615dd746 | ||
|
|
789518f70d | ||
|
|
ad3008d855 | ||
|
|
3da426603f | ||
|
|
8d26e34b0a | ||
|
|
110d1d7245 | ||
|
|
f7384623df | ||
|
|
5d24e166ab | ||
|
|
d9ec5bcd24 | ||
|
|
bf9cd7625b | ||
|
|
afbf98c974 | ||
|
|
fedb68cde7 | ||
|
|
f787937a30 | ||
|
|
f54fef7e7b | ||
|
|
e36c77aaf3 | ||
|
|
de45e48c37 | ||
|
|
ef0ce8224e | ||
|
|
5b4d5387bf | ||
|
|
5c460b38c9 | ||
|
|
8b836ab446 | ||
|
|
1be1fccc76 | ||
|
|
2c1fda97f0 | ||
|
|
71f7b719b5 | ||
|
|
e39006b511 | ||
|
|
37d07d415a | ||
|
|
b80c8f78fc | ||
|
|
7707179b93 | ||
|
|
4e7d8bacdb | ||
|
|
0c46fa5a56 | ||
|
|
36aca00de3 | ||
|
|
0ec8cf1b53 |
@@ -8,6 +8,6 @@ charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
|
||||
[*.{md,yml,yaml,json,toml,htm,html,js,css,svg,sh,bash}]
|
||||
[*.{md,yml,yaml,json,toml,htm,html,js,css,svg,sh,bash,fish}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,13 +1,15 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Use the template to report bugs and issues
|
||||
about: Use this to report bugs and issues
|
||||
---
|
||||
|
||||
> Thanks for your bug report!
|
||||
>
|
||||
> Before submitting this issue, please make sure the same problem was
|
||||
> not already reported by someone else.
|
||||
>
|
||||
> Please describe the bug you're facing. Consider pasting example
|
||||
> Taskfiles showing how to reproduce the problem.
|
||||
|
||||
- Task version:
|
||||
- Operating System:
|
||||
|
||||
### Example Taskfile showing the issue
|
||||
|
||||
```yaml
|
||||
|
||||
```
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Help forum on Discord
|
||||
url: https://discord.com/channels/974121106208354339/1025054680289660989
|
||||
about: 'The Discord #help channel is the best way to get help from the community.'
|
||||
- name: Questions, Ideas and General Discussions
|
||||
url: https://github.com/go-task/task/discussions
|
||||
about: Ask questions and discuss general ideas with the community
|
||||
about: Ask questions and discuss general ideas with the community.
|
||||
|
||||
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Use the template to make feature requests
|
||||
about: Use this to make feature requests
|
||||
---
|
||||
|
||||
Describe in detail what feature do you want to see in Task.
|
||||
Give examples if possible.
|
||||
|
||||
Please, search if this wasn't proposed before, and if this is more like an idea
|
||||
than a strong feature request, consider opening a
|
||||
[discussion](https://github.com/go-task/task/discussions) instead.
|
||||
> Describe in detail what feature do you want to see in Task.
|
||||
> Give examples if possible.
|
||||
>
|
||||
> Please, search if this wasn't proposed before, and if this is more like an idea
|
||||
> than a strong feature request, consider opening a
|
||||
> [discussion](https://github.com/go-task/task/discussions) instead.
|
||||
|
||||
5
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
> Thanks for your pull request, we really appreciate contributions!
|
||||
>
|
||||
> Please understand that it may take some time to be reviewed.
|
||||
>
|
||||
> Also, make sure to follow the [Contribution Guide](https://taskfile.dev/contributing/).
|
||||
2
.github/workflows/lint.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.18.x
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
||||
2
.github/workflows/release.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
|
||||
2
.github/workflows/test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.17.x, 1.18.x]
|
||||
go-version: [1.18.x, 1.19.x]
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{matrix.platform}}
|
||||
steps:
|
||||
|
||||
2
.gitignore
vendored
@@ -22,6 +22,7 @@ dist/
|
||||
# editors
|
||||
.idea/
|
||||
.vscode/
|
||||
.fleet/
|
||||
|
||||
# exuberant ctags
|
||||
tags
|
||||
@@ -30,3 +31,4 @@ tags
|
||||
!/bin/.keep
|
||||
/testdata/vars/v1
|
||||
/tmp
|
||||
node_modules
|
||||
|
||||
@@ -17,6 +17,9 @@ build:
|
||||
goarch: 386
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w # Don't set main.version.
|
||||
|
||||
|
||||
76
CHANGELOG.md
@@ -1,12 +1,86 @@
|
||||
# Changelog
|
||||
|
||||
## v3.17.0
|
||||
|
||||
- Add a "Did you mean ...?" suggestion when a task does not exits another one
|
||||
with a similar name is found
|
||||
([#867](https://github.com/go-task/task/issues/867), [#880](https://github.com/go-task/task/pull/880)).
|
||||
- Now YAML parse errors will print which Taskfile failed to parse
|
||||
([#885](https://github.com/go-task/task/issues/885), [#887](https://github.com/go-task/task/pull/887)).
|
||||
- Add ability to set `aliases` for tasks and namespaces ([#268](https://github.com/go-task/task/pull/268), [#340](https://github.com/go-task/task/pull/340), [#879](https://github.com/go-task/task/pull/879)).
|
||||
- Improvements to Fish shell completion
|
||||
([#897](https://github.com/go-task/task/pull/897)).
|
||||
- Added ability to set a different watch interval by setting
|
||||
`interval: '500ms'` or using the `--interval=500ms` flag
|
||||
([#813](https://github.com/go-task/task/issues/813), [#865](https://github.com/go-task/task/pull/865)).
|
||||
- Add colored output to `--list`, `--list-all` and `--summary` flags ([#845](https://github.com/go-task/task/pull/845), [#874](https://github.com/go-task/task/pull/874)).
|
||||
- Fix unexpected behavior where `label:` was being shown instead of the task
|
||||
name on `--list`
|
||||
([#603](https://github.com/go-task/task/issues/603), [#877](https://github.com/go-task/task/pull/877)).
|
||||
|
||||
## v3.16.0 - 2022-09-29
|
||||
|
||||
- Add `npm` as new installation method: `npm i -g @go-task/cli`
|
||||
([#870](https://github.com/go-task/task/issues/870), [#871](https://github.com/go-task/task/pull/871), [npm package](https://www.npmjs.com/package/@go-task/cli)).
|
||||
- Add support to marking tasks and includes as internal, which will hide them
|
||||
from `--list` and `--list-all`
|
||||
([#818](https://github.com/go-task/task/pull/818)).
|
||||
|
||||
## v3.15.2 - 2022-09-08
|
||||
|
||||
- Fix error when using variable in `env:` introduced in the previous release
|
||||
([#858](https://github.com/go-task/task/issues/858), [#866](https://github.com/go-task/task/pull/866)).
|
||||
- Fix handling of `CLI_ARGS` (`--`) in Bash completion
|
||||
([#863](https://github.com/go-task/task/pull/863)).
|
||||
- On zsh completion, add ability to replace `--list-all` with `--list` as
|
||||
already possible on the Bash completion
|
||||
([#861](https://github.com/go-task/task/pull/861)).
|
||||
|
||||
## v3.15.0 - 2022-09-03
|
||||
|
||||
- Add new special variables `ROOT_DIR` and `TASKFILE_DIR`. This was a highly
|
||||
requested feature
|
||||
([#215](https://github.com/go-task/task/issues/215), [#857](https://github.com/go-task/task/pull/857), [Documentation](https://taskfile.dev/api/#special-variables)).
|
||||
- Follow symlinks on `sources`
|
||||
([#826](https://github.com/go-task/task/issues/826), [#831](https://github.com/go-task/task/pull/831)).
|
||||
- Improvements and fixes to Bash completion
|
||||
([#835](https://github.com/go-task/task/pull/835), [#844](https://github.com/go-task/task/pull/844)).
|
||||
|
||||
## v3.14.1 - 2022-08-03
|
||||
|
||||
- Always resolve relative include paths relative to the including Taskfile
|
||||
([#822](https://github.com/go-task/task/issues/822), [#823](https://github.com/go-task/task/pull/823)).
|
||||
- Fix ZSH and PowerShell completions to consider all tasks instead of just the
|
||||
public ones (those with descriptions)
|
||||
([#803](https://github.com/go-task/task/pull/803)).
|
||||
|
||||
## v3.14.0 - 2022-07-08
|
||||
|
||||
- Add ability to override the `.task` directory location with the
|
||||
`TASK_TEMP_DIR` environment variable.
|
||||
- Allow to override Task colors using environment variables:
|
||||
`TASK_COLOR_RESET`, `TASK_COLOR_BLUE`, `TASK_COLOR_GREEN`,
|
||||
`TASK_COLOR_CYAN`, `TASK_COLOR_YELLOW`, `TASK_COLOR_MAGENTA`
|
||||
and `TASK_COLOR_RED`
|
||||
([#568](https://github.com/go-task/task/pull/568), [#792](https://github.com/go-task/task/pull/792)).
|
||||
- Fixed bug when using the `output: group` mode where STDOUT and STDERR were
|
||||
being print in separated blocks instead of in the right order
|
||||
([#779](https://github.com/go-task/task/issues/779)).
|
||||
- Starting on this release, ARM architecture binaries are been released to Snap
|
||||
as well
|
||||
([#795](https://github.com/go-task/task/issues/795)).
|
||||
- i386 binaries won't be available anymore on Snap because Ubuntu removed the support
|
||||
for this architecture.
|
||||
- Upgrade mvdan.cc/sh, which fixes a bug with associative arrays
|
||||
([#785](https://github.com/go-task/task/issues/785), [mvdan/sh#884](https://github.com/mvdan/sh/issues/884), [mvdan/sh#893](https://github.com/mvdan/sh/pull/893)).
|
||||
|
||||
## v3.13.0 - 2022-06-13
|
||||
|
||||
- Added `-n` as an alias to `--dry`
|
||||
([#776](https://github.com/go-task/task/issues/776), [#777](https://github.com/go-task/task/pull/777)).
|
||||
- Fix behavior of interrupt (SIGINT, SIGTERM) signals. Task will now give time
|
||||
for the processes running to do cleanup work
|
||||
([#458](https://github.com/go-task/task/issues/458), [#479](https://github.com/go-task/task/pull/479), [#728](https://github.com/go-task/task/issues/728)).
|
||||
([#458](https://github.com/go-task/task/issues/458), [#479](https://github.com/go-task/task/pull/479), [#728](https://github.com/go-task/task/issues/728), [#769](https://github.com/go-task/task/pull/769)).
|
||||
- Add new `--exit-code` (`-x`) flag that will pass-through the exit form the
|
||||
command being ran
|
||||
([#755](https://github.com/go-task/task/pull/755)).
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
See <a href="https://taskfile.dev">taskfile.dev</a> for the documentation.
|
||||
<a href="https://taskfile.dev/installation/">Installation</a> | <a href="https://taskfile.dev/usage/">Documentation</a> | <a href="https://twitter.com/taskfiledev">Twitter</a> | <a href="https://discord.gg/6TY36E39UK">Discord</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
30
Taskfile.yml
@@ -2,6 +2,7 @@ version: '3'
|
||||
|
||||
includes:
|
||||
docs:
|
||||
aliases: [d]
|
||||
taskfile: ./docs
|
||||
dir: ./docs
|
||||
|
||||
@@ -23,6 +24,7 @@ tasks:
|
||||
|
||||
install:
|
||||
desc: Installs Task
|
||||
aliases: [i]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
cmds:
|
||||
@@ -42,6 +44,7 @@ tasks:
|
||||
|
||||
lint:
|
||||
desc: Runs golangci-lint
|
||||
aliases: [l]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
cmds:
|
||||
@@ -65,15 +68,40 @@ tasks:
|
||||
|
||||
test:
|
||||
desc: Runs test suite
|
||||
deps: [install, sleepit:build]
|
||||
aliases: [t]
|
||||
deps: [install]
|
||||
cmds:
|
||||
- go test {{catLines .GO_PACKAGES}}
|
||||
|
||||
test:signals:
|
||||
desc: Runs test suite with signals tests included
|
||||
deps: [install, sleepit:build]
|
||||
cmds:
|
||||
- go test {{catLines .GO_PACKAGES}} -tags signals
|
||||
|
||||
test-release:
|
||||
desc: Tests release process without publishing
|
||||
cmds:
|
||||
- goreleaser --snapshot --rm-dist
|
||||
|
||||
docs:changelog:
|
||||
desc: Copy CHANGELOG.md to the documentation website
|
||||
vars:
|
||||
FILE: docs/docs/changelog.md
|
||||
cmds:
|
||||
- rm {{.FILE}}
|
||||
- 'echo "---" >> {{.FILE}}'
|
||||
- 'echo "slug: /changelog/" >> {{.FILE}}'
|
||||
- 'echo "sidebar_position: 6" >> {{.FILE}}'
|
||||
- 'echo "---" >> {{.FILE}}'
|
||||
- 'echo "" >> {{.FILE}}'
|
||||
- 'cat CHANGELOG.md >> {{.FILE}}'
|
||||
|
||||
npm:publish:
|
||||
desc: Publish release to npm
|
||||
cmds:
|
||||
- npm publish --access=public
|
||||
|
||||
packages:
|
||||
cmds:
|
||||
- echo '{{.GO_PACKAGES}}'
|
||||
|
||||
@@ -73,6 +73,7 @@ func main() {
|
||||
entrypoint string
|
||||
output taskfile.Output
|
||||
color bool
|
||||
interval string
|
||||
)
|
||||
|
||||
pflag.BoolVar(&versionFlag, "version", false, "show Task version")
|
||||
@@ -96,6 +97,7 @@ func main() {
|
||||
pflag.StringVar(&output.Group.End, "output-group-end", "", "message template to print after a task's grouped output")
|
||||
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.StringVarP(&interval, "interval", "I", "5s", "interval to watch for changes")
|
||||
pflag.Parse()
|
||||
|
||||
if versionFlag {
|
||||
@@ -151,6 +153,7 @@ func main() {
|
||||
Parallel: parallel,
|
||||
Color: color,
|
||||
Concurrency: concurrency,
|
||||
Interval: interval,
|
||||
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
|
||||
@@ -1,26 +1,55 @@
|
||||
#!/bin/bash
|
||||
# vim: set tabstop=2 shiftwidth=2 expandtab:
|
||||
|
||||
GO_TASK_PROGNAME=task
|
||||
_GO_TASK_COMPLETION_LIST_OPTION='--list-all'
|
||||
|
||||
_go_task_completion()
|
||||
function _task()
|
||||
{
|
||||
local cur
|
||||
_get_comp_words_by_ref -n : cur
|
||||
local cur prev words cword
|
||||
_init_completion -n : || return
|
||||
|
||||
case "$cur" in
|
||||
--*)
|
||||
local options
|
||||
options="$(_parse_help task)"
|
||||
mapfile -t COMPREPLY < <(compgen -W "$options" -- "$cur")
|
||||
# Check for `--` within command-line and quit or strip suffix.
|
||||
local i
|
||||
for i in "${!words[@]}"; do
|
||||
if [ "${words[$i]}" == "--" ]; then
|
||||
# Do not complete words following `--` passed to CLI_ARGS.
|
||||
[ $cword -gt $i ] && return
|
||||
# Remove the words following `--` to not put --list in CLI_ARGS.
|
||||
words=( "${words[@]:0:$i}" )
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Handle special arguments of options.
|
||||
case "$prev" in
|
||||
-d|--dir)
|
||||
_filedir -d
|
||||
return $?
|
||||
;;
|
||||
*)
|
||||
local tasks
|
||||
tasks="$($GO_TASK_PROGNAME --list-all 2> /dev/null | awk 'NR>1 { sub(/:$/,"",$2); print $2 }')"
|
||||
mapfile -t COMPREPLY < <(compgen -W "$tasks" -- "$cur")
|
||||
-t|--taskfile)
|
||||
_filedir yaml || return $?
|
||||
_filedir yml
|
||||
return $?
|
||||
;;
|
||||
-o|--output)
|
||||
COMPREPLY=( $( compgen -W "interleaved group prefixed" -- $cur ) )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Handle normal options.
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "$(_parse_help $1)" -- $cur ) )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Prepare task name completions.
|
||||
local tasks=( $( "${words[@]}" --silent $_GO_TASK_COMPLETION_LIST_OPTION 2> /dev/null ) )
|
||||
COMPREPLY=( $( compgen -W "${tasks[*]}" -- "$cur" ) )
|
||||
|
||||
# Post-process because task names might contain colons.
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
complete -F _go_task_completion $GO_TASK_PROGNAME
|
||||
complete -F _task task
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
set GO_TASK_PROGNAME task
|
||||
|
||||
function __task_get_tasks --description "Prints all available tasks with their description"
|
||||
set -l output ($GO_TASK_PROGNAME --list-all | sed '1d; s/\* \(.*\):\s*\(.*\)/\1\t\2/' | string split0)
|
||||
# Read the list of tasks (and potential errors)
|
||||
$GO_TASK_PROGNAME --list-all 2>&1 | read -lz rawOutput
|
||||
|
||||
# Return on non-zero exit code (for cases when there is no Taskfile found or etc.)
|
||||
if test $status -ne 0
|
||||
return
|
||||
end
|
||||
|
||||
# Grab names and descriptions (if any) of the tasks
|
||||
set -l output (echo $rawOutput | sed '1d; s/\* \(.*\):\s*\(.*\)/\1\t\2/' | string split0)
|
||||
if test $output
|
||||
echo $output
|
||||
echo $output
|
||||
end
|
||||
end
|
||||
|
||||
complete -c $GO_TASK_PROGNAME -d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was
|
||||
specified.' -xa "(__task_get_tasks)"
|
||||
|
||||
|
||||
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
|
||||
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'sets directory of execution'
|
||||
complete -c $GO_TASK_PROGNAME -l dry -d 'compiles and prints tasks in the order that they would be run, without executing them'
|
||||
|
||||
@@ -3,7 +3,7 @@ $scriptBlock = {
|
||||
$curReg = "task{.exe}? (.*?)$"
|
||||
$startsWith = $wordToComplete | Select-String $curReg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value }
|
||||
$reg = "\* ($startsWith.+?):"
|
||||
$listOutput = $(task -l)
|
||||
$listOutput = $(task --list-all)
|
||||
$listOutput | Select-String $reg -AllMatches | ForEach-Object { $_.Matches.Groups[1].Value + " " }
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
local context state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"
|
||||
|
||||
# Listing commands from Taskfile.yml
|
||||
function __task_list() {
|
||||
local -a scripts cmd
|
||||
@@ -27,7 +29,7 @@ function __task_list() {
|
||||
(( enabled )) || return 0
|
||||
|
||||
scripts=()
|
||||
for item in "${(@)${(f)$("${cmd[@]}" --list)}[2,-1]#\* }"; do
|
||||
for item in "${(@)${(f)$("${cmd[@]}" $_GO_TASK_COMPLETION_LIST_OPTION)}[2,-1]#\* }"; do
|
||||
task="${item%%:[[:space:]]*}"
|
||||
desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
|
||||
@@ -8,10 +8,12 @@ tasks:
|
||||
|
||||
start:
|
||||
desc: Start website
|
||||
aliases: [s]
|
||||
vars:
|
||||
HOST: '{{default "localhost" .HOST}}'
|
||||
PORT: '{{default "3001" .PORT}}'
|
||||
cmds:
|
||||
- npx docusaurus start --no-open --port={{.PORT}}
|
||||
- npx docusaurus start --no-open --host={{.HOST}} --port={{.PORT}}
|
||||
|
||||
build:
|
||||
desc: Build website
|
||||
@@ -24,6 +26,7 @@ tasks:
|
||||
- rm -rf ./build
|
||||
|
||||
deploy:
|
||||
desc: Build and deploy Docusaurus. Requires GIT_USER and GIT_PASS envs to be previous set
|
||||
desc: Build and deploy Docusaurus
|
||||
summary: Requires GIT_USER and GIT_PASS envs to be previous set
|
||||
cmds:
|
||||
- npx docusaurus deploy
|
||||
|
||||
@@ -15,7 +15,7 @@ task [--flags] [tasks...] [-- CLI_ARGS...]
|
||||
|
||||
:::tip
|
||||
|
||||
If `--` is given, all remaning arguments to assigned to a special `CLI_ARGS`
|
||||
If `--` is given, all remaning arguments will be assigned to a special `CLI_ARGS`
|
||||
variable
|
||||
|
||||
:::
|
||||
@@ -30,6 +30,7 @@ variable
|
||||
| `-f` | `--force` | `bool` | `false` | Forces execution even when the task is up-to-date. |
|
||||
| `-h` | `--help` | `bool` | `false` | Shows Task usage. |
|
||||
| `-i` | `--init` | `bool` | `false` | Creates a new Taskfile.yaml in the current folder. |
|
||||
| `-I` | `--interval` | `string` | `5s` | Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid [Go Duration](https://pkg.go.dev/time#ParseDuration). |
|
||||
| `-l` | `--list` | `bool` | `false` | Lists tasks with description of current Taskfile. |
|
||||
| `-a` | `--list-all` | `bool` | `false` | Lists tasks with or without a description. |
|
||||
| `-o` | `--output` | `string` | Default set in the Taskfile or `intervealed` | Sets output style: [`interleaved`/`group`/`prefixed`]. |
|
||||
@@ -44,6 +45,34 @@ variable
|
||||
| | `--version` | `bool` | `false` | Show Task version. |
|
||||
| `-w` | `--watch` | `bool` | `false` | Enables watch of the given task. |
|
||||
|
||||
## Special Variables
|
||||
|
||||
There are some special variables that is available on the templating system:
|
||||
|
||||
| Var | Description |
|
||||
| - | - |
|
||||
| `CLI_ARGS` | Contain all extra arguments passed after `--` when calling Task through the CLI. |
|
||||
| `TASK` | The name of the current task. |
|
||||
| `ROOT_DIR` | The absolute path of the root Taskfile. |
|
||||
| `TASKFILE_DIR` | The absolute path of the included Taskfile. |
|
||||
| `CHECKSUM` | The checksum of the files listed in `sources`. Only available within the `status` prop and if method is set to `checksum`. |
|
||||
| `TIMESTAMP` | The date object of the greatest timestamp of the files listes in `sources`. Only available within the `status` prop and if method is set to `timestamp`. |
|
||||
|
||||
## ENV
|
||||
|
||||
Some environment variables can be overriden to adjust Task behavior.
|
||||
|
||||
| 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_COLOR_RESET` | `0` | Color used for white. |
|
||||
| `TASK_COLOR_BLUE` | `34` | Color used for blue. |
|
||||
| `TASK_COLOR_GREEN` | `32` | Color used for green. |
|
||||
| `TASK_COLOR_CYAN` | `36` | Color used for cyan. |
|
||||
| `TASK_COLOR_YELLOW` | `33` | Color used for yellow. |
|
||||
| `TASK_COLOR_MAGENTA` | `35` | Color used for magenta. |
|
||||
| `TASK_COLOR_RED` | `31` | Color used for red. |
|
||||
|
||||
## Schema
|
||||
|
||||
### Taskfile
|
||||
@@ -56,19 +85,21 @@ variable
|
||||
| `method` | `string` | `checksum` | Default method in this Taskfile. Can be overriden in a task by task basis. Available options: `checksum`, `timestamp` and `none`. |
|
||||
| `silent` | `bool` | `false` | Default "silent" options for this Taskfile. If `false`, can be overidden with `true` in a task by task basis. |
|
||||
| `run` | `string` | `always` | Default "run" option for this Taskfile. Available options: `always`, `once` and `when_changed`. |
|
||||
| `interval` | `string` | `5s` | Sets a different watch interval when using `--watch`, the default being 5 seconds. This string should be a valid [Go Duration](https://pkg.go.dev/time#ParseDuration). |
|
||||
| `vars` | [`map[string]Variable`](#variable) | | Global variables. |
|
||||
| `env` | [`map[string]Variable`](#variable) | | Global environment. |
|
||||
| `dotenv` | `[]string` | | A list of `.env` file paths to be parsed. |
|
||||
| `tasks` | [`map[string]Task`](#task) | | The task definitions. |
|
||||
|
||||
|
||||
### Include
|
||||
|
||||
| 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. |
|
||||
| `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. |
|
||||
| `optional` | `bool` | `false` | If `true`, no errors will be thrown if the specified file does not exist. |
|
||||
| `internal` | `bool` | `false` | If `true`, tasks will not be callable from the command line and will be omitted from both `--list` and `--list-all`. |
|
||||
| `aliases` | `[]string` | | Alternative names for the namespace of the included Taskfile. |
|
||||
|
||||
:::info
|
||||
|
||||
@@ -87,10 +118,13 @@ includes:
|
||||
| - | - | - | - |
|
||||
| `desc` | `string` | | A short description of the task. This is listed when calling `task --list`. |
|
||||
| `summary` | `string` | | A longer description of the task. This is listed when calling `task --summary [task]`. |
|
||||
| `aliases` | `[]string` | | Alternative names for the task. |
|
||||
| `label` | `string` | | Overrides the name of the task in the output when a task is run. Supports variables. |
|
||||
| `sources` | `[]string` | | List of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs. |
|
||||
| `dir` | `string` | | The current directory which this task should run. |
|
||||
| `method` | `string` | `checksum` | Method used by this task. Default to the one declared globally or `checksum`. Available options: `checksum`, `timestamp` and `none` |
|
||||
| `silent` | `bool` | `false` | Skips some output for this task. Note that STDOUT and STDERR of the commands will still be redirected. |
|
||||
| `internal` | `bool` | `false` | If `true`, this task will not be callable from the command line and will be omitted from both `--list` and `--list-all`. |
|
||||
| `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`. |
|
||||
| `prefix` | `string` | | Allows to override the prefix print before the STDOUT. Only relevant when using the `prefixed` output mode. |
|
||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the commands. |
|
||||
@@ -114,6 +148,9 @@ tasks:
|
||||
foobar:
|
||||
- echo "foo"
|
||||
- echo "bar"
|
||||
|
||||
baz:
|
||||
cmd: echo "baz"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -5,13 +5,87 @@ sidebar_position: 6
|
||||
|
||||
# Changelog
|
||||
|
||||
## v3.17.0
|
||||
|
||||
- Add a "Did you mean ...?" suggestion when a task does not exits another one
|
||||
with a similar name is found
|
||||
([#867](https://github.com/go-task/task/issues/867), [#880](https://github.com/go-task/task/pull/880)).
|
||||
- Now YAML parse errors will print which Taskfile failed to parse
|
||||
([#885](https://github.com/go-task/task/issues/885), [#887](https://github.com/go-task/task/pull/887)).
|
||||
- Add ability to set `aliases` for tasks and namespaces ([#268](https://github.com/go-task/task/pull/268), [#340](https://github.com/go-task/task/pull/340), [#879](https://github.com/go-task/task/pull/879)).
|
||||
- Improvements to Fish shell completion
|
||||
([#897](https://github.com/go-task/task/pull/897)).
|
||||
- Added ability to set a different watch interval by setting
|
||||
`interval: '500ms'` or using the `--interval=500ms` flag
|
||||
([#813](https://github.com/go-task/task/issues/813), [#865](https://github.com/go-task/task/pull/865)).
|
||||
- Add colored output to `--list`, `--list-all` and `--summary` flags ([#845](https://github.com/go-task/task/pull/845), [#874](https://github.com/go-task/task/pull/874)).
|
||||
- Fix unexpected behavior where `label:` was being shown instead of the task
|
||||
name on `--list`
|
||||
([#603](https://github.com/go-task/task/issues/603), [#877](https://github.com/go-task/task/pull/877)).
|
||||
|
||||
## v3.16.0 - 2022-09-29
|
||||
|
||||
- Add `npm` as new installation method: `npm i -g @go-task/cli`
|
||||
([#870](https://github.com/go-task/task/issues/870), [#871](https://github.com/go-task/task/pull/871), [npm package](https://www.npmjs.com/package/@go-task/cli)).
|
||||
- Add support to marking tasks and includes as internal, which will hide them
|
||||
from `--list` and `--list-all`
|
||||
([#818](https://github.com/go-task/task/pull/818)).
|
||||
|
||||
## v3.15.2 - 2022-09-08
|
||||
|
||||
- Fix error when using variable in `env:` introduced in the previous release
|
||||
([#858](https://github.com/go-task/task/issues/858), [#866](https://github.com/go-task/task/pull/866)).
|
||||
- Fix handling of `CLI_ARGS` (`--`) in Bash completion
|
||||
([#863](https://github.com/go-task/task/pull/863)).
|
||||
- On zsh completion, add ability to replace `--list-all` with `--list` as
|
||||
already possible on the Bash completion
|
||||
([#861](https://github.com/go-task/task/pull/861)).
|
||||
|
||||
## v3.15.0 - 2022-09-03
|
||||
|
||||
- Add new special variables `ROOT_DIR` and `TASKFILE_DIR`. This was a highly
|
||||
requested feature
|
||||
([#215](https://github.com/go-task/task/issues/215), [#857](https://github.com/go-task/task/pull/857), [Documentation](https://taskfile.dev/api/#special-variables)).
|
||||
- Follow symlinks on `sources`
|
||||
([#826](https://github.com/go-task/task/issues/826), [#831](https://github.com/go-task/task/pull/831)).
|
||||
- Improvements and fixes to Bash completion
|
||||
([#835](https://github.com/go-task/task/pull/835), [#844](https://github.com/go-task/task/pull/844)).
|
||||
|
||||
## v3.14.1 - 2022-08-03
|
||||
|
||||
- Always resolve relative include paths relative to the including Taskfile
|
||||
([#822](https://github.com/go-task/task/issues/822), [#823](https://github.com/go-task/task/pull/823)).
|
||||
- Fix ZSH and PowerShell completions to consider all tasks instead of just the
|
||||
public ones (those with descriptions)
|
||||
([#803](https://github.com/go-task/task/pull/803)).
|
||||
|
||||
## v3.14.0 - 2022-07-08
|
||||
|
||||
- Add ability to override the `.task` directory location with the
|
||||
`TASK_TEMP_DIR` environment variable.
|
||||
- Allow to override Task colors using environment variables:
|
||||
`TASK_COLOR_RESET`, `TASK_COLOR_BLUE`, `TASK_COLOR_GREEN`,
|
||||
`TASK_COLOR_CYAN`, `TASK_COLOR_YELLOW`, `TASK_COLOR_MAGENTA`
|
||||
and `TASK_COLOR_RED`
|
||||
([#568](https://github.com/go-task/task/pull/568), [#792](https://github.com/go-task/task/pull/792)).
|
||||
- Fixed bug when using the `output: group` mode where STDOUT and STDERR were
|
||||
being print in separated blocks instead of in the right order
|
||||
([#779](https://github.com/go-task/task/issues/779)).
|
||||
- Starting on this release, ARM architecture binaries are been released to Snap
|
||||
as well
|
||||
([#795](https://github.com/go-task/task/issues/795)).
|
||||
- i386 binaries won't be available anymore on Snap because Ubuntu removed the support
|
||||
for this architecture.
|
||||
- Upgrade mvdan.cc/sh, which fixes a bug with associative arrays
|
||||
([#785](https://github.com/go-task/task/issues/785), [mvdan/sh#884](https://github.com/mvdan/sh/issues/884), [mvdan/sh#893](https://github.com/mvdan/sh/pull/893)).
|
||||
|
||||
## v3.13.0 - 2022-06-13
|
||||
|
||||
- Added `-n` as an alias to `--dry`
|
||||
([#776](https://github.com/go-task/task/issues/776), [#777](https://github.com/go-task/task/pull/777)).
|
||||
- Fix behavior of interrupt (SIGINT, SIGTERM) signals. Task will now give time
|
||||
for the processes running to do cleanup work
|
||||
([#458](https://github.com/go-task/task/issues/458), [#479](https://github.com/go-task/task/pull/479), [#728](https://github.com/go-task/task/issues/728)).
|
||||
([#458](https://github.com/go-task/task/issues/458), [#479](https://github.com/go-task/task/pull/479), [#728](https://github.com/go-task/task/issues/728), [#769](https://github.com/go-task/task/pull/769)).
|
||||
- Add new `--exit-code` (`-x`) flag that will pass-through the exit form the
|
||||
command being ran
|
||||
([#755](https://github.com/go-task/task/pull/755)).
|
||||
|
||||
@@ -46,9 +46,11 @@ Some installation methods are maintained by third party:
|
||||
|
||||
- [GitHub Actions](https://github.com/arduino/setup-task)
|
||||
by [@arduino](https://github.com/arduino)
|
||||
- [AUR](https://aur.archlinux.org/packages/taskfile-git)
|
||||
by [@kovetskiy](https://github.com/kovetskiy)
|
||||
- [AUR](https://aur.archlinux.org/packages/go-task-bin)
|
||||
by [@carlsmedstad](https://github.com/carlsmedstad)
|
||||
- [Scoop](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json)
|
||||
- [Fedora](https://packages.fedoraproject.org/pkgs/golang-github-task/go-task/)
|
||||
- [NixOS](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/tools/go-task/default.nix)
|
||||
|
||||
## More
|
||||
|
||||
|
||||
122
docs/docs/contributing.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
slug: /contributing/
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions to Task are very welcome, but we ask that you read this document
|
||||
before submitting a PR.
|
||||
|
||||
## Before you start
|
||||
|
||||
- **Check existing work** - Is there an existing PR? Are there issues discussing
|
||||
the feature/change you want to make? Please make sure you consider/address these
|
||||
discussions in your work.
|
||||
- **Backwards compatibility** - Will your change break existing Taskfiles? It is
|
||||
much more likely that your change will merged if it backwards compatible. Is
|
||||
there an approach you can take that maintains this compatibility? If not,
|
||||
consider opening an issue first so that API changes can be discussed before you
|
||||
invest you time into a PR.
|
||||
|
||||
## 1. Setup
|
||||
|
||||
- **Go** - Task is written in [Go]. We always support the latest two major Go
|
||||
versions, so make sure your version is recent enough.
|
||||
- **Node.js** - [Node.js] is used to host Task's documentation server and is
|
||||
required if you want to run this server locally.
|
||||
- **Yarn** - [Yarn] is the Node.js package manager used by Task.
|
||||
|
||||
## 2. Making changes
|
||||
|
||||
- **Code style** - Try to maintain the existing code style where possible and
|
||||
ensure that code is formatted by `gofmt`. We use `golangci-lint` in our CI to
|
||||
enforce a consistent style and best-practise. There's a `lint` command in
|
||||
the Taskfile to run this locally.
|
||||
- **Documentation** - Ensure that you add/update any relevant documentation. See
|
||||
the [updating documentation](#updating-documentation) section below.
|
||||
- **Tests** - Ensure that you add/update any relevant tests and that all tests
|
||||
are passing before submitting the PR. See the [writing tests](#writing-tests)
|
||||
section below.
|
||||
|
||||
### Running your changes
|
||||
|
||||
To run Task with working changes, you can use `go run ./cmd/task`. To run a
|
||||
development build of task against a test Taskfile in `testdata`, you can use `go
|
||||
run ./cmd/task --dir ./testdata/<my_test_dir> <task_name>`.
|
||||
|
||||
### Updating documentation
|
||||
|
||||
Task uses [Docusaurus] to host a documentation server. This can be setup and run
|
||||
locally by using `task docs:setup` and `task docs:start` respectively (requires
|
||||
`nodejs` & `yarn`). All content is written in Markdown and is located in the
|
||||
`docs/docs` directory. All Markdown documents should have an 80 character line
|
||||
wrap limit.
|
||||
|
||||
When making a change, consider whether a change to the [Usage Guide](./usage.md)
|
||||
is necessary. This document contains descriptions and examples of how to use
|
||||
Task features. If you're adding a new feature, try to find an appropriate place
|
||||
to add a new section. If you're updating an existing feature, ensure that the
|
||||
documentation and any examples are up-to-date. Ensure that any examples follow
|
||||
the [Taskfile Styleguide](./styleguide.md).
|
||||
|
||||
If you added a new command/flag, ensure that you add it to the [API
|
||||
Reference](./api_reference.md).
|
||||
|
||||
### Writing tests
|
||||
|
||||
Most of Task's test are held in the `task_test.go` file in the project root and
|
||||
this is where you'll most likely want to add new ones too. Most of these tests
|
||||
also have a subdirectory in the `testdata` directory where any Taskfiles/data
|
||||
required to run the tests are stored.
|
||||
|
||||
When making a changes, consider whether new tests are required. These tests
|
||||
should ensure that the functionality you are adding will continue to work in the
|
||||
future. Existing tests may also need updating if you have changed Task's
|
||||
behaviour.
|
||||
|
||||
## 3. Committing your code
|
||||
|
||||
Try to write meaningful commit messages and avoid having too many commits on
|
||||
the PR. Most PRs should likely have a single commit (although for bigger PRs it
|
||||
may be reasonable to split it in a few). Git squash and rebase is your friend!
|
||||
|
||||
## 4. Submitting a PR
|
||||
|
||||
- **Describe your changes** - Ensure that you provide a comprehensive
|
||||
description of your changes.
|
||||
- **Issue/PR links** - Link any previous work such as related issues or PRs.
|
||||
Please describe how your changes differ to/extend this work.
|
||||
- **Examples** - Add any examples that you think are useful to demonstrate the
|
||||
effect of your changes.
|
||||
- **Draft PRs** - If your changes are incomplete, but you would like to discuss
|
||||
them, open the PR as a draft and add a comment to start a discussion. Using
|
||||
comments rather than the PR description allows the description to be updated
|
||||
later while preserving any discussions.
|
||||
|
||||
## FAQ
|
||||
|
||||
> I want to contribute, where do I start?
|
||||
|
||||
Take a look at the list of [open issues]. We have a [good first issue] label for
|
||||
simpler issues that are ideal for first time contributions.
|
||||
|
||||
All kinds of contributions are welcome, whether its a typo fix or a shiny new
|
||||
feature. You can also contribute by upvoting/commenting on issues, helping to
|
||||
answer questions or contributing to other [community projects](./community.md).
|
||||
|
||||
> I'm stuck, where can I get help?
|
||||
|
||||
If you have questions, feel free to ask them in the `#help` channel on our
|
||||
[Discord server].
|
||||
|
||||
---
|
||||
|
||||
[Go]: https://go.dev
|
||||
[install version 1.18+]: https://go.dev/doc/install
|
||||
[Node.js]: https://nodejs.org/en/
|
||||
[Yarn]: https://yarnpkg.com/
|
||||
[Docusaurus]: https://docusaurus.io
|
||||
[open issues]: https://github.com/go-task/task/issues
|
||||
[good first issue]: https://github.com/go-task/task/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
|
||||
[Discord server]: https://discord.gg/6TY36E39UK
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: /donate/
|
||||
sidebar_position: 9
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
# Donate
|
||||
|
||||
@@ -42,11 +42,10 @@ This installation method is community owned.
|
||||
|
||||
### Scoop
|
||||
|
||||
If you're on Windows and have [Scoop][scoop] installed, use `extras` bucket
|
||||
to install Task like:
|
||||
If you're on Windows and have [Scoop][scoop] installed, getting
|
||||
Task is as simple as running:
|
||||
|
||||
```cmd
|
||||
scoop bucket add extras
|
||||
scoop install task
|
||||
```
|
||||
|
||||
@@ -56,15 +55,23 @@ may take some time until it's available on Scoop.
|
||||
### AUR
|
||||
|
||||
If you're on Arch Linux you can install Task from
|
||||
[AUR](https://aur.archlinux.org/packages/taskfile-git) using your favorite
|
||||
[AUR](https://aur.archlinux.org/packages/go-task-bin) using your favorite
|
||||
package manager such as `yay`, `pacaur` or `yaourt`:
|
||||
|
||||
```cmd
|
||||
yay -S taskfile-git
|
||||
yay -S go-task-bin
|
||||
```
|
||||
|
||||
This installation method is community owned, but since it's `-git` version of
|
||||
the package, it's always latest available version based on the Git repository.
|
||||
Alternatively, there's
|
||||
[this package](https://aur.archlinux.org/packages/go-task) which installs from
|
||||
the source code instead of downloading the binary from the
|
||||
[releases page](https://github.com/go-task/task/releases):
|
||||
|
||||
```cmd
|
||||
yay -S go-task
|
||||
```
|
||||
|
||||
This installation method is community owned.
|
||||
|
||||
### Fedora
|
||||
|
||||
@@ -90,6 +97,15 @@ nix-env -iA nixpkgs.go-task
|
||||
This installation method is community owned. After a new release of Task, it
|
||||
may take some time until it's available in [nixpkgs](https://github.com/NixOS/nixpkgs).
|
||||
|
||||
### npm
|
||||
|
||||
You can also use Node and npm to install Task by installing
|
||||
[this package](https://www.npmjs.com/package/@go-task/cli).
|
||||
|
||||
```bash
|
||||
npm install -g @go-task/cli
|
||||
```
|
||||
|
||||
## Get The Binary
|
||||
|
||||
### Binary
|
||||
@@ -107,19 +123,25 @@ We also have an [install script][installscript] which is very useful in
|
||||
scenarios like CI. Many thanks to [GoDownloader][godownloader] for enabling the
|
||||
easy generation of this script.
|
||||
|
||||
By default, it installs on the `./bin` directory relative to the working
|
||||
directory:
|
||||
|
||||
```bash
|
||||
# For Default Installation to ./bin with debug logging
|
||||
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d
|
||||
|
||||
# For Installation To /usr/local/bin for userwide access with debug logging
|
||||
# May require sudo sh
|
||||
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
|
||||
|
||||
```
|
||||
|
||||
:::info
|
||||
It is possible to override the installation directory with the `-b` parameter.
|
||||
On Linux, common choices are `~/.local/bin` and `~/bin` to install for the
|
||||
current user or `/usr/local/bin` to install for all users:
|
||||
|
||||
This method will download the binary on the local `./bin` directory by default.
|
||||
```bash
|
||||
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
|
||||
```
|
||||
|
||||
:::caution
|
||||
|
||||
On macOS and Windows, `~/.local/bin` and `~/bin` are not added to `$PATH` by
|
||||
default.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -38,12 +38,12 @@ guide to check the full schema documentation and Task features.
|
||||
## Features
|
||||
|
||||
- [Easy installation](installation.md): just download a single binary, add to
|
||||
$PATH and you're done! Or you can also install using [Homebrew][homebrew],
|
||||
[Snapcraft][snapcraft], or [Scoop][scoop] if you want;
|
||||
`$PATH` and you're done! Or you can also install using [Homebrew][homebrew],
|
||||
[Snapcraft][snapcraft], or [Scoop][scoop] if you want.
|
||||
- Available on CIs: by adding [this simple command](installation.md#install-script)
|
||||
to install on your CI script and you're done to use Task as part of your CI pipeline;
|
||||
to install on your CI script and you're ready to use Task as part of your CI pipeline;
|
||||
- Truly cross-platform: while most build tools only work well on Linux or macOS,
|
||||
Task also supports Windows thanks to [this shell interpreter for Go][sh];
|
||||
Task also supports Windows thanks to [this shell interpreter for Go][sh].
|
||||
- Great for code generation: you can easily [prevent a task from running](/usage#prevent-unnecessary-work)
|
||||
if a given set of files haven't changed since last run (based either on its
|
||||
timestamp or content).
|
||||
@@ -54,4 +54,4 @@ guide to check the full schema documentation and Task features.
|
||||
[homebrew]: https://brew.sh/
|
||||
[snapcraft]: https://snapcraft.io/
|
||||
[scoop]: https://scoop.sh/
|
||||
[sh]: https://mvdan.cc/sh
|
||||
[sh]: https://github.com/mvdan/sh
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: /releasing/
|
||||
sidebar_position: 7
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# Releasing
|
||||
@@ -13,27 +13,35 @@ the `test-release` task of the Taskfile.
|
||||
artifacts automatically when a new Git tag is pushed to master
|
||||
(raw executables and DEB and RPM packages).
|
||||
|
||||
Since v3.15.0, raw executables can also be reproduced and verified locally by
|
||||
checking out a specific tag and calling `goreleaser build`, using the Go version
|
||||
defined in the above GitHub Actions.
|
||||
|
||||
# Homebrew
|
||||
|
||||
To release a new version on the [Homebrew tap][homebrewtap] edit the
|
||||
[Formula/go-task.rb][gotaskrb] file, updating with the new version, download
|
||||
URL and sha256.
|
||||
Goreleaser will automatically push a new commit to the
|
||||
[Formula/go-task.rb][gotaskrb] file in the [Homebrew tap][homebrewtap]
|
||||
repository to release the new version.
|
||||
|
||||
# npm
|
||||
|
||||
To release to npm update the version in the [`package.json`][packagejson] file
|
||||
and then run `task npm:publish` to push it.
|
||||
|
||||
# Snapcraft
|
||||
|
||||
The exception is the publishing of a new version of the
|
||||
[snap package][snappackage]. This current require two steps after publishing
|
||||
the binaries:
|
||||
The [snap package][snappackage] requires to manual steps to release a new
|
||||
version:
|
||||
|
||||
* Updating the current version on [snapcraft.yaml][snapcraftyaml];
|
||||
* Moving both `i386` and `amd64` new artifacts to the stable channel on
|
||||
the [Snapcraft dashboard][snapcraftdashboard]
|
||||
* Updating the current version on [snapcraft.yaml][snapcraftyaml].
|
||||
* Moving both `amd64`, `armhf` and `arm64` new artifacts to the stable channel on
|
||||
the [Snapcraft dashboard][snapcraftdashboard].
|
||||
|
||||
# Scoop
|
||||
|
||||
Scoop is a community owned installation method. Scoop owners usually take care
|
||||
of updating versions there by editing
|
||||
[this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
|
||||
Scoop is a command-line package manager for the Windows operating system.
|
||||
Scoop package manifests are maintained by the community.
|
||||
Scoop owners usually take care of updating versions there by editing [this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
|
||||
If you think its Task version is outdated, open an issue to let us know.
|
||||
|
||||
# Nix
|
||||
@@ -46,6 +54,7 @@ If you think its Task version is outdated, open an issue to let us know.
|
||||
[goreleaser]: https://goreleaser.com/
|
||||
[homebrewtap]: https://github.com/go-task/homebrew-tap
|
||||
[gotaskrb]: https://github.com/go-task/homebrew-tap/blob/master/Formula/go-task.rb
|
||||
[packagejson]: https://github.com/go-task/task/blob/master/package.json#L3
|
||||
[snappackage]: https://github.com/go-task/snap
|
||||
[snapcraftyaml]: https://github.com/go-task/snap/blob/master/snap/snapcraft.yaml#L2
|
||||
[snapcraftdashboard]: https://snapcraft.io/task/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: /taskfile-versions/
|
||||
sidebar_position: 8
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
# Taskfile Versions
|
||||
|
||||
@@ -136,6 +136,8 @@ namespace. So, you'd call `task docs:serve` to run the `serve` task from
|
||||
`documentation/Taskfile.yml` or `task docker:build` to run the `build` task
|
||||
from the `DockerTasks.yml` file.
|
||||
|
||||
Relative paths are resolved relative to the directory containing the including Taskfile.
|
||||
|
||||
### OS-specific Taskfiles
|
||||
|
||||
With `version: '2'`, task automatically includes any `Taskfile_{{OS}}.yml`
|
||||
@@ -192,6 +194,22 @@ tasks:
|
||||
- echo "This command can still be successfully executed if ./tests/Taskfile.yml does not exist"
|
||||
```
|
||||
|
||||
### Internal includes
|
||||
|
||||
Includes marked as internal will set all the tasks of the included file to be
|
||||
internal as well (see the [Internal tasks](#internal-tasks) section below).
|
||||
This is useful when including utility tasks that are not intended to be used
|
||||
directly by the user.
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
tests:
|
||||
taskfile: ./taskfiles/Utils.yml
|
||||
internal: true
|
||||
```
|
||||
|
||||
### Vars of included Taskfiles
|
||||
|
||||
You can also specify variables when including a Taskfile. This may be useful
|
||||
@@ -212,15 +230,53 @@ includes:
|
||||
DOCKER_IMAGE: frontend_image
|
||||
```
|
||||
|
||||
### Namespace aliases
|
||||
|
||||
When including a Taskfile, you can give the namespace a list of `aliases`.
|
||||
This works in the same way as [task aliases](#task-aliases) and can be used
|
||||
together to create shorter and easier-to-type commands.
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
generate:
|
||||
taskfile: ./taskfiles/Generate.yml
|
||||
aliases: [gen]
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
Vars declared in the included Taskfile have preference over the
|
||||
included ones! If you want a variable in an included Taskfile to be overridable,
|
||||
variables in the including Taskfile! If you want a variable in an included Taskfile to be overridable,
|
||||
use the [default function](https://go-task.github.io/slim-sprig/defaults.html):
|
||||
`MY_VAR: '{{.MY_VAR | default "my-default-value"}}'`.
|
||||
|
||||
:::
|
||||
|
||||
## Internal tasks
|
||||
|
||||
Internal tasks are tasks that cannot be called directly by the user. They will
|
||||
not appear in the output when running `task --list|--list-all`. Other tasks may
|
||||
call internal tasks in the usual way. This is useful for creating reusable,
|
||||
function-like tasks that have no useful purpose on the command line.
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build-image-1:
|
||||
cmds:
|
||||
- task: build-image
|
||||
vars:
|
||||
DOCKER_IMAGE: image-1
|
||||
|
||||
build-image:
|
||||
internal: true
|
||||
cmds:
|
||||
- docker build -t {{.DOCKER_IMAGE}} .
|
||||
```
|
||||
|
||||
## Task directory
|
||||
|
||||
By default, tasks will be executed in the directory where the Taskfile is
|
||||
@@ -406,8 +462,6 @@ tasks:
|
||||
Task will compare the checksum of the source files to determine if it's
|
||||
necessary to run the task. If not, it will just print a message like
|
||||
`Task "js" is up to date`.
|
||||
You will probably want to ignore the `.task` folder in your `.gitignore` file
|
||||
(It is there that Task stores the last checksum).
|
||||
|
||||
If you prefer this check to be made by the modification timestamp of the files,
|
||||
instead of its checksum (content), just set the `method` property to `timestamp`.
|
||||
@@ -423,9 +477,34 @@ tasks:
|
||||
- ./*.go
|
||||
generates:
|
||||
- app{{exeExt}}
|
||||
method: checksum
|
||||
method: timestamp
|
||||
```
|
||||
|
||||
In situations where you need more flexibility the `status` keyword can be used.
|
||||
You can even combine the two. See the documentation for
|
||||
[status](#using-programmatic-checks-to-indicate-a-task-is-up-to-date) for an
|
||||
example.
|
||||
|
||||
:::info
|
||||
|
||||
By default, task stores checksums on a local `.task` directory in the project's
|
||||
directory. Most of the time, you'll want to have this directory on `.gitignore`
|
||||
(or equivalent) so it isn't committed. (If you have a task for code generation
|
||||
that is committed it may make sense to commit the checksum of that task as
|
||||
well, though).
|
||||
|
||||
If you want these files to be stored in another directory, you can set a
|
||||
`TASK_TEMP_DIR` environment variable in your machine. It can contain a relative
|
||||
path like `tmp/task` that will be interpreted as relative to the project
|
||||
directory, or an absolute or home path like `/tmp/.task` or `~/.task`
|
||||
(subdirectories will be created for each project).
|
||||
|
||||
```bash
|
||||
export TASK_TEMP_DIR='~/.task'
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::info
|
||||
|
||||
Each task has only one checksum stored for its `sources`. If you want
|
||||
@@ -496,6 +575,30 @@ up-to-date.
|
||||
Also, `task --status [tasks]...` will exit with a non-zero exit code if any of
|
||||
the tasks are not up-to-date.
|
||||
|
||||
`status` can be combined with the [fingerprinting](#by-fingerprinting-locally-generated-files-and-their-sources)
|
||||
to have a task run if either the the source/generated artifacts changes, or the
|
||||
programmatic check fails:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:prod:
|
||||
desc: Build for production usage.
|
||||
cmds:
|
||||
- composer install
|
||||
# Run this task if source files changes.
|
||||
sources:
|
||||
- composer.json
|
||||
- composer.lock
|
||||
generates:
|
||||
- ./vendor/composer/installed.json
|
||||
- ./vendor/autoload.php
|
||||
# But also run the task if the last build was not a production build.
|
||||
status:
|
||||
- grep -q '"dev": false' ./vendor/composer/installed.json
|
||||
```
|
||||
|
||||
### Using programmatic checks to cancel the execution of a task and its dependencies
|
||||
|
||||
In addition to `status` checks, `preconditions` checks are
|
||||
@@ -877,6 +980,30 @@ If the task does not have a summary or a description, a warning is printed.
|
||||
|
||||
Please note: *showing the summary will not execute the command*.
|
||||
|
||||
## Task aliases
|
||||
|
||||
Aliases are alternative names for tasks. They can be used to make it easier and
|
||||
quicker to run tasks with long or hard-to-type names. You can use them on the
|
||||
command line, when [calling sub-tasks](#calling-another-task) in your Taskfile
|
||||
and when [including tasks](#including-other-taskfiles) with aliases from another
|
||||
Taskfile. They can also be used together with [namespace
|
||||
aliases](#namespace-aliases).
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
generate:
|
||||
aliases: [gen]
|
||||
cmds:
|
||||
- task: gen-mocks
|
||||
|
||||
generate-mocks:
|
||||
aliases: [gen-mocks]
|
||||
cmds:
|
||||
- echo "generating..."
|
||||
```
|
||||
|
||||
## Overriding task name
|
||||
|
||||
Sometimes you may want to override the task name printed on the summary, up-to-date
|
||||
@@ -1058,7 +1185,7 @@ version: '3'
|
||||
|
||||
output:
|
||||
group:
|
||||
begin: '::begin::{{.TASK}}'
|
||||
begin: '::group::{{.TASK}}'
|
||||
end: '::endgroup::'
|
||||
|
||||
tasks:
|
||||
@@ -1070,7 +1197,7 @@ tasks:
|
||||
|
||||
```bash
|
||||
$ task default
|
||||
::begin::default
|
||||
::group::default
|
||||
Hello, World!
|
||||
::endgroup::
|
||||
```
|
||||
@@ -1159,5 +1286,9 @@ With the flags `--watch` or `-w` task will watch for file changes
|
||||
and run the task again. This requires the `sources` attribute to be given,
|
||||
so task knows which files to watch.
|
||||
|
||||
The default watch interval is 5 seconds, but it's possible to change it by
|
||||
either setting `interval: '500ms'` in the root of the Taskfile passing it
|
||||
as an argument like `--interval=500ms`.
|
||||
|
||||
[gotemplate]: https://golang.org/pkg/text/template/
|
||||
[minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// @ts-check
|
||||
// Note: type annotations allow type checking and IDEs autocompletion
|
||||
|
||||
const lightCodeTheme = require('prism-react-renderer/themes/github');
|
||||
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
|
||||
const lightCodeTheme = require('./src/themes/prismLight');
|
||||
const darkCodeTheme = require('./src/themes/prismDark');
|
||||
|
||||
const GITHUB_URL = 'https://github.com/go-task/task';
|
||||
const TWITTER_URL = 'https://twitter.com/taskfiledev';
|
||||
const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
@@ -100,6 +101,11 @@ const config = {
|
||||
label: 'GitHub',
|
||||
position: 'right'
|
||||
},
|
||||
{
|
||||
href: TWITTER_URL,
|
||||
label: 'Twitter',
|
||||
position: 'right'
|
||||
},
|
||||
{
|
||||
href: DISCORD_URL,
|
||||
label: 'Discord',
|
||||
@@ -134,6 +140,10 @@ const config = {
|
||||
label: 'GitHub',
|
||||
href: GITHUB_URL
|
||||
},
|
||||
{
|
||||
label: 'Twitter',
|
||||
href: TWITTER_URL
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
href: DISCORD_URL
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,400;0,700;1,400;1,700&family=Roboto:ital,wght@0,400;0,700;1,400;1,700&display=swap');
|
||||
|
||||
:root {
|
||||
--ifm-font-family-base: Roboto, system-ui, -apple-system, Segoe UI, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
--ifm-font-family-monospace: 'Roboto Mono', SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
||||
|
||||
--ifm-color-primary: #43ABA2 ;
|
||||
--ifm-color-primary-dark: #3AB2A6;
|
||||
--ifm-color-primary-darker: #32B8AB;
|
||||
@@ -11,13 +16,6 @@
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #43ABA2 ;
|
||||
--ifm-color-primary-dark: #3AB2A6;
|
||||
--ifm-color-primary-darker: #32B8AB;
|
||||
--ifm-color-primary-darkest: #29BEB0;
|
||||
--ifm-color-primary-light: #4CA59D;
|
||||
--ifm-color-primary-lighter: #559F98;
|
||||
--ifm-color-primary-lightest: #5D9993;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
|
||||
79
docs/src/themes/prismDark.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const darkTheme = require('prism-react-renderer/themes/vsDark/index.cjs.js');
|
||||
|
||||
module.exports = {
|
||||
plain: {
|
||||
color: '#D4D4D4',
|
||||
backgroundColor: '#212121'
|
||||
},
|
||||
styles: [
|
||||
...darkTheme.styles,
|
||||
{
|
||||
types: ['title'],
|
||||
style: {
|
||||
color: '#569CD6',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['property', 'parameter'],
|
||||
style: {
|
||||
color: '#9CDCFE'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['script'],
|
||||
style: {
|
||||
color: '#D4D4D4'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['boolean', 'arrow', 'atrule', 'tag'],
|
||||
style: {
|
||||
color: '#569CD6'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['number', 'color', 'unit'],
|
||||
style: {
|
||||
color: '#B5CEA8'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['font-matter'],
|
||||
style: {
|
||||
color: '#CE9178'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['keyword', 'rule'],
|
||||
style: {
|
||||
color: '#C586C0'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['regex'],
|
||||
style: {
|
||||
color: '#D16969'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['maybe-class-name'],
|
||||
style: {
|
||||
color: '#4EC9B0'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['constant'],
|
||||
style: {
|
||||
color: '#4FC1FF'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
100
docs/src/themes/prismLight.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const lightTheme = require('prism-react-renderer/themes/github/index.cjs.js');
|
||||
|
||||
module.exports = {
|
||||
...lightTheme,
|
||||
styles: [
|
||||
...lightTheme.styles,
|
||||
{
|
||||
types: ['title'],
|
||||
style: {
|
||||
color: '#0550AE',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['parameter'],
|
||||
style: {
|
||||
color: '#953800'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['boolean', 'rule', 'color', 'number', 'constant', 'property'],
|
||||
style: {
|
||||
color: '#005CC5'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['atrule', 'tag'],
|
||||
style: {
|
||||
color: '#22863A'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['script'],
|
||||
style: {
|
||||
color: '#24292E'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['operator', 'unit', 'rule'],
|
||||
style: {
|
||||
color: '#D73A49'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['font-matter', 'string', 'attr-value'],
|
||||
style: {
|
||||
color: '#C6105F'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['class-name'],
|
||||
style: {
|
||||
color: '#116329'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['attr-name'],
|
||||
style: {
|
||||
color: '#0550AE'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['keyword'],
|
||||
style: {
|
||||
color: '#CF222E'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['function'],
|
||||
style: {
|
||||
color: '#8250DF'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['selector'],
|
||||
style: {
|
||||
color: '#6F42C1'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['variable'],
|
||||
style: {
|
||||
color: '#E36209'
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['comment'],
|
||||
style: {
|
||||
color: '#6B6B6B'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
BIN
docs/static/img/favicon.ico
vendored
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 170 KiB |
BIN
docs/static/img/logo.png
vendored
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 13 KiB |
6
docs/static/img/logo.svg
vendored
@@ -1,5 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 375 375" width="500" height="500">
|
||||
<path fill="#29beb0" d="M 187.570312 190.933594 L 187.570312 375 L 30.070312 279.535156 L 30.070312 95.464844 Z"/>
|
||||
<path fill="#69d2c8" d="M 187.570312 190.933594 L 187.570312 375 L 345.070312 279.535156 L 345.070312 95.464844 Z"/>
|
||||
<path fill="#94dfd8" d="M 187.570312 190.933594 L 30.070312 95.464844 L 187.570312 0 L 345.070312 95.464844 Z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 375 375"><path fill="#29beb0" d="M 187.570312 190.933594 L 187.570312 375 L 30.070312 279.535156 L 30.070312 95.464844 Z"/><path fill="#69d2c8" d="M 187.570312 190.933594 L 187.570312 375 L 345.070312 279.535156 L 345.070312 95.464844 Z"/><path fill="#94dfd8" d="M 187.570312 190.933594 L 30.070312 95.464844 L 187.570312 0 L 345.070312 95.464844 Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 446 B After Width: | Height: | Size: 435 B |
4
docs/static/img/logo_mono.svg
vendored
@@ -1,3 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 375 375" width="500" height="500">
|
||||
<path d="M 29.021972 281.445466 L 183.370289 375 L 183.370289 194.622441 L 29.021972 101.064089 Z M 345.978037 281.445466 L 345.978037 101.064079 L 191.629731 194.622431 L 191.629731 375 Z M 342.140723 93.731759 L 187.5 0 L 32.859297 93.731759 L 187.5 187.467349 Z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 375 375"><path d="M 29.021972 281.445466 L 183.370289 375 L 183.370289 194.622441 L 29.021972 101.064089 Z M 345.978037 281.445466 L 345.978037 101.064079 L 191.629731 194.622431 L 191.629731 375 Z M 342.140723 93.731759 L 187.5 0 L 32.859297 93.731759 L 187.5 187.467349 Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 365 B After Width: | Height: | Size: 360 B |
BIN
docs/static/img/og-image.png
vendored
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 19 KiB |
BIN
docs/static/img/pix.png
vendored
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 1.2 KiB |
15
docs/static/js/carbon.js
vendored
@@ -1,6 +1,6 @@
|
||||
(function () {
|
||||
function attachAd() {
|
||||
const el = document.createElement('script');
|
||||
var el = document.createElement('script');
|
||||
el.setAttribute('type', 'text/javascript');
|
||||
el.setAttribute('id', '_carbonads_js');
|
||||
el.setAttribute(
|
||||
@@ -9,7 +9,7 @@
|
||||
);
|
||||
el.setAttribute('async', 'async');
|
||||
|
||||
const wrapper = document.getElementById('sidebar-ads');
|
||||
var wrapper = document.getElementById('sidebar-ads');
|
||||
wrapper.innerHTML = '';
|
||||
wrapper.appendChild(el);
|
||||
}
|
||||
@@ -17,8 +17,13 @@
|
||||
setTimeout(function () {
|
||||
attachAd();
|
||||
|
||||
window.addEventListener('popstate', function () {
|
||||
attachAd();
|
||||
});
|
||||
var currentPath = window.location.pathname;
|
||||
|
||||
setInterval(function () {
|
||||
if (currentPath !== window.location.pathname) {
|
||||
currentPath = window.location.pathname;
|
||||
attachAd();
|
||||
}
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
})();
|
||||
|
||||
@@ -1577,33 +1577,46 @@
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.0":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9"
|
||||
integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
|
||||
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
|
||||
dependencies:
|
||||
"@jridgewell/set-array" "^1.0.0"
|
||||
"@jridgewell/set-array" "^1.0.1"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/resolve-uri@^3.0.3":
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe"
|
||||
integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
|
||||
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
|
||||
|
||||
"@jridgewell/set-array@^1.0.0":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea"
|
||||
integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==
|
||||
|
||||
"@jridgewell/set-array@^1.0.1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
|
||||
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
|
||||
|
||||
"@jridgewell/source-map@^0.3.2":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
|
||||
integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10":
|
||||
version "1.4.13"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c"
|
||||
integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==
|
||||
version "1.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.9":
|
||||
version "0.3.13"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea"
|
||||
integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==
|
||||
version "0.3.14"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
|
||||
integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
@@ -4960,11 +4973,6 @@ lodash.some@^4.4.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
|
||||
integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=
|
||||
|
||||
lodash.sortby@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
||||
|
||||
lodash.uniq@4.5.0, lodash.uniq@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
@@ -6815,13 +6823,6 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@~0.8.0-beta.0:
|
||||
version "0.8.0-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11"
|
||||
integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==
|
||||
dependencies:
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
space-separated-tokens@^1.0.0:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
|
||||
@@ -7036,13 +7037,13 @@ terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.1:
|
||||
terser "^5.7.2"
|
||||
|
||||
terser@^5.10.0, terser@^5.7.2:
|
||||
version "5.13.1"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.13.1.tgz#66332cdc5a01b04a224c9fad449fc1a18eaa1799"
|
||||
integrity sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==
|
||||
version "5.14.2"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
|
||||
integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
|
||||
dependencies:
|
||||
"@jridgewell/source-map" "^0.3.2"
|
||||
acorn "^8.5.0"
|
||||
commander "^2.20.0"
|
||||
source-map "~0.8.0-beta.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
text-table@^0.2.0:
|
||||
@@ -7092,13 +7093,6 @@ totalist@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
|
||||
integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==
|
||||
|
||||
tr46@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
|
||||
integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
@@ -7437,11 +7431,6 @@ webidl-conversions@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
|
||||
|
||||
webidl-conversions@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
|
||||
|
||||
webpack-bundle-analyzer@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz#1b0eea2947e73528754a6f9af3e91b2b6e0f79d5"
|
||||
@@ -7585,15 +7574,6 @@ whatwg-url@^5.0.0:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
whatwg-url@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
|
||||
integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
|
||||
dependencies:
|
||||
lodash.sortby "^4.7.0"
|
||||
tr46 "^1.0.1"
|
||||
webidl-conversions "^4.0.2"
|
||||
|
||||
which@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
|
||||
35
errors.go
@@ -3,6 +3,7 @@ package task
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
@@ -13,11 +14,37 @@ var (
|
||||
)
|
||||
|
||||
type taskNotFoundError struct {
|
||||
taskName string
|
||||
taskName string
|
||||
didYouMean string
|
||||
}
|
||||
|
||||
func (err *taskNotFoundError) Error() string {
|
||||
return fmt.Sprintf(`task: Task "%s" not found`, err.taskName)
|
||||
if err.didYouMean != "" {
|
||||
return fmt.Sprintf(
|
||||
`task: Task %q does not exist. Did you mean %q?`,
|
||||
err.taskName,
|
||||
err.didYouMean,
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`task: Task %q does not exist`, err.taskName)
|
||||
}
|
||||
|
||||
type multipleTasksWithAliasError struct {
|
||||
aliasName string
|
||||
taskNames []string
|
||||
}
|
||||
|
||||
func (err *multipleTasksWithAliasError) Error() string {
|
||||
return fmt.Sprintf(`task: Multiple tasks (%s) with alias %q found`, strings.Join(err.taskNames, ", "), err.aliasName)
|
||||
}
|
||||
|
||||
type taskInternalError struct {
|
||||
taskName string
|
||||
}
|
||||
|
||||
func (err *taskInternalError) Error() string {
|
||||
return fmt.Sprintf(`task: Task "%s" is internal`, err.taskName)
|
||||
}
|
||||
|
||||
type TaskRunError struct {
|
||||
@@ -26,7 +53,7 @@ type TaskRunError struct {
|
||||
}
|
||||
|
||||
func (err *TaskRunError) Error() string {
|
||||
return fmt.Sprintf(`task: Failed to run task "%s": %v`, err.taskName, err.err)
|
||||
return fmt.Sprintf(`task: Failed to run task %q: %v`, err.taskName, err.err)
|
||||
}
|
||||
|
||||
func (err *TaskRunError) ExitCode() int {
|
||||
@@ -46,7 +73,7 @@ type MaximumTaskCallExceededError struct {
|
||||
|
||||
func (e *MaximumTaskCallExceededError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
`task: maximum task call exceeded (%d) for task "%s": probably an cyclic dep or infinite loop`,
|
||||
`task: maximum task call exceeded (%d) for task %q: probably an cyclic dep or infinite loop`,
|
||||
MaximumTaskCall,
|
||||
e.task,
|
||||
)
|
||||
|
||||
11
go.mod
@@ -7,11 +7,13 @@ require (
|
||||
github.com/mattn/go-zglob v0.0.3
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/radovskyb/watcher v1.0.7
|
||||
github.com/sajari/fuzzy v1.0.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/stretchr/testify v1.8.0
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/sh/v3 v3.5.1
|
||||
mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -19,8 +21,9 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
)
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
44
go.sum
@@ -1,30 +1,17 @@
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 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/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
|
||||
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
@@ -34,41 +21,38 @@ github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To=
|
||||
github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
|
||||
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
||||
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
|
||||
mvdan.cc/sh/v3 v3.5.1 h1:hmP3UOw4f+EYexsJjFxvU38+kn+V/s2CclXHanIBkmQ=
|
||||
mvdan.cc/sh/v3 v3.5.1/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=
|
||||
mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899 h1:nwm4t5PtLlFd/H342GP50CtYf7vyMCOZkPx3g9shO0c=
|
||||
mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=
|
||||
|
||||
20
help.go
@@ -42,9 +42,15 @@ func (e *Executor) printTasks(listAll bool) {
|
||||
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
|
||||
|
||||
// Format in tab-separated columns with a tab stop of 8.
|
||||
w := tabwriter.NewWriter(e.Stdout, 0, 8, 0, '\t', 0)
|
||||
w := tabwriter.NewWriter(e.Stdout, 0, 8, 6, ' ', 0)
|
||||
for _, task := range tasks {
|
||||
fmt.Fprintf(w, "* %s: \t%s\n", task.Name(), task.Desc)
|
||||
e.Logger.FOutf(w, logger.Yellow, "* ")
|
||||
e.Logger.FOutf(w, logger.Green, task.Task)
|
||||
e.Logger.FOutf(w, logger.Default, ": \t%s", task.Desc)
|
||||
if len(task.Aliases) > 0 {
|
||||
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
|
||||
}
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
@@ -52,7 +58,9 @@ func (e *Executor) printTasks(listAll bool) {
|
||||
func (e *Executor) allTaskNames() (tasks []*taskfile.Task) {
|
||||
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
tasks = append(tasks, task)
|
||||
if !task.Internal {
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
}
|
||||
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
|
||||
return
|
||||
@@ -61,7 +69,7 @@ func (e *Executor) allTaskNames() (tasks []*taskfile.Task) {
|
||||
func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
|
||||
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if task.Desc != "" {
|
||||
if !task.Internal && task.Desc != "" {
|
||||
compiledTask, err := e.FastCompiledTask(taskfile.Call{Task: task.Task})
|
||||
if err == nil {
|
||||
task = compiledTask
|
||||
@@ -73,7 +81,7 @@ func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
|
||||
return
|
||||
}
|
||||
|
||||
// PrintTaskNames prints only the task names in a Taskfile.
|
||||
// ListTaskNames prints only the task names in a Taskfile.
|
||||
// Only tasks with a non-empty description are printed if allTasks is false.
|
||||
// Otherwise, all task names are printed.
|
||||
func (e *Executor) ListTaskNames(allTasks bool) {
|
||||
@@ -92,7 +100,7 @@ func (e *Executor) ListTaskNames(allTasks bool) {
|
||||
// create a string slice from all map values (*taskfile.Task)
|
||||
s := make([]string, 0, len(e.Taskfile.Tasks))
|
||||
for _, t := range e.Taskfile.Tasks {
|
||||
if allTasks || t.Desc != "" {
|
||||
if (allTasks || t.Desc != "") && !t.Internal {
|
||||
s = append(s, strings.TrimRight(t.Task, ":"))
|
||||
}
|
||||
}
|
||||
|
||||
7
init.go
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
)
|
||||
|
||||
const defaultTaskfile = `# https://taskfile.dev
|
||||
@@ -23,13 +24,13 @@ tasks:
|
||||
|
||||
// InitTaskfile Taskfile creates a new Taskfile
|
||||
func InitTaskfile(w io.Writer, dir string) error {
|
||||
f := filepath.Join(dir, "Taskfile.yaml")
|
||||
f := filepathext.SmartJoin(dir, "Taskfile.yaml")
|
||||
|
||||
if _, err := os.Stat(f); err == nil {
|
||||
return ErrTaskfileAlreadyExists
|
||||
}
|
||||
|
||||
if err := os.WriteFile(f, []byte(defaultTaskfile), 0644); err != nil {
|
||||
if err := os.WriteFile(f, []byte(defaultTaskfile), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(w, "Taskfile.yaml created in the current directory\n")
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
@@ -44,7 +44,13 @@ func (c *CompilerV3) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*ta
|
||||
func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluateShVars bool) (*taskfile.Vars, error) {
|
||||
result := compiler.GetEnviron()
|
||||
if t != nil {
|
||||
result.Set("TASK", taskfile.Var{Static: t.Task})
|
||||
specialVars, err := c.getSpecialVars(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range specialVars {
|
||||
result.Set(k, taskfile.Var{Static: v})
|
||||
}
|
||||
}
|
||||
|
||||
getRangeFunc := func(dir string) func(k string, v taskfile.Var) error {
|
||||
@@ -83,9 +89,7 @@ func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluat
|
||||
if err := tr.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !filepath.IsAbs(dir) {
|
||||
dir = filepath.Join(c.Dir, dir)
|
||||
}
|
||||
dir = filepathext.SmartJoin(c.Dir, dir)
|
||||
taskRangeFunc = getRangeFunc(dir)
|
||||
}
|
||||
|
||||
@@ -167,3 +171,23 @@ func (c *CompilerV3) ResetCache() {
|
||||
|
||||
c.dynamicCache = nil
|
||||
}
|
||||
|
||||
func (c *CompilerV3) getSpecialVars(t *taskfile.Task) (map[string]string, error) {
|
||||
taskfileDir, err := c.getTaskfileDir(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
"TASK": t.Task,
|
||||
"ROOT_DIR": c.Dir,
|
||||
"TASKFILE_DIR": taskfileDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CompilerV3) getTaskfileDir(t *taskfile.Task) (string, error) {
|
||||
if t.IncludedTaskfile != nil {
|
||||
return t.IncludedTaskfile.FullDirPath()
|
||||
}
|
||||
return c.Dir, nil
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func IsExitError(err error) bool {
|
||||
// if available.
|
||||
func Expand(s string) (string, error) {
|
||||
s = filepath.ToSlash(s)
|
||||
s = strings.Replace(s, " ", `\ `, -1)
|
||||
s = strings.ReplaceAll(s, " ", `\ `)
|
||||
fields, err := shell.Fields(s, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
31
internal/filepathext/filepathext.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package filepathext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// SmartJoin joins two paths, but only if the second is not already an
|
||||
// absolute path.
|
||||
func SmartJoin(a, b string) string {
|
||||
if filepath.IsAbs(b) {
|
||||
return b
|
||||
}
|
||||
return filepath.Join(a, b)
|
||||
}
|
||||
|
||||
// TryAbsToRel tries to convert an absolute path to relative based on the
|
||||
// process working directory. If it can't, it returns the absolute path.
|
||||
func TryAbsToRel(abs string) string {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return abs
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(wd, abs)
|
||||
if err != nil {
|
||||
return abs
|
||||
}
|
||||
|
||||
return rel
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
@@ -9,13 +11,36 @@ import (
|
||||
type Color func() PrintFunc
|
||||
type PrintFunc func(io.Writer, string, ...interface{})
|
||||
|
||||
func Default() PrintFunc { return color.New(color.Reset).FprintfFunc() }
|
||||
func Blue() PrintFunc { return color.New(color.FgBlue).FprintfFunc() }
|
||||
func Green() PrintFunc { return color.New(color.FgGreen).FprintfFunc() }
|
||||
func Cyan() PrintFunc { return color.New(color.FgCyan).FprintfFunc() }
|
||||
func Yellow() PrintFunc { return color.New(color.FgYellow).FprintfFunc() }
|
||||
func Magenta() PrintFunc { return color.New(color.FgMagenta).FprintfFunc() }
|
||||
func Red() PrintFunc { return color.New(color.FgRed).FprintfFunc() }
|
||||
func Default() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_RESET", color.Reset)).FprintfFunc()
|
||||
}
|
||||
func Blue() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_BLUE", color.FgBlue)).FprintfFunc()
|
||||
}
|
||||
func Green() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_GREEN", color.FgGreen)).FprintfFunc()
|
||||
}
|
||||
func Cyan() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_CYAN", color.FgCyan)).FprintfFunc()
|
||||
}
|
||||
func Yellow() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_YELLOW", color.FgYellow)).FprintfFunc()
|
||||
}
|
||||
func Magenta() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_MAGENTA", color.FgMagenta)).FprintfFunc()
|
||||
}
|
||||
func Red() PrintFunc {
|
||||
return color.New(envColor("TASK_COLOR_RED", color.FgRed)).FprintfFunc()
|
||||
}
|
||||
|
||||
func envColor(env string, defaultColor color.Attribute) color.Attribute {
|
||||
override, err := strconv.Atoi(os.Getenv(env))
|
||||
if err == nil {
|
||||
return color.Attribute(override)
|
||||
|
||||
}
|
||||
return defaultColor
|
||||
}
|
||||
|
||||
// Logger is just a wrapper that prints stuff to STDOUT or STDERR,
|
||||
// with optional color.
|
||||
@@ -28,6 +53,11 @@ type Logger struct {
|
||||
|
||||
// Outf prints stuff to STDOUT.
|
||||
func (l *Logger) Outf(color Color, s string, args ...interface{}) {
|
||||
l.FOutf(l.Stdout, color, s+"\n", args...)
|
||||
}
|
||||
|
||||
// FOutf prints stuff to the given writer.
|
||||
func (l *Logger) FOutf(w io.Writer, color Color, s string, args ...interface{}) {
|
||||
if len(args) == 0 {
|
||||
s, args = "%s", []interface{}{s}
|
||||
}
|
||||
@@ -35,7 +65,7 @@ func (l *Logger) Outf(color Color, s string, args ...interface{}) {
|
||||
color = Default
|
||||
}
|
||||
print := color()
|
||||
print(l.Stdout, s+"\n", args...)
|
||||
print(w, s, args...)
|
||||
}
|
||||
|
||||
// VerboseOutf prints stuff to STDOUT if verbose mode is enabled.
|
||||
|
||||
@@ -5,24 +5,24 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type Group struct{
|
||||
type Group struct {
|
||||
Begin, End string
|
||||
}
|
||||
|
||||
func (g Group) WrapWriter(w io.Writer, _ string, tmpl Templater) io.Writer {
|
||||
gw := &groupWriter{writer: w}
|
||||
func (g Group) WrapWriter(stdOut, _ io.Writer, _ string, tmpl Templater) (io.Writer, io.Writer, CloseFunc) {
|
||||
gw := &groupWriter{writer: stdOut}
|
||||
if g.Begin != "" {
|
||||
gw.begin = tmpl.Replace(g.Begin) + "\n"
|
||||
}
|
||||
if g.End != "" {
|
||||
gw.end = tmpl.Replace(g.End) + "\n"
|
||||
}
|
||||
return gw
|
||||
return gw, gw, func() error { return gw.close() }
|
||||
}
|
||||
|
||||
type groupWriter struct {
|
||||
writer io.Writer
|
||||
buff bytes.Buffer
|
||||
writer io.Writer
|
||||
buff bytes.Buffer
|
||||
begin, end string
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (gw *groupWriter) Write(p []byte) (int, error) {
|
||||
return gw.buff.Write(p)
|
||||
}
|
||||
|
||||
func (gw *groupWriter) Close() error {
|
||||
func (gw *groupWriter) close() error {
|
||||
if gw.buff.Len() == 0 {
|
||||
// don't print begin/end messages if there's no buffered entries
|
||||
return nil
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
|
||||
type Interleaved struct{}
|
||||
|
||||
func (Interleaved) WrapWriter(w io.Writer, _ string, _ Templater) io.Writer {
|
||||
return w
|
||||
func (Interleaved) WrapWriter(stdOut, stdErr io.Writer, _ string, _ Templater) (io.Writer, io.Writer, CloseFunc) {
|
||||
return stdOut, stdErr, func() error { return nil }
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@ type Templater interface {
|
||||
}
|
||||
|
||||
type Output interface {
|
||||
WrapWriter(w io.Writer, prefix string, tmpl Templater) io.Writer
|
||||
WrapWriter(stdOut, stdErr io.Writer, prefix string, tmpl Templater) (io.Writer, io.Writer, CloseFunc)
|
||||
}
|
||||
|
||||
type CloseFunc func() error
|
||||
|
||||
// Build the Output for the requested taskfile.Output.
|
||||
func BuildFor(o *taskfile.Output) (Output, error) {
|
||||
switch o.Name {
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
func TestInterleaved(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Interleaved{}
|
||||
var w = o.WrapWriter(&b, "", nil)
|
||||
var w, _, _ = o.WrapWriter(&b, io.Discard, "", nil)
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, "foo\nbar\n", b.String())
|
||||
@@ -27,14 +27,19 @@ func TestInterleaved(t *testing.T) {
|
||||
func TestGroup(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Group{}
|
||||
var w = o.WrapWriter(&b, "", nil).(io.WriteCloser)
|
||||
var stdOut, stdErr, cleanup = o.WrapWriter(&b, io.Discard, "", nil)
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
fmt.Fprintln(stdOut, "out\nout")
|
||||
assert.Equal(t, "", b.String())
|
||||
fmt.Fprintln(w, "baz")
|
||||
fmt.Fprintln(stdErr, "err\nerr")
|
||||
assert.Equal(t, "", b.String())
|
||||
assert.NoError(t, w.Close())
|
||||
assert.Equal(t, "foo\nbar\nbaz\n", b.String())
|
||||
fmt.Fprintln(stdOut, "out")
|
||||
assert.Equal(t, "", b.String())
|
||||
fmt.Fprintln(stdErr, "err")
|
||||
assert.Equal(t, "", b.String())
|
||||
|
||||
assert.NoError(t, cleanup())
|
||||
assert.Equal(t, "out\nout\nerr\nerr\nout\nerr\n", b.String())
|
||||
}
|
||||
|
||||
func TestGroupWithBeginEnd(t *testing.T) {
|
||||
@@ -53,19 +58,19 @@ func TestGroupWithBeginEnd(t *testing.T) {
|
||||
}
|
||||
t.Run("simple", func(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var w = o.WrapWriter(&b, "", &tmpl).(io.WriteCloser)
|
||||
var w, _, cleanup = o.WrapWriter(&b, io.Discard, "", &tmpl)
|
||||
|
||||
fmt.Fprintln(w, "foo\nbar")
|
||||
assert.Equal(t, "", b.String())
|
||||
fmt.Fprintln(w, "baz")
|
||||
assert.Equal(t, "", b.String())
|
||||
assert.NoError(t, w.Close())
|
||||
assert.NoError(t, cleanup())
|
||||
assert.Equal(t, "::group::example-value\nfoo\nbar\nbaz\n::endgroup::\n", b.String())
|
||||
})
|
||||
t.Run("no output", func(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var w = o.WrapWriter(&b, "", &tmpl).(io.WriteCloser)
|
||||
assert.NoError(t, w.Close())
|
||||
var _, _, cleanup = o.WrapWriter(&b, io.Discard, "", &tmpl)
|
||||
assert.NoError(t, cleanup())
|
||||
assert.Equal(t, "", b.String())
|
||||
})
|
||||
}
|
||||
@@ -73,7 +78,7 @@ func TestGroupWithBeginEnd(t *testing.T) {
|
||||
func TestPrefixed(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
var o output.Output = output.Prefixed{}
|
||||
var w = o.WrapWriter(&b, "prefix", nil).(io.WriteCloser)
|
||||
var w, _, cleanup = o.WrapWriter(&b, io.Discard, "prefix", nil)
|
||||
|
||||
t.Run("simple use cases", func(t *testing.T) {
|
||||
b.Reset()
|
||||
@@ -82,6 +87,7 @@ func TestPrefixed(t *testing.T) {
|
||||
assert.Equal(t, "[prefix] foo\n[prefix] bar\n", b.String())
|
||||
fmt.Fprintln(w, "baz")
|
||||
assert.Equal(t, "[prefix] foo\n[prefix] bar\n[prefix] baz\n", b.String())
|
||||
assert.NoError(t, cleanup())
|
||||
})
|
||||
|
||||
t.Run("multiple writes for a single line", func(t *testing.T) {
|
||||
@@ -92,7 +98,7 @@ func TestPrefixed(t *testing.T) {
|
||||
assert.Equal(t, "", b.String())
|
||||
}
|
||||
|
||||
assert.NoError(t, w.Close())
|
||||
assert.NoError(t, cleanup())
|
||||
assert.Equal(t, "[prefix] Test!\n", b.String())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
|
||||
type Prefixed struct{}
|
||||
|
||||
func (Prefixed) WrapWriter(w io.Writer, prefix string, _ Templater) io.Writer {
|
||||
return &prefixWriter{writer: w, prefix: prefix}
|
||||
func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ Templater) (io.Writer, io.Writer, CloseFunc) {
|
||||
pw := &prefixWriter{writer: stdOut, prefix: prefix}
|
||||
return pw, pw, func() error { return pw.close() }
|
||||
}
|
||||
|
||||
type prefixWriter struct {
|
||||
@@ -28,7 +29,7 @@ func (pw *prefixWriter) Write(p []byte) (int, error) {
|
||||
return n, pw.writeOutputLines(false)
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) Close() error {
|
||||
func (pw *prefixWriter) close() error {
|
||||
return pw.writeOutputLines(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,14 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
)
|
||||
|
||||
// Checksum validades if a task is up to date by calculating its source
|
||||
// files checksum
|
||||
type Checksum struct {
|
||||
BaseDir string
|
||||
TempDir string
|
||||
TaskDir string
|
||||
Task string
|
||||
Sources []string
|
||||
@@ -43,8 +45,8 @@ func (c *Checksum) IsUpToDate() (bool, error) {
|
||||
}
|
||||
|
||||
if !c.Dry {
|
||||
_ = os.MkdirAll(filepath.Join(c.BaseDir, ".task", "checksum"), 0755)
|
||||
if err = os.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
|
||||
_ = os.MkdirAll(filepathext.SmartJoin(c.TempDir, "checksum"), 0o755)
|
||||
if err = os.WriteFile(checksumFile, []byte(newMd5+"\n"), 0o644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
@@ -107,7 +109,7 @@ func (*Checksum) Kind() string {
|
||||
}
|
||||
|
||||
func (c *Checksum) checksumFilePath() string {
|
||||
return filepath.Join(c.BaseDir, ".task", "checksum", c.normalizeFilename(c.Task))
|
||||
return filepath.Join(c.TempDir, "checksum", c.normalizeFilename(c.Task))
|
||||
}
|
||||
|
||||
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
|
||||
|
||||
@@ -2,12 +2,12 @@ package status
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/mattn/go-zglob"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
)
|
||||
|
||||
func globs(dir string, globs []string) ([]string, error) {
|
||||
@@ -25,17 +25,18 @@ func globs(dir string, globs []string) ([]string, error) {
|
||||
|
||||
func Glob(dir string, g string) ([]string, error) {
|
||||
files := make([]string, 0)
|
||||
if !filepath.IsAbs(g) {
|
||||
g = filepath.Join(dir, g)
|
||||
}
|
||||
g = filepathext.SmartJoin(dir, g)
|
||||
|
||||
g, err := execext.Expand(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fs, err := zglob.Glob(g)
|
||||
|
||||
fs, err := zglob.GlobFollowSymlinks(g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, f := range fs {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
|
||||
@@ -28,6 +28,7 @@ func PrintTask(l *logger.Logger, t *taskfile.Task) {
|
||||
printTaskName(l, t)
|
||||
printTaskDescribingText(t, l)
|
||||
printTaskDependencies(l, t)
|
||||
printTaskAliases(l, t)
|
||||
printTaskCommands(l, t)
|
||||
}
|
||||
|
||||
@@ -56,10 +57,23 @@ func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
|
||||
}
|
||||
|
||||
func printTaskName(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(logger.Default, "task: %s", t.Name())
|
||||
l.FOutf(l.Stdout, logger.Default, "task: ")
|
||||
l.FOutf(l.Stdout, logger.Green, "%s\n", t.Name())
|
||||
l.Outf(logger.Default, "")
|
||||
}
|
||||
|
||||
func printTaskAliases(l *logger.Logger, t *taskfile.Task) {
|
||||
if len(t.Aliases) == 0 {
|
||||
return
|
||||
}
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "aliases:")
|
||||
for _, alias := range t.Aliases {
|
||||
l.FOutf(l.Stdout, logger.Default, " - ")
|
||||
l.Outf(logger.Cyan, alias)
|
||||
}
|
||||
}
|
||||
|
||||
func hasDescription(t *taskfile.Task) bool {
|
||||
return t.Desc != ""
|
||||
}
|
||||
@@ -94,10 +108,11 @@ func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(logger.Default, "commands:")
|
||||
for _, c := range t.Cmds {
|
||||
isCommand := c.Cmd != ""
|
||||
l.FOutf(l.Stdout, logger.Default, " - ")
|
||||
if isCommand {
|
||||
l.Outf(logger.Default, " - %s", c.Cmd)
|
||||
l.FOutf(l.Stdout, logger.Yellow, "%s\n", c.Cmd)
|
||||
} else {
|
||||
l.Outf(logger.Default, " - Task: %s", c.Task)
|
||||
l.FOutf(l.Stdout, logger.Green, "Task: %s\n", c.Task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ func init() {
|
||||
"OS": func() string { return runtime.GOOS },
|
||||
"ARCH": func() string { return runtime.GOARCH },
|
||||
"catLines": func(s string) string {
|
||||
s = strings.Replace(s, "\r\n", " ", -1)
|
||||
return strings.Replace(s, "\n", " ", -1)
|
||||
s = strings.ReplaceAll(s, "\r\n", " ")
|
||||
return strings.ReplaceAll(s, "\n", " ")
|
||||
},
|
||||
"splitLines": func(s string) []string {
|
||||
s = strings.Replace(s, "\r\n", "\n", -1)
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||
return strings.Split(s, "\n")
|
||||
},
|
||||
"fromSlash": func(path string) string {
|
||||
|
||||
32
package-lock.json
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.17.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.17.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@go-task/go-npm": "^0.1.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@go-task/go-npm": {
|
||||
"version": "0.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.15.tgz",
|
||||
"integrity": "sha512-xG+6SsSQsa6MzWML1CABWHTwHrCrBqXc/D1POoMDGIgjsRE/PB1noSBGLFhvU5DWHdPksqbAt/w9VOjbqlXpYw==",
|
||||
"bin": {
|
||||
"go-npm": "bin/index.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@go-task/go-npm": {
|
||||
"version": "0.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.15.tgz",
|
||||
"integrity": "sha512-xG+6SsSQsa6MzWML1CABWHTwHrCrBqXc/D1POoMDGIgjsRE/PB1noSBGLFhvU5DWHdPksqbAt/w9VOjbqlXpYw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
34
package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.17.0",
|
||||
"description": "A task runner / simpler Make alternative written in Go",
|
||||
"scripts": {
|
||||
"postinstall": "go-npm install",
|
||||
"preuninstall": "go-npm uninstall"
|
||||
},
|
||||
"goBinary": {
|
||||
"name": "task",
|
||||
"path": "./bin",
|
||||
"url": "https://github.com/go-task/task/releases/download/v{{version}}/task_{{platform}}_{{arch}}{{archive_ext}}"
|
||||
},
|
||||
"files": [],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/go-task/task.git"
|
||||
},
|
||||
"keywords": [
|
||||
"task",
|
||||
"taskfile",
|
||||
"build-tool",
|
||||
"task-runner"
|
||||
],
|
||||
"author": "Andrey Nering",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/go-task/task/issues"
|
||||
},
|
||||
"homepage": "https://taskfile.dev",
|
||||
"dependencies": {
|
||||
"@go-task/go-npm": "^0.1.15"
|
||||
}
|
||||
}
|
||||
322
setup.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
compilerv2 "github.com/go-task/task/v3/internal/compiler/v2"
|
||||
compilerv3 "github.com/go-task/task/v3/internal/compiler/v3"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile/read"
|
||||
|
||||
"github.com/sajari/fuzzy"
|
||||
)
|
||||
|
||||
func (e *Executor) Setup() error {
|
||||
if err := e.setCurrentDir(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.readTaskfile(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.setupFuzzyModel()
|
||||
|
||||
v, err := e.Taskfile.ParsedVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.setupTempDir(); err != nil {
|
||||
return err
|
||||
}
|
||||
e.setupStdFiles()
|
||||
e.setupLogger()
|
||||
if err := e.setupOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.setupCompiler(v); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.readDotEnvFiles(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.doVersionChecks(v); err != nil {
|
||||
return err
|
||||
}
|
||||
e.setupDefaults(v)
|
||||
e.setupConcurrencyState()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) setCurrentDir() error {
|
||||
if e.Dir == "" {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.Dir = wd
|
||||
} else if !filepath.IsAbs(e.Dir) {
|
||||
abs, err := filepath.Abs(e.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.Dir = abs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) readTaskfile() error {
|
||||
var err error
|
||||
e.Taskfile, err = read.Taskfile(&read.ReaderNode{
|
||||
Dir: e.Dir,
|
||||
Entrypoint: e.Entrypoint,
|
||||
Parent: nil,
|
||||
Optional: false,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Executor) setupFuzzyModel() {
|
||||
if e.Taskfile != nil {
|
||||
model := fuzzy.NewModel()
|
||||
model.SetThreshold(1) // because we want to build grammar based on every task name
|
||||
|
||||
var words []string
|
||||
for taskName := range e.Taskfile.Tasks {
|
||||
words = append(words, taskName)
|
||||
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
words = append(words, task.Aliases...)
|
||||
}
|
||||
}
|
||||
|
||||
model.Train(words)
|
||||
e.fuzzyModel = model
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) setupTempDir() error {
|
||||
if e.TempDir != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if os.Getenv("TASK_TEMP_DIR") == "" {
|
||||
e.TempDir = filepathext.SmartJoin(e.Dir, ".task")
|
||||
} else if filepath.IsAbs(os.Getenv("TASK_TEMP_DIR")) || strings.HasPrefix(os.Getenv("TASK_TEMP_DIR"), "~") {
|
||||
tempDir, err := execext.Expand(os.Getenv("TASK_TEMP_DIR"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectDir, _ := filepath.Abs(e.Dir)
|
||||
projectName := filepath.Base(projectDir)
|
||||
e.TempDir = filepathext.SmartJoin(tempDir, projectName)
|
||||
} else {
|
||||
e.TempDir = filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) setupStdFiles() {
|
||||
if e.Stdin == nil {
|
||||
e.Stdin = os.Stdin
|
||||
}
|
||||
if e.Stdout == nil {
|
||||
e.Stdout = os.Stdout
|
||||
}
|
||||
if e.Stderr == nil {
|
||||
e.Stderr = os.Stderr
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) setupLogger() {
|
||||
e.Logger = &logger.Logger{
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
Verbose: e.Verbose,
|
||||
Color: e.Color,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) setupOutput() error {
|
||||
if !e.OutputStyle.IsSet() {
|
||||
e.OutputStyle = e.Taskfile.Output
|
||||
}
|
||||
|
||||
var err error
|
||||
e.Output, err = output.BuildFor(&e.OutputStyle)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Executor) setupCompiler(v float64) error {
|
||||
if v < 3 {
|
||||
var err error
|
||||
e.taskvars, err = read.Taskvars(e.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.Compiler = &compilerv2.CompilerV2{
|
||||
Dir: e.Dir,
|
||||
Taskvars: e.taskvars,
|
||||
TaskfileVars: e.Taskfile.Vars,
|
||||
Expansions: e.Taskfile.Expansions,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
} else {
|
||||
e.Compiler = &compilerv3.CompilerV3{
|
||||
Dir: e.Dir,
|
||||
TaskfileEnv: e.Taskfile.Env,
|
||||
TaskfileVars: e.Taskfile.Vars,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) readDotEnvFiles(v float64) error {
|
||||
if v < 3.0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
env, err := read.Dotenv(e.Compiler, e.Taskfile, e.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = env.Range(func(key string, value taskfile.Var) error {
|
||||
if _, ok := e.Taskfile.Env.Mapping[key]; !ok {
|
||||
e.Taskfile.Env.Set(key, value)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Executor) setupDefaults(v float64) {
|
||||
// Color available only on v3
|
||||
if v < 3 {
|
||||
e.Logger.Color = false
|
||||
}
|
||||
|
||||
if e.Taskfile.Method == "" {
|
||||
if v >= 3 {
|
||||
e.Taskfile.Method = "checksum"
|
||||
} else {
|
||||
e.Taskfile.Method = "timestamp"
|
||||
}
|
||||
}
|
||||
|
||||
if e.Taskfile.Run == "" {
|
||||
e.Taskfile.Run = "always"
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) setupConcurrencyState() {
|
||||
e.executionHashes = make(map[string]context.Context)
|
||||
|
||||
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
|
||||
e.mkdirMutexMap = make(map[string]*sync.Mutex, len(e.Taskfile.Tasks))
|
||||
for k := range e.Taskfile.Tasks {
|
||||
e.taskCallCount[k] = new(int32)
|
||||
e.mkdirMutexMap[k] = &sync.Mutex{}
|
||||
}
|
||||
|
||||
if e.Concurrency > 0 {
|
||||
e.concurrencySemaphore = make(chan struct{}, e.Concurrency)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Executor) doVersionChecks(v float64) error {
|
||||
if v < 2 {
|
||||
return fmt.Errorf(`task: Taskfile versions prior to v2 are not supported anymore`)
|
||||
}
|
||||
|
||||
// consider as equal to the greater version if round
|
||||
if v == 2.0 {
|
||||
v = 2.6
|
||||
}
|
||||
if v == 3.0 {
|
||||
v = 3.8
|
||||
}
|
||||
|
||||
if v > 3.8 {
|
||||
return fmt.Errorf(`task: Taskfile versions greater than v3.8 not implemented in the version of Task`)
|
||||
}
|
||||
|
||||
if v < 2.1 && !e.Taskfile.Output.IsSet() {
|
||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
||||
}
|
||||
if v < 2.2 && e.Taskfile.Includes.Len() > 0 {
|
||||
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
|
||||
}
|
||||
if v >= 3.0 && e.Taskfile.Expansions > 2 {
|
||||
return fmt.Errorf(`task: The "expansions" setting is not available anymore on v3.0`)
|
||||
}
|
||||
if v < 3.8 && e.Taskfile.Output.Group.IsSet() {
|
||||
return fmt.Errorf(`task: Taskfile option "output.group" is only available starting on Taskfile version v3.8`)
|
||||
}
|
||||
|
||||
if v <= 2.1 {
|
||||
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
||||
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if task.IgnoreError {
|
||||
return err
|
||||
}
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd.IgnoreError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v < 2.6 {
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if len(task.Preconditions) > 0 {
|
||||
return errors.New(`task: Task option "preconditions" is only available starting on Taskfile version v2.6`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v < 3 {
|
||||
err := e.Taskfile.Includes.Range(func(_ string, taskfile taskfile.IncludedTaskfile) error {
|
||||
if taskfile.AdvancedImport {
|
||||
return errors.New(`task: Import with additional parameters is only available starting on Taskfile version v3`)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v < 3.7 {
|
||||
if e.Taskfile.Run != "" {
|
||||
return errors.New(`task: Setting the "run" type is only available starting on Taskfile version v3.7`)
|
||||
}
|
||||
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if task.Run != "" {
|
||||
return errors.New(`task: Setting the "run" type is only available starting on Taskfile version v3.7`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// NOTE(@andreynering): This function intercepts SIGINT and SIGTERM signals
|
||||
// so the Task process is not killed immediatelly and processes running have
|
||||
// so the Task process is not killed immediately and processes running have
|
||||
// time to do cleanup work.
|
||||
func (e *Executor) InterceptInterruptSignals() {
|
||||
ch := make(chan os.Signal, 3)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
//go:build signals
|
||||
// +build signals
|
||||
|
||||
// This file contains tests for signal handling on Unix.
|
||||
// Based on code from https://github.com/marco-m/timeit
|
||||
@@ -95,7 +95,7 @@ func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker {
|
||||
|
||||
func (e *Executor) checksumChecker(t *taskfile.Task) status.Checker {
|
||||
return &status.Checksum{
|
||||
BaseDir: e.Dir,
|
||||
TempDir: e.TempDir,
|
||||
TaskDir: t.Dir,
|
||||
Task: t.Name(),
|
||||
Sources: t.Sources,
|
||||
|
||||
282
task.go
@@ -2,7 +2,6 @@ package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -10,16 +9,15 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
compilerv2 "github.com/go-task/task/v3/internal/compiler/v2"
|
||||
compilerv3 "github.com/go-task/task/v3/internal/compiler/v3"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/internal/summary"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile/read"
|
||||
|
||||
"github.com/sajari/fuzzy"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -34,6 +32,7 @@ type Executor struct {
|
||||
Taskfile *taskfile.Taskfile
|
||||
|
||||
Dir string
|
||||
TempDir string
|
||||
Entrypoint string
|
||||
Force bool
|
||||
Watch bool
|
||||
@@ -44,6 +43,7 @@ type Executor struct {
|
||||
Parallel bool
|
||||
Color bool
|
||||
Concurrency int
|
||||
Interval string
|
||||
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
@@ -54,7 +54,8 @@ type Executor struct {
|
||||
Output output.Output
|
||||
OutputStyle taskfile.Output
|
||||
|
||||
taskvars *taskfile.Vars
|
||||
taskvars *taskfile.Vars
|
||||
fuzzyModel *fuzzy.Model
|
||||
|
||||
concurrencySemaphore chan struct{}
|
||||
taskCallCount map[string]*int32
|
||||
@@ -66,11 +67,16 @@ type Executor struct {
|
||||
// Run runs Task
|
||||
func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||
// check if given tasks exist
|
||||
for _, c := range calls {
|
||||
if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
|
||||
// FIXME: move to the main package
|
||||
for _, call := range calls {
|
||||
task, err := e.GetTask(call)
|
||||
if err != nil {
|
||||
e.ListTasksWithDesc()
|
||||
return &taskNotFoundError{taskName: c.Task}
|
||||
return err
|
||||
}
|
||||
|
||||
if task.Internal {
|
||||
e.ListTasksWithDesc()
|
||||
return &taskInternalError{taskName: call.Task}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,211 +110,14 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// readTaskfile selects and parses the entrypoint.
|
||||
func (e *Executor) readTaskfile() error {
|
||||
var err error
|
||||
e.Taskfile, err = read.Taskfile(&read.ReaderNode{
|
||||
Dir: e.Dir,
|
||||
Entrypoint: e.Entrypoint,
|
||||
Parent: nil,
|
||||
Optional: false,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup setups Executor's internal state
|
||||
func (e *Executor) Setup() error {
|
||||
err := e.readTaskfile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := e.Taskfile.ParsedVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v < 3.0 {
|
||||
e.taskvars, err = read.Taskvars(e.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if e.Stdin == nil {
|
||||
e.Stdin = os.Stdin
|
||||
}
|
||||
if e.Stdout == nil {
|
||||
e.Stdout = os.Stdout
|
||||
}
|
||||
if e.Stderr == nil {
|
||||
e.Stderr = os.Stderr
|
||||
}
|
||||
e.Logger = &logger.Logger{
|
||||
Stdout: e.Stdout,
|
||||
Stderr: e.Stderr,
|
||||
Verbose: e.Verbose,
|
||||
Color: e.Color,
|
||||
}
|
||||
|
||||
if v < 2 {
|
||||
return fmt.Errorf(`task: Taskfile versions prior to v2 are not supported anymore`)
|
||||
}
|
||||
|
||||
// consider as equal to the greater version if round
|
||||
if v == 2.0 {
|
||||
v = 2.6
|
||||
}
|
||||
if v == 3.0 {
|
||||
v = 3.8
|
||||
}
|
||||
|
||||
if v > 3.8 {
|
||||
return fmt.Errorf(`task: Taskfile versions greater than v3.8 not implemented in the version of Task`)
|
||||
}
|
||||
|
||||
// Color available only on v3
|
||||
if v < 3 {
|
||||
e.Logger.Color = false
|
||||
}
|
||||
|
||||
if v < 3 {
|
||||
e.Compiler = &compilerv2.CompilerV2{
|
||||
Dir: e.Dir,
|
||||
Taskvars: e.taskvars,
|
||||
TaskfileVars: e.Taskfile.Vars,
|
||||
Expansions: e.Taskfile.Expansions,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
} else {
|
||||
e.Compiler = &compilerv3.CompilerV3{
|
||||
Dir: e.Dir,
|
||||
TaskfileEnv: e.Taskfile.Env,
|
||||
TaskfileVars: e.Taskfile.Vars,
|
||||
Logger: e.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
if v >= 3.0 {
|
||||
env, err := read.Dotenv(e.Compiler, e.Taskfile, e.Dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = env.Range(func(key string, value taskfile.Var) error {
|
||||
if _, ok := e.Taskfile.Env.Mapping[key]; !ok {
|
||||
e.Taskfile.Env.Set(key, value)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v < 2.1 && !e.Taskfile.Output.IsSet() {
|
||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
||||
}
|
||||
if v < 2.2 && e.Taskfile.Includes.Len() > 0 {
|
||||
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
|
||||
}
|
||||
if v >= 3.0 && e.Taskfile.Expansions > 2 {
|
||||
return fmt.Errorf(`task: The "expansions" setting is not available anymore on v3.0`)
|
||||
}
|
||||
if v < 3.8 && e.Taskfile.Output.Group.IsSet() {
|
||||
return fmt.Errorf(`task: Taskfile option "output.group" is only available starting on Taskfile version v3.8`)
|
||||
}
|
||||
|
||||
if !e.OutputStyle.IsSet() {
|
||||
e.OutputStyle = e.Taskfile.Output
|
||||
}
|
||||
e.Output, err = output.BuildFor(&e.OutputStyle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.Taskfile.Method == "" {
|
||||
if v >= 3 {
|
||||
e.Taskfile.Method = "checksum"
|
||||
} else {
|
||||
e.Taskfile.Method = "timestamp"
|
||||
}
|
||||
}
|
||||
|
||||
if v <= 2.1 {
|
||||
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
||||
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if task.IgnoreError {
|
||||
return err
|
||||
}
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd.IgnoreError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v < 2.6 {
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if len(task.Preconditions) > 0 {
|
||||
return errors.New(`task: Task option "preconditions" is only available starting on Taskfile version v2.6`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v < 3 {
|
||||
err := e.Taskfile.Includes.Range(func(_ string, taskfile taskfile.IncludedTaskfile) error {
|
||||
if taskfile.AdvancedImport {
|
||||
return errors.New(`task: Import with additional parameters is only available starting on Taskfile version v3`)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v < 3.7 {
|
||||
if e.Taskfile.Run != "" {
|
||||
return errors.New(`task: Setting the "run" type is only available starting on Taskfile version v3.7`)
|
||||
}
|
||||
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if task.Run != "" {
|
||||
return errors.New(`task: Setting the "run" type is only available starting on Taskfile version v3.7`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if e.Taskfile.Run == "" {
|
||||
e.Taskfile.Run = "always"
|
||||
}
|
||||
|
||||
e.executionHashes = make(map[string]context.Context)
|
||||
|
||||
e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
|
||||
e.mkdirMutexMap = make(map[string]*sync.Mutex, len(e.Taskfile.Tasks))
|
||||
for k := range e.Taskfile.Tasks {
|
||||
e.taskCallCount[k] = new(int32)
|
||||
e.mkdirMutexMap[k] = &sync.Mutex{}
|
||||
}
|
||||
|
||||
if e.Concurrency > 0 {
|
||||
e.concurrencySemaphore = make(chan struct{}, e.Concurrency)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunTask runs a task by its name
|
||||
func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||
t, err := e.CompiledTask(call)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !e.Watch && atomic.AddInt32(e.taskCallCount[call.Task], 1) >= MaximumTaskCall {
|
||||
return &MaximumTaskCallExceededError{task: call.Task}
|
||||
if !e.Watch && atomic.AddInt32(e.taskCallCount[t.Task], 1) >= MaximumTaskCall {
|
||||
return &MaximumTaskCallExceededError{task: t.Task}
|
||||
}
|
||||
|
||||
release := e.acquireConcurrencyLimit()
|
||||
@@ -381,7 +190,7 @@ func (e *Executor) mkdir(t *taskfile.Task) error {
|
||||
defer mutex.Unlock()
|
||||
|
||||
if _, err := os.Stat(t.Dir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(t.Dir, 0755); err != nil {
|
||||
if err := os.MkdirAll(t.Dir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -449,19 +258,10 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
||||
if err != nil {
|
||||
return fmt.Errorf("task: failed to get variables: %w", err)
|
||||
}
|
||||
stdOut := outputWrapper.WrapWriter(e.Stdout, t.Prefix, outputTemplater)
|
||||
stdErr := outputWrapper.WrapWriter(e.Stderr, t.Prefix, outputTemplater)
|
||||
|
||||
stdOut, stdErr, close := outputWrapper.WrapWriter(e.Stdout, e.Stderr, t.Prefix, outputTemplater)
|
||||
defer func() {
|
||||
if _, ok := stdOut.(*os.File); !ok {
|
||||
if closer, ok := stdOut.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
if _, ok := stdErr.(*os.File); !ok {
|
||||
if closer, ok := stdErr.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
if err := close(); err != nil {
|
||||
e.Logger.Errf(logger.Red, "task: unable to close writter: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -534,3 +334,43 @@ func (e *Executor) startExecution(ctx context.Context, t *taskfile.Task, execute
|
||||
|
||||
return execute(ctx)
|
||||
}
|
||||
|
||||
// GetTask will return the task with the name matching the given call from the taskfile.
|
||||
// If no task is found, it will search for tasks with a matching alias.
|
||||
// If multiple tasks contain the same alias or no matches are found an error is returned.
|
||||
func (e *Executor) GetTask(call taskfile.Call) (*taskfile.Task, error) {
|
||||
// Search for a matching task
|
||||
matchingTask, ok := e.Taskfile.Tasks[call.Task]
|
||||
if ok {
|
||||
return matchingTask, nil
|
||||
}
|
||||
|
||||
// If didn't find one, search for a task with a matching alias
|
||||
var aliasedTasks []string
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if slices.Contains(task.Aliases, call.Task) {
|
||||
aliasedTasks = append(aliasedTasks, task.Task)
|
||||
matchingTask = task
|
||||
}
|
||||
}
|
||||
// If we found multiple tasks
|
||||
if len(aliasedTasks) > 1 {
|
||||
return nil, &multipleTasksWithAliasError{
|
||||
aliasName: call.Task,
|
||||
taskNames: aliasedTasks,
|
||||
}
|
||||
}
|
||||
// If we found no tasks
|
||||
if len(aliasedTasks) == 0 {
|
||||
didYouMean := ""
|
||||
if e.fuzzyModel != nil {
|
||||
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
||||
}
|
||||
return nil, &taskNotFoundError{
|
||||
taskName: call.Task,
|
||||
didYouMean: didYouMean,
|
||||
}
|
||||
}
|
||||
|
||||
return matchingTask, nil
|
||||
}
|
||||
|
||||
402
task_test.go
@@ -10,10 +10,12 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
@@ -37,11 +39,12 @@ func (fct fileContentTest) name(file string) string {
|
||||
|
||||
func (fct fileContentTest) Run(t *testing.T) {
|
||||
for f := range fct.Files {
|
||||
_ = os.Remove(filepath.Join(fct.Dir, f))
|
||||
_ = os.Remove(filepathext.SmartJoin(fct.Dir, f))
|
||||
}
|
||||
|
||||
e := &task.Executor{
|
||||
Dir: fct.Dir,
|
||||
TempDir: filepathext.SmartJoin(fct.Dir, ".task"),
|
||||
Entrypoint: fct.Entrypoint,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
@@ -51,13 +54,14 @@ func (fct fileContentTest) Run(t *testing.T) {
|
||||
|
||||
for name, expectContent := range fct.Files {
|
||||
t.Run(fct.name(name), func(t *testing.T) {
|
||||
b, err := os.ReadFile(filepath.Join(fct.Dir, name))
|
||||
path := filepathext.SmartJoin(fct.Dir, name)
|
||||
b, err := os.ReadFile(path)
|
||||
assert.NoError(t, err, "Error reading file")
|
||||
s := string(b)
|
||||
if fct.TrimSpace {
|
||||
s = strings.TrimSpace(s)
|
||||
}
|
||||
assert.Equal(t, expectContent, s, "unexpected file content")
|
||||
assert.Equal(t, expectContent, s, "unexpected file content in %s", path)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -161,6 +165,39 @@ func TestMultilineVars(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecialVars(t *testing.T) {
|
||||
const dir = "testdata/special_vars"
|
||||
const target = "default"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: target}))
|
||||
|
||||
toAbs := func(rel string) string {
|
||||
abs, err := filepath.Abs(rel)
|
||||
assert.NoError(t, err)
|
||||
return abs
|
||||
}
|
||||
|
||||
output := buff.String()
|
||||
|
||||
// Root Taskfile
|
||||
assert.Contains(t, output, "root/TASK=print")
|
||||
assert.Contains(t, output, "root/ROOT_DIR="+toAbs("testdata/special_vars"))
|
||||
assert.Contains(t, output, "root/TASKFILE_DIR="+toAbs("testdata/special_vars"))
|
||||
|
||||
// Included Taskfile
|
||||
assert.Contains(t, output, "included/TASK=included:print")
|
||||
assert.Contains(t, output, "included/ROOT_DIR="+toAbs("testdata/special_vars"))
|
||||
assert.Contains(t, output, "included/TASKFILE_DIR="+toAbs("testdata/special_vars/included"))
|
||||
}
|
||||
|
||||
func TestVarsInvalidTmpl(t *testing.T) {
|
||||
const (
|
||||
dir = "testdata/vars/v2"
|
||||
@@ -233,7 +270,7 @@ func TestDeps(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
_ = os.Remove(filepath.Join(dir, f))
|
||||
_ = os.Remove(filepathext.SmartJoin(dir, f))
|
||||
}
|
||||
|
||||
e := &task.Executor{
|
||||
@@ -245,7 +282,7 @@ func TestDeps(t *testing.T) {
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
|
||||
for _, f := range files {
|
||||
f = filepath.Join(dir, f)
|
||||
f = filepathext.SmartJoin(dir, f)
|
||||
if _, err := os.Stat(f); err != nil {
|
||||
t.Errorf("File %s should exist", f)
|
||||
}
|
||||
@@ -261,7 +298,7 @@ func TestStatus(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
path := filepath.Join(dir, f)
|
||||
path := filepathext.SmartJoin(dir, f)
|
||||
_ = os.Remove(path)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
t.Errorf("File should not exist: %v", err)
|
||||
@@ -270,17 +307,18 @@ func TestStatus(t *testing.T) {
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
Dir: dir,
|
||||
TempDir: filepathext.SmartJoin(dir, ".task"),
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-foo"}))
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "gen-bar"}))
|
||||
|
||||
for _, f := range files {
|
||||
if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
|
||||
if _, err := os.Stat(filepathext.SmartJoin(dir, f)); err != nil {
|
||||
t.Errorf("File should exist: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -357,10 +395,10 @@ func TestGenerates(t *testing.T) {
|
||||
fileWithSpaces = "my text file.txt"
|
||||
)
|
||||
|
||||
var srcFile = filepath.Join(dir, srcTask)
|
||||
var srcFile = filepathext.SmartJoin(dir, srcTask)
|
||||
|
||||
for _, task := range []string{srcTask, relTask, absTask, fileWithSpaces} {
|
||||
path := filepath.Join(dir, task)
|
||||
path := filepathext.SmartJoin(dir, task)
|
||||
_ = os.Remove(path)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
t.Errorf("File should not exist: %v", err)
|
||||
@@ -376,7 +414,7 @@ func TestGenerates(t *testing.T) {
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
for _, theTask := range []string{relTask, absTask, fileWithSpaces} {
|
||||
var destFile = filepath.Join(dir, theTask)
|
||||
var destFile = filepathext.SmartJoin(dir, theTask)
|
||||
var upToDate = fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) +
|
||||
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
|
||||
|
||||
@@ -413,12 +451,38 @@ func TestStatusChecksum(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
_ = os.Remove(filepath.Join(dir, f))
|
||||
_ = os.Remove(filepathext.SmartJoin(dir, f))
|
||||
|
||||
_, err := os.Stat(filepath.Join(dir, f))
|
||||
_, err := os.Stat(filepathext.SmartJoin(dir, f))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
TempDir: filepathext.SmartJoin(dir, ".task"),
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||
for _, f := range files {
|
||||
_, err := os.Stat(filepathext.SmartJoin(dir, f))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
buff.Reset()
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||
}
|
||||
|
||||
func TestAlias(t *testing.T) {
|
||||
const dir = "testdata/alias"
|
||||
|
||||
data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias.txt"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
@@ -426,16 +490,43 @@ func TestStatusChecksum(t *testing.T) {
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "f"}))
|
||||
assert.Equal(t, string(data), buff.String())
|
||||
}
|
||||
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||
for _, f := range files {
|
||||
_, err := os.Stat(filepath.Join(dir, f))
|
||||
assert.NoError(t, err)
|
||||
func TestDuplicateAlias(t *testing.T) {
|
||||
const dir = "testdata/alias"
|
||||
|
||||
data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias-duplicate.txt"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "x"}))
|
||||
assert.Equal(t, string(data), buff.String())
|
||||
}
|
||||
|
||||
buff.Reset()
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"}))
|
||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||
func TestAliasSummary(t *testing.T) {
|
||||
const dir = "testdata/alias"
|
||||
|
||||
data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias-summary.txt"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Summary: true,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "f"}))
|
||||
assert.Equal(t, string(data), buff.String())
|
||||
}
|
||||
|
||||
func TestLabelUpToDate(t *testing.T) {
|
||||
@@ -508,7 +599,7 @@ func TestLabelInSummary(t *testing.T) {
|
||||
assert.Contains(t, buff.String(), "foobar")
|
||||
}
|
||||
|
||||
func TestLabelInList(t *testing.T) {
|
||||
func TestNoLabelInList(t *testing.T) {
|
||||
const dir = "testdata/label_list"
|
||||
|
||||
var buff bytes.Buffer
|
||||
@@ -519,7 +610,7 @@ func TestLabelInList(t *testing.T) {
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
e.ListTasksWithDesc()
|
||||
assert.Contains(t, buff.String(), "foobar")
|
||||
assert.Contains(t, buff.String(), "foo")
|
||||
}
|
||||
|
||||
// task -al case 1: listAll list all tasks
|
||||
@@ -573,12 +664,13 @@ func TestListCanListDescOnly(t *testing.T) {
|
||||
func TestStatusVariables(t *testing.T) {
|
||||
const dir = "testdata/status_vars"
|
||||
|
||||
_ = os.RemoveAll(filepath.Join(dir, ".task"))
|
||||
_ = os.Remove(filepath.Join(dir, "generated.txt"))
|
||||
_ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
||||
_ = os.Remove(filepathext.SmartJoin(dir, "generated.txt"))
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
TempDir: filepathext.SmartJoin(dir, ".task"),
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: false,
|
||||
@@ -589,7 +681,7 @@ func TestStatusVariables(t *testing.T) {
|
||||
|
||||
assert.Contains(t, buff.String(), "d41d8cd98f00b204e9800998ecf8427e")
|
||||
|
||||
inf, err := os.Stat(filepath.Join(dir, "source.txt"))
|
||||
inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt"))
|
||||
assert.NoError(t, err)
|
||||
ts := fmt.Sprintf("%d", inf.ModTime().Unix())
|
||||
tf := inf.ModTime().String()
|
||||
@@ -600,7 +692,7 @@ func TestStatusVariables(t *testing.T) {
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
const dir = "testdata/init"
|
||||
var file = filepath.Join(dir, "Taskfile.yaml")
|
||||
var file = filepathext.SmartJoin(dir, "Taskfile.yaml")
|
||||
|
||||
_ = os.Remove(file)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
@@ -689,7 +781,7 @@ func TestExpand(t *testing.T) {
|
||||
func TestDry(t *testing.T) {
|
||||
const dir = "testdata/dry"
|
||||
|
||||
file := filepath.Join(dir, "file.txt")
|
||||
file := filepathext.SmartJoin(dir, "file.txt")
|
||||
_ = os.Remove(file)
|
||||
|
||||
var buff bytes.Buffer
|
||||
@@ -714,14 +806,15 @@ func TestDry(t *testing.T) {
|
||||
func TestDryChecksum(t *testing.T) {
|
||||
const dir = "testdata/dry_checksum"
|
||||
|
||||
checksumFile := filepath.Join(dir, ".task/checksum/default")
|
||||
checksumFile := filepathext.SmartJoin(dir, ".task/checksum/default")
|
||||
_ = os.Remove(checksumFile)
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
Dry: true,
|
||||
Dir: dir,
|
||||
TempDir: filepathext.SmartJoin(dir, ".task"),
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
Dry: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
|
||||
@@ -769,7 +862,6 @@ func TestIncludesMultiLevel(t *testing.T) {
|
||||
|
||||
func TestIncludeCycle(t *testing.T) {
|
||||
const dir = "testdata/includes_cycle"
|
||||
expectedError := "task: include cycle detected between testdata/includes_cycle/Taskfile.yml <--> testdata/includes_cycle/one/two/Taskfile.yml"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
@@ -779,7 +871,9 @@ func TestIncludeCycle(t *testing.T) {
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
assert.EqualError(t, e.Setup(), expectedError)
|
||||
err := e.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "task: include cycle detected between")
|
||||
}
|
||||
|
||||
func TestIncorrectVersionIncludes(t *testing.T) {
|
||||
@@ -797,6 +891,22 @@ func TestIncorrectVersionIncludes(t *testing.T) {
|
||||
assert.EqualError(t, e.Setup(), expectedError)
|
||||
}
|
||||
|
||||
func TestIncludesIncorrect(t *testing.T) {
|
||||
const dir = "testdata/includes_incorrect"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
|
||||
err := e.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "task: Failed to parse testdata/includes_incorrect/incomplete.yml:")
|
||||
}
|
||||
|
||||
func TestIncludesEmptyMain(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/includes_empty",
|
||||
@@ -847,27 +957,39 @@ func TestIncludesOptional(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIncludesOptionalImplicitFalse(t *testing.T) {
|
||||
const dir = "testdata/includes_optional_implicit_false"
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
message := "stat %s/%s/TaskfileOptional.yml: no such file or directory"
|
||||
expected := fmt.Sprintf(message, wd, dir)
|
||||
|
||||
e := task.Executor{
|
||||
Dir: "testdata/includes_optional_implicit_false",
|
||||
Dir: dir,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
|
||||
err := e.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "stat testdata/includes_optional_implicit_false/TaskfileOptional.yml: no such file or directory", err.Error())
|
||||
assert.Equal(t, expected, err.Error())
|
||||
}
|
||||
|
||||
func TestIncludesOptionalExplicitFalse(t *testing.T) {
|
||||
const dir = "testdata/includes_optional_explicit_false"
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
message := "stat %s/%s/TaskfileOptional.yml: no such file or directory"
|
||||
expected := fmt.Sprintf(message, wd, dir)
|
||||
|
||||
e := task.Executor{
|
||||
Dir: "testdata/includes_optional_explicit_false",
|
||||
Dir: dir,
|
||||
Stdout: io.Discard,
|
||||
Stderr: io.Discard,
|
||||
}
|
||||
|
||||
err := e.Setup()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "stat testdata/includes_optional_explicit_false/TaskfileOptional.yml: no such file or directory", err.Error())
|
||||
assert.Equal(t, expected, err.Error())
|
||||
}
|
||||
|
||||
func TestIncludesFromCustomTaskfile(t *testing.T) {
|
||||
@@ -885,6 +1007,106 @@ func TestIncludesFromCustomTaskfile(t *testing.T) {
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestIncludesRelativePath(t *testing.T) {
|
||||
const dir = "testdata/includes_rel_path"
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "common:pwd"}))
|
||||
assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
|
||||
|
||||
buff.Reset()
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "included:common:pwd"}))
|
||||
assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
|
||||
}
|
||||
|
||||
func TestIncludesInternal(t *testing.T) {
|
||||
const dir = "testdata/internal_task"
|
||||
tests := []struct {
|
||||
name string
|
||||
task string
|
||||
expectedErr bool
|
||||
expectedOutput string
|
||||
}{
|
||||
{"included internal task via task", "task-1", false, "Hello, World!\n"},
|
||||
{"included internal task via dep", "task-2", false, "Hello, World!\n"},
|
||||
{
|
||||
"included internal direct",
|
||||
"included:task-3",
|
||||
true,
|
||||
"task: No tasks with description available. Try --list-all to list all tasks\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
err := e.Run(context.Background(), taskfile.Call{Task: test.task})
|
||||
if test.expectedErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, test.expectedOutput, buff.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInternalTask(t *testing.T) {
|
||||
const dir = "testdata/internal_task"
|
||||
tests := []struct {
|
||||
name string
|
||||
task string
|
||||
expectedErr bool
|
||||
expectedOutput string
|
||||
}{
|
||||
{"internal task via task", "task-1", false, "Hello, World!\n"},
|
||||
{"internal task via dep", "task-2", false, "Hello, World!\n"},
|
||||
{
|
||||
"internal direct",
|
||||
"task-3",
|
||||
true,
|
||||
"task: No tasks with description available. Try --list-all to list all tasks\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: true,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
|
||||
err := e.Run(context.Background(), taskfile.Call{Task: test.task})
|
||||
if test.expectedErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, test.expectedOutput, buff.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportedFileNames(t *testing.T) {
|
||||
fileNames := []string{
|
||||
"Taskfile.yml",
|
||||
@@ -921,12 +1143,12 @@ func TestSummary(t *testing.T) {
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-with-summary"}, taskfile.Call{Task: "other-task-with-summary"}))
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(dir, "task-with-summary.txt"))
|
||||
data, err := os.ReadFile(filepathext.SmartJoin(dir, "task-with-summary.txt"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedOutput := string(data)
|
||||
if runtime.GOOS == "windows" {
|
||||
expectedOutput = strings.Replace(expectedOutput, "\r\n", "\n", -1)
|
||||
expectedOutput = strings.ReplaceAll(expectedOutput, "\r\n", "\n")
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedOutput, buff.String())
|
||||
@@ -1294,3 +1516,97 @@ func TestErrorCode(t *testing.T) {
|
||||
assert.True(t, ok, "cannot cast returned error to *task.TaskRunError")
|
||||
assert.Equal(t, 42, casted.ExitCode(), "unexpected exit code from task")
|
||||
}
|
||||
|
||||
func TestEvaluateSymlinksInPaths(t *testing.T) {
|
||||
const dir = "testdata/evaluate_symlinks_in_paths"
|
||||
var buff bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Silent: false,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
err := e.Run(context.Background(), taskfile.Call{Task: "default"})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, `task: Task "default" is up to date`, strings.TrimSpace(buff.String()))
|
||||
buff.Reset()
|
||||
err = e.Run(context.Background(), taskfile.Call{Task: "test-sym"})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, `task: Task "test-sym" is up to date`, strings.TrimSpace(buff.String()))
|
||||
buff.Reset()
|
||||
err = e.Run(context.Background(), taskfile.Call{Task: "default"})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, `task: Task "default" is up to date`, strings.TrimSpace(buff.String()))
|
||||
buff.Reset()
|
||||
err = e.Run(context.Background(), taskfile.Call{Task: "default"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `task: Task "default" is up to date`, strings.TrimSpace(buff.String()))
|
||||
buff.Reset()
|
||||
err = e.Run(context.Background(), taskfile.Call{Task: "reset"})
|
||||
assert.NoError(t, err)
|
||||
buff.Reset()
|
||||
err = os.RemoveAll(dir + "/.task")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFileWatcherInterval(t *testing.T) {
|
||||
const dir = "testdata/watcher_interval"
|
||||
expectedOutput := strings.TrimSpace(`
|
||||
task: Started watching for tasks: default
|
||||
task: [default] echo "Hello, World!"
|
||||
Hello, World!
|
||||
task: [default] echo "Hello, World!"
|
||||
Hello, World!
|
||||
`)
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := &task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
Watch: true,
|
||||
}
|
||||
|
||||
assert.NoError(t, e.Setup())
|
||||
buff.Reset()
|
||||
|
||||
err := os.MkdirAll(filepathext.SmartJoin(dir, "src"), 0755)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.WriteFile(filepathext.SmartJoin(dir, "src/a"), []byte("test"), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
go func(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
err := e.Run(ctx, taskfile.Call{Task: "default"})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(ctx)
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
err = os.WriteFile(filepathext.SmartJoin(dir, "src/a"), []byte("test updated"), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(700 * time.Millisecond)
|
||||
cancel()
|
||||
assert.Equal(t, expectedOutput, strings.TrimSpace(buff.String()))
|
||||
buff.Reset()
|
||||
err = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(filepathext.SmartJoin(dir, "src"))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
23
taskfile/copy.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package taskfile
|
||||
|
||||
import "golang.org/x/exp/constraints"
|
||||
|
||||
func deepCopySlice[T any](orig []T) []T {
|
||||
if orig == nil {
|
||||
return nil
|
||||
}
|
||||
c := make([]T, len(orig))
|
||||
copy(c, orig)
|
||||
return c
|
||||
}
|
||||
|
||||
func deepCopyMap[K constraints.Ordered, V any](orig map[K]V) map[K]V {
|
||||
if orig == nil {
|
||||
return nil
|
||||
}
|
||||
c := make(map[K]V, len(orig))
|
||||
for k, v := range orig {
|
||||
c[k] = v
|
||||
}
|
||||
return c
|
||||
}
|
||||
@@ -2,17 +2,26 @@ package taskfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// IncludedTaskfile represents information about included tasksfile
|
||||
// IncludedTaskfile represents information about included taskfiles
|
||||
type IncludedTaskfile struct {
|
||||
Taskfile string
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Aliases []string
|
||||
AdvancedImport bool
|
||||
Vars *Vars
|
||||
BaseDir string // The directory from which the including taskfile was loaded; used to resolve relative paths
|
||||
}
|
||||
|
||||
// IncludedTaskfiles represents information about included tasksfiles
|
||||
@@ -64,7 +73,7 @@ func (tfs *IncludedTaskfiles) Set(key string, includedTaskfile IncludedTaskfile)
|
||||
if tfs.Mapping == nil {
|
||||
tfs.Mapping = make(map[string]IncludedTaskfile, 1)
|
||||
}
|
||||
if !stringSliceContains(tfs.Keys, key) {
|
||||
if !slices.Contains(tfs.Keys, key) {
|
||||
tfs.Keys = append(tfs.Keys, key)
|
||||
}
|
||||
tfs.Mapping[key] = includedTaskfile
|
||||
@@ -95,6 +104,8 @@ func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) err
|
||||
Taskfile string
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Aliases []string
|
||||
Vars *Vars
|
||||
}
|
||||
if err := unmarshal(&includedTaskfile); err != nil {
|
||||
@@ -103,7 +114,54 @@ func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) err
|
||||
it.Taskfile = includedTaskfile.Taskfile
|
||||
it.Dir = includedTaskfile.Dir
|
||||
it.Optional = includedTaskfile.Optional
|
||||
it.Internal = includedTaskfile.Internal
|
||||
it.Aliases = includedTaskfile.Aliases
|
||||
it.AdvancedImport = true
|
||||
it.Vars = includedTaskfile.Vars
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of IncludedTaskfile and copies
|
||||
// data by value from the source struct.
|
||||
func (it *IncludedTaskfile) DeepCopy() *IncludedTaskfile {
|
||||
if it == nil {
|
||||
return nil
|
||||
}
|
||||
return &IncludedTaskfile{
|
||||
Taskfile: it.Taskfile,
|
||||
Dir: it.Dir,
|
||||
Optional: it.Optional,
|
||||
Internal: it.Internal,
|
||||
AdvancedImport: it.AdvancedImport,
|
||||
Vars: it.Vars.DeepCopy(),
|
||||
BaseDir: it.BaseDir,
|
||||
}
|
||||
}
|
||||
|
||||
// FullTaskfilePath returns the fully qualified path to the included taskfile
|
||||
func (it *IncludedTaskfile) FullTaskfilePath() (string, error) {
|
||||
return it.resolvePath(it.Taskfile)
|
||||
}
|
||||
|
||||
// FullDirPath returns the fully qualified path to the included taskfile's working directory
|
||||
func (it *IncludedTaskfile) FullDirPath() (string, error) {
|
||||
return it.resolvePath(it.Dir)
|
||||
}
|
||||
|
||||
func (it *IncludedTaskfile) resolvePath(path string) (string, error) {
|
||||
path, err := execext.Expand(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if filepath.IsAbs(path) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
result, err := filepath.Abs(filepathext.SmartJoin(it.BaseDir, path))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("task: error resolving path %s relative to %s: %w", path, it.BaseDir, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
const NamespaceSeparator = ":"
|
||||
|
||||
// Merge merges the second Taskfile into the first
|
||||
func Merge(t1, t2 *Taskfile, namespaces ...string) error {
|
||||
func Merge(t1, t2 *Taskfile, includedTaskfile *IncludedTaskfile, namespaces ...string) error {
|
||||
if t1.Version != t2.Version {
|
||||
return fmt.Errorf(`task: Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
||||
}
|
||||
@@ -39,20 +39,38 @@ func Merge(t1, t2 *Taskfile, namespaces ...string) error {
|
||||
t1.Tasks = make(Tasks)
|
||||
}
|
||||
for k, v := range t2.Tasks {
|
||||
// FIXME(@andreynering): Refactor this block, otherwise we can
|
||||
// have serious side-effects in the future, since we're editing
|
||||
// the original references instead of deep copying them.
|
||||
// We do a deep copy of the task struct here to ensure that no data can
|
||||
// be changed elsewhere once the taskfile is merged.
|
||||
task := v.DeepCopy()
|
||||
|
||||
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = v
|
||||
// Set the task to internal if EITHER the included task or the included
|
||||
// taskfile are marked as internal
|
||||
task.Internal = task.Internal || includedTaskfile.Internal
|
||||
|
||||
for _, dep := range v.Deps {
|
||||
// Add namespaces to dependencies, commands and aliases
|
||||
for _, dep := range task.Deps {
|
||||
dep.Task = taskNameWithNamespace(dep.Task, namespaces...)
|
||||
}
|
||||
for _, cmd := range v.Cmds {
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd != nil && cmd.Task != "" {
|
||||
cmd.Task = taskNameWithNamespace(cmd.Task, namespaces...)
|
||||
}
|
||||
}
|
||||
for i, alias := range task.Aliases {
|
||||
task.Aliases[i] = taskNameWithNamespace(alias, namespaces...)
|
||||
}
|
||||
// Add namespace aliases
|
||||
if includedTaskfile != nil {
|
||||
for _, namespaceAlias := range includedTaskfile.Aliases {
|
||||
task.Aliases = append(task.Aliases, taskNameWithNamespace(task.Task, namespaceAlias))
|
||||
for _, alias := range v.Aliases {
|
||||
task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the task to the merged taskfile
|
||||
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = task
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,11 +2,11 @@ package read
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"github.com/go-task/task/v3/internal/compiler"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
@@ -27,10 +27,11 @@ func Dotenv(c compiler.Compiler, tf *taskfile.Taskfile, dir string) (*taskfile.V
|
||||
|
||||
for _, dotEnvPath := range tf.Dotenv {
|
||||
dotEnvPath = tr.Replace(dotEnvPath)
|
||||
|
||||
if !filepath.IsAbs(dotEnvPath) {
|
||||
dotEnvPath = filepath.Join(dir, dotEnvPath)
|
||||
if dotEnvPath == "" {
|
||||
continue
|
||||
}
|
||||
dotEnvPath = filepathext.SmartJoin(dir, dotEnvPath)
|
||||
|
||||
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
@@ -44,7 +44,8 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
}
|
||||
readerNode.Dir = d
|
||||
}
|
||||
path, err := exists(filepath.Join(readerNode.Dir, readerNode.Entrypoint))
|
||||
|
||||
path, err := exists(filepathext.SmartJoin(readerNode.Dir, readerNode.Entrypoint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -60,6 +61,16 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Annotate any included Taskfile reference with a base directory for resolving relative paths
|
||||
_ = t.Includes.Range(func(key string, includedFile taskfile.IncludedTaskfile) error {
|
||||
// Set the base directory for resolving relative paths, but only if not already set
|
||||
if includedFile.BaseDir == "" {
|
||||
includedFile.BaseDir = readerNode.Dir
|
||||
t.Includes.Set(key, includedFile)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
|
||||
if v >= 3.0 {
|
||||
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}
|
||||
@@ -67,21 +78,22 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
Taskfile: tr.Replace(includedTask.Taskfile),
|
||||
Dir: tr.Replace(includedTask.Dir),
|
||||
Optional: includedTask.Optional,
|
||||
Internal: includedTask.Internal,
|
||||
Aliases: includedTask.Aliases,
|
||||
AdvancedImport: includedTask.AdvancedImport,
|
||||
Vars: includedTask.Vars,
|
||||
BaseDir: includedTask.BaseDir,
|
||||
}
|
||||
if err := tr.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
path, err := execext.Expand(includedTask.Taskfile)
|
||||
path, err := includedTask.FullTaskfilePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(readerNode.Dir, path)
|
||||
}
|
||||
|
||||
path, err = exists(path)
|
||||
if err != nil {
|
||||
if includedTask.Optional {
|
||||
@@ -114,27 +126,31 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
}
|
||||
|
||||
if includedTask.AdvancedImport {
|
||||
dir, err := includedTask.FullDirPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range includedTaskfile.Vars.Mapping {
|
||||
o := v
|
||||
o.Dir = filepath.Join(readerNode.Dir, includedTask.Dir)
|
||||
o.Dir = dir
|
||||
includedTaskfile.Vars.Mapping[k] = o
|
||||
}
|
||||
for k, v := range includedTaskfile.Env.Mapping {
|
||||
o := v
|
||||
o.Dir = filepath.Join(readerNode.Dir, includedTask.Dir)
|
||||
o.Dir = dir
|
||||
includedTaskfile.Env.Mapping[k] = o
|
||||
}
|
||||
|
||||
for _, task := range includedTaskfile.Tasks {
|
||||
if !filepath.IsAbs(task.Dir) {
|
||||
task.Dir = filepath.Join(includedTask.Dir, task.Dir)
|
||||
}
|
||||
task.Dir = filepathext.SmartJoin(dir, task.Dir)
|
||||
task.IncludeVars = includedTask.Vars
|
||||
task.IncludedTaskfileVars = includedTaskfile.Vars
|
||||
task.IncludedTaskfile = &includedTask
|
||||
}
|
||||
}
|
||||
|
||||
if err = taskfile.Merge(t, includedTaskfile, namespace); err != nil {
|
||||
if err = taskfile.Merge(t, includedTaskfile, &includedTask, namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -144,13 +160,13 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
}
|
||||
|
||||
if v < 3.0 {
|
||||
path = filepath.Join(readerNode.Dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
|
||||
path = filepathext.SmartJoin(readerNode.Dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
|
||||
if _, err = os.Stat(path); err == nil {
|
||||
osTaskfile, err := readTaskfile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = taskfile.Merge(t, osTaskfile); err != nil {
|
||||
if err = taskfile.Merge(t, osTaskfile, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -173,7 +189,10 @@ func readTaskfile(file string) (*taskfile.Taskfile, error) {
|
||||
return nil, err
|
||||
}
|
||||
var t taskfile.Taskfile
|
||||
return &t, yaml.NewDecoder(f).Decode(&t)
|
||||
if err := yaml.NewDecoder(f).Decode(&t); err != nil {
|
||||
return nil, fmt.Errorf("task: Failed to parse %s:\n%w", filepathext.TryAbsToRel(file), err)
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func exists(path string) (string, error) {
|
||||
@@ -186,7 +205,7 @@ func exists(path string) (string, error) {
|
||||
}
|
||||
|
||||
for _, n := range defaultTaskfiles {
|
||||
fpath := filepath.Join(path, n)
|
||||
fpath := filepathext.SmartJoin(path, n)
|
||||
if _, err := os.Stat(fpath); err == nil {
|
||||
return fpath, nil
|
||||
}
|
||||
@@ -203,14 +222,14 @@ func checkCircularIncludes(node *ReaderNode) error {
|
||||
return errors.New("task: failed to check for include cycle: node.Parent was nil")
|
||||
}
|
||||
var curNode = node
|
||||
var basePath = filepath.Join(node.Dir, node.Entrypoint)
|
||||
var basePath = filepathext.SmartJoin(node.Dir, node.Entrypoint)
|
||||
for curNode.Parent != nil {
|
||||
curNode = curNode.Parent
|
||||
curPath := filepath.Join(curNode.Dir, curNode.Entrypoint)
|
||||
curPath := filepathext.SmartJoin(curNode.Dir, curNode.Entrypoint)
|
||||
if curPath == basePath {
|
||||
return fmt.Errorf("task: include cycle detected between %s <--> %s",
|
||||
curPath,
|
||||
filepath.Join(node.Parent.Dir, node.Parent.Entrypoint),
|
||||
filepathext.SmartJoin(node.Parent.Dir, node.Parent.Entrypoint),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package read
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
func Taskvars(dir string) (*taskfile.Vars, error) {
|
||||
vars := &taskfile.Vars{}
|
||||
|
||||
path := filepath.Join(dir, "Taskvars.yml")
|
||||
path := filepathext.SmartJoin(dir, "Taskvars.yml")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
vars, err = readTaskvars(path)
|
||||
if err != nil {
|
||||
@@ -23,7 +23,7 @@ func Taskvars(dir string) (*taskfile.Vars, error) {
|
||||
}
|
||||
}
|
||||
|
||||
path = filepath.Join(dir, fmt.Sprintf("Taskvars_%s.yml", runtime.GOOS))
|
||||
path = filepathext.SmartJoin(dir, fmt.Sprintf("Taskvars_%s.yml", runtime.GOOS))
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
osVars, err := readTaskvars(path)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package taskfile
|
||||
|
||||
func stringSliceContains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -11,6 +11,7 @@ type Task struct {
|
||||
Label string
|
||||
Desc string
|
||||
Summary string
|
||||
Aliases []string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
@@ -20,12 +21,14 @@ type Task struct {
|
||||
Env *Vars
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool
|
||||
Run string
|
||||
IncludeVars *Vars
|
||||
IncludedTaskfileVars *Vars
|
||||
IncludedTaskfile *IncludedTaskfile
|
||||
}
|
||||
|
||||
func (t *Task) Name() string {
|
||||
@@ -54,6 +57,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
Label string
|
||||
Desc string
|
||||
Summary string
|
||||
Aliases []string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
@@ -63,6 +67,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
Env *Vars
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
@@ -75,6 +80,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
t.Deps = task.Deps
|
||||
t.Label = task.Label
|
||||
t.Desc = task.Desc
|
||||
t.Aliases = task.Aliases
|
||||
t.Summary = task.Summary
|
||||
t.Sources = task.Sources
|
||||
t.Generates = task.Generates
|
||||
@@ -85,9 +91,42 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
t.Env = task.Env
|
||||
t.Silent = task.Silent
|
||||
t.Interactive = task.Interactive
|
||||
t.Internal = task.Internal
|
||||
t.Method = task.Method
|
||||
t.Prefix = task.Prefix
|
||||
t.IgnoreError = task.IgnoreError
|
||||
t.Run = task.Run
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of Task and copies
|
||||
// data by value from the source struct.
|
||||
func (t *Task) DeepCopy() *Task {
|
||||
c := &Task{
|
||||
Task: t.Task,
|
||||
Cmds: deepCopySlice(t.Cmds),
|
||||
Deps: deepCopySlice(t.Deps),
|
||||
Label: t.Label,
|
||||
Desc: t.Desc,
|
||||
Summary: t.Summary,
|
||||
Aliases: deepCopySlice(t.Aliases),
|
||||
Sources: deepCopySlice(t.Sources),
|
||||
Generates: deepCopySlice(t.Generates),
|
||||
Status: deepCopySlice(t.Status),
|
||||
Preconditions: deepCopySlice(t.Preconditions),
|
||||
Dir: t.Dir,
|
||||
Vars: t.Vars.DeepCopy(),
|
||||
Env: t.Env.DeepCopy(),
|
||||
Silent: t.Silent,
|
||||
Interactive: t.Interactive,
|
||||
Internal: t.Internal,
|
||||
Method: t.Method,
|
||||
Prefix: t.Prefix,
|
||||
IgnoreError: t.IgnoreError,
|
||||
Run: t.Run,
|
||||
IncludeVars: t.IncludeVars.DeepCopy(),
|
||||
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
||||
IncludedTaskfile: t.IncludedTaskfile.DeepCopy(),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type Taskfile struct {
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval string
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||
@@ -34,10 +35,13 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
Silent bool
|
||||
Dotenv []string
|
||||
Run string
|
||||
Interval string
|
||||
}
|
||||
|
||||
if err := unmarshal(&taskfile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tf.Version = taskfile.Version
|
||||
tf.Expansions = taskfile.Expansions
|
||||
tf.Output = taskfile.Output
|
||||
@@ -49,6 +53,8 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
tf.Silent = taskfile.Silent
|
||||
tf.Dotenv = taskfile.Dotenv
|
||||
tf.Run = taskfile.Run
|
||||
tf.Interval = taskfile.Interval
|
||||
|
||||
if tf.Expansions <= 0 {
|
||||
tf.Expansions = 2
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package taskfile
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -34,6 +35,18 @@ func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of Vars and copies
|
||||
// data by value from the source struct.
|
||||
func (vs *Vars) DeepCopy() *Vars {
|
||||
if vs == nil {
|
||||
return nil
|
||||
}
|
||||
return &Vars{
|
||||
Keys: deepCopySlice(vs.Keys),
|
||||
Mapping: deepCopyMap(vs.Mapping),
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges the given Vars into the caller one
|
||||
func (vs *Vars) Merge(other *Vars) {
|
||||
_ = other.Range(func(key string, value Var) error {
|
||||
@@ -47,7 +60,7 @@ func (vs *Vars) Set(key string, value Var) {
|
||||
if vs.Mapping == nil {
|
||||
vs.Mapping = make(map[string]Var, 1)
|
||||
}
|
||||
if !stringSliceContains(vs.Keys, key) {
|
||||
if !slices.Contains(vs.Keys, key) {
|
||||
vs.Keys = append(vs.Keys, key)
|
||||
}
|
||||
vs.Mapping[key] = value
|
||||
|
||||
19
testdata/alias/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: Taskfile2.yml
|
||||
aliases: [inc, i]
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
aliases: [f, x]
|
||||
cmds:
|
||||
- echo "foo"
|
||||
- task: b
|
||||
|
||||
bar:
|
||||
aliases: [b, x]
|
||||
cmds:
|
||||
- echo "bar"
|
||||
- task: inc:q
|
||||
7
testdata/alias/Taskfile2.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
qux:
|
||||
aliases: [q, x]
|
||||
cmds:
|
||||
- echo "qux"
|
||||
1
testdata/alias/alias-duplicate.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: No tasks with description available. Try --list-all to list all tasks
|
||||
11
testdata/alias/alias-summary.txt
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
task: foo
|
||||
|
||||
(task does not have description or summary)
|
||||
|
||||
aliases:
|
||||
- f
|
||||
- x
|
||||
|
||||
commands:
|
||||
- echo "foo"
|
||||
- Task: b
|
||||
6
testdata/alias/alias.txt
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
task: [foo] echo "foo"
|
||||
foo
|
||||
task: [bar] echo "bar"
|
||||
bar
|
||||
task: [included:qux] echo "qux"
|
||||
qux
|
||||
17
testdata/evaluate_symlinks_in_paths/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
sources:
|
||||
- src/**/*
|
||||
cmds:
|
||||
- echo "some job"
|
||||
|
||||
test-sym:
|
||||
cmds:
|
||||
- echo "shared file source changed" > src/shared/b
|
||||
|
||||
reset:
|
||||
cmds:
|
||||
- echo "shared file source" > src/shared/b
|
||||
- echo "file source" > src/a
|
||||
1
testdata/evaluate_symlinks_in_paths/shared/b
vendored
Normal file
@@ -0,0 +1 @@
|
||||
shared file source
|
||||
1
testdata/evaluate_symlinks_in_paths/shared/inner_shared/c
vendored
Normal file
@@ -0,0 +1 @@
|
||||
inner shared file source
|
||||
1
testdata/evaluate_symlinks_in_paths/src/a
vendored
Normal file
@@ -0,0 +1 @@
|
||||
file source
|
||||
1
testdata/evaluate_symlinks_in_paths/src/shared
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../shared
|
||||
4
testdata/includes_incorrect/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included: incomplete.yml
|
||||
4
testdata/includes_incorrect/incomplete.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: '3'
|
||||
|
||||
name:
|
||||
'test
|
||||
15
testdata/includes_internal/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: Taskfile2.yml
|
||||
internal: true
|
||||
|
||||
tasks:
|
||||
task-1:
|
||||
cmds:
|
||||
- task: included:default
|
||||
|
||||
task-2:
|
||||
deps:
|
||||
- included:default
|
||||
6
testdata/includes_internal/Taskfile2.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
task-3:
|
||||
cmds:
|
||||
- echo "Hello, World!"
|
||||
10
testdata/includes_rel_path/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: ./included
|
||||
dir: ./included
|
||||
|
||||
common:
|
||||
taskfile: ./common
|
||||
dir: ./common
|
||||
4
testdata/includes_rel_path/common/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
pwd: pwd
|
||||
6
testdata/includes_rel_path/included/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
common:
|
||||
taskfile: ../common
|
||||
dir: ../common
|
||||
15
testdata/internal_task/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
task-1:
|
||||
cmds:
|
||||
- task: task-3
|
||||
|
||||
task-2:
|
||||
deps:
|
||||
- task-3
|
||||
|
||||
task-3:
|
||||
internal: true
|
||||
cmds:
|
||||
- echo "Hello, World!"
|
||||
18
testdata/special_vars/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: ./included
|
||||
dir: ./included
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: print
|
||||
- task: included:print
|
||||
|
||||
print:
|
||||
cmds:
|
||||
- echo root/TASK={{.TASK}}
|
||||
- echo root/ROOT_DIR={{.ROOT_DIR}}
|
||||
- echo root/TASKFILE_DIR={{.TASKFILE_DIR}}
|
||||
8
testdata/special_vars/included/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
print:
|
||||
cmds:
|
||||
- echo included/TASK={{.TASK}}
|
||||
- echo included/ROOT_DIR={{.ROOT_DIR}}
|
||||
- echo included/TASKFILE_DIR={{.TASKFILE_DIR}}
|
||||
1
testdata/watcher_interval/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
src/*
|
||||
16
testdata/watcher_interval/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# https://taskfile.dev
|
||||
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
GREETING: Hello, World!
|
||||
|
||||
interval: "500ms"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
sources:
|
||||
- "src/*"
|
||||
cmds:
|
||||
- echo "{{.GREETING}}"
|
||||
silent: false
|
||||
15
variables.go
@@ -1,10 +1,10 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/status"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
@@ -22,13 +22,12 @@ func (e *Executor) FastCompiledTask(call taskfile.Call) (*taskfile.Task, error)
|
||||
}
|
||||
|
||||
func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskfile.Task, error) {
|
||||
origTask, ok := e.Taskfile.Tasks[call.Task]
|
||||
if !ok {
|
||||
return nil, &taskNotFoundError{call.Task}
|
||||
origTask, err := e.GetTask(call)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vars *taskfile.Vars
|
||||
var err error
|
||||
if evaluateShVars {
|
||||
vars, err = e.Compiler.GetVariables(origTask, call)
|
||||
} else {
|
||||
@@ -50,6 +49,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
Label: r.Replace(origTask.Label),
|
||||
Desc: r.Replace(origTask.Desc),
|
||||
Summary: r.Replace(origTask.Summary),
|
||||
Aliases: origTask.Aliases,
|
||||
Sources: r.ReplaceSlice(origTask.Sources),
|
||||
Generates: r.ReplaceSlice(origTask.Generates),
|
||||
Dir: r.Replace(origTask.Dir),
|
||||
@@ -57,6 +57,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
Env: nil,
|
||||
Silent: origTask.Silent,
|
||||
Interactive: origTask.Interactive,
|
||||
Internal: origTask.Internal,
|
||||
Method: r.Replace(origTask.Method),
|
||||
Prefix: r.Replace(origTask.Prefix),
|
||||
IgnoreError: origTask.IgnoreError,
|
||||
@@ -68,8 +69,8 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Dir != "" && !filepath.IsAbs(new.Dir) {
|
||||
new.Dir = filepath.Join(e.Dir, new.Dir)
|
||||
if e.Dir != "" {
|
||||
new.Dir = filepathext.SmartJoin(e.Dir, new.Dir)
|
||||
}
|
||||
if new.Prefix == "" {
|
||||
new.Prefix = new.Task
|
||||
|
||||