Compare commits

...

102 Commits

Author SHA1 Message Date
Pete Davison
1275ab1b5b v3.39.0 2024-09-07 20:05:46 +00:00
dependabot[bot]
0c05dcbe0f chore(deps): bump github.com/mattn/go-zglob from 0.0.5 to 0.0.6 (#1791)
Bumps [github.com/mattn/go-zglob](https://github.com/mattn/go-zglob) from 0.0.5 to 0.0.6.
- [Commits](https://github.com/mattn/go-zglob/compare/v0.0.5...v0.0.6)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-zglob
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-07 21:00:52 +01:00
Pete Davison
e9983e299f chore: changelog for #1713 2024-09-07 20:00:00 +00:00
dependabot[bot]
a450f2daea chore(deps): bump golang.org/x/term from 0.23.0 to 0.24.0 (#1790)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.23.0 to 0.24.0.
- [Commits](https://github.com/golang/term/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-07 20:55:27 +01:00
Valentin Maerten
c77c8a419b refactor: check if the remote exists just before reading it (#1713)
* refactor: check if the remote exists in the read to avoid doing it in offline mode

* fix: timeout error was not working

* fix: use cached copy if available
2024-09-07 20:54:05 +01:00
Andrey Nering
a233b52c65 chore: add changelog for #1777, #1778 2024-09-06 10:48:25 -03:00
Valentin Maerten
0e2c9cc88f fix: include flatten with a default task (#1778) 2024-09-06 10:44:28 -03:00
dependabot[bot]
dd9cec611a chore(deps): bump micromatch from 4.0.5 to 4.0.8 in /website (#1789)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 13:43:32 +00:00
dependabot[bot]
6985413f93 chore(deps): bump webpack from 5.91.0 to 5.94.0 in /website (#1776)
Bumps [webpack](https://github.com/webpack/webpack) from 5.91.0 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.91.0...v5.94.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 10:41:39 -03:00
dependabot[bot]
cf77768c82 chore(deps): bump github.com/Masterminds/semver/v3 from 3.2.1 to 3.3.0 (#1779)
Bumps [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) from 3.2.1 to 3.3.0.
- [Release notes](https://github.com/Masterminds/semver/releases)
- [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Masterminds/semver/compare/v3.2.1...v3.3.0)

---
updated-dependencies:
- dependency-name: github.com/Masterminds/semver/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 10:40:55 -03:00
dependabot[bot]
6c3b13b676 chore(deps): bump github.com/mattn/go-zglob from 0.0.4 to 0.0.5 (#1780)
Bumps [github.com/mattn/go-zglob](https://github.com/mattn/go-zglob) from 0.0.4 to 0.0.5.
- [Commits](https://github.com/mattn/go-zglob/compare/v0.0.4...v0.0.5)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-zglob
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 10:40:31 -03:00
Pete Davison
ad45c7aeb3 chore: changelog for #1784 2024-09-02 21:45:19 +00:00
Pete Davison
e4b4d04abd fix: matrix loops should be deterministic (#1784) 2024-09-02 22:43:54 +01:00
Valentin Maerten
a3bdb6c40a chore: changelog for #1782 2024-09-02 22:10:05 +02:00
Valentin Maerten
eb39dd94d0 fix(completion): display aliases in fish completion (#1782) 2024-09-02 16:06:01 -04:00
Pete Davison
21cd573770 chore: changelog for #1767 2024-09-02 19:32:19 +00:00
Pete Davison
281d259e6e feat: loop over a matrix (#1767) 2024-09-02 20:29:00 +01:00
Pete Davison
1cb5daf73e chore: changelog for #1157 2024-09-02 19:28:41 +00:00
Pete Davison
3747b2ab7f feat: completion command (#1157) 2024-09-02 19:21:53 +00:00
Andrey Nering
d727ef5393 website: add @vmaerten as a maintainer 2024-09-02 16:04:52 -03:00
Valentin Maerten
a72b65b3b2 chore: changelog for #1704 2024-08-26 23:19:05 +02:00
Valentin Maerten
ef3b853728 feat: add option to declare an included Taskfile as flatten (#1704) 2024-08-26 17:17:39 -04:00
Valentin Maerten
f302b50519 chore: changelog for #1715 2024-08-25 23:06:58 +02:00
Valentin Maerten
c243b0ec7e fix(remote): TASK_REMOTE_DIR does not work when absolute (#1715) 2024-08-25 17:03:28 -04:00
Thanu Poptiphueng
32158dac87 docs: fix variable name (#1754) 2024-08-24 22:24:42 -03:00
Andrey Nering
0a59890a46 chore(dev): add .vscode/extensions.json with recommended extensions 2024-08-24 22:16:22 -03:00
Andrey Nering
defbcf6acd chore: add changelog for #1764 2024-08-24 21:57:09 -03:00
Daniel Story
045d054a5f feat: add ALIAS special var (#1764) 2024-08-24 21:50:45 -03:00
dependabot[bot]
0941de3318 chore(deps): bump mvdan.cc/sh/v3 from 3.8.0 to 3.9.0 (#1765)
Bumps [mvdan.cc/sh/v3](https://github.com/mvdan/sh) from 3.8.0 to 3.9.0.
- [Release notes](https://github.com/mvdan/sh/releases)
- [Changelog](https://github.com/mvdan/sh/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdan/sh/compare/v3.8.0...v3.9.0)

---
updated-dependencies:
- dependency-name: mvdan.cc/sh/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-19 13:30:05 -03:00
Andrey Nering
b259edeb65 feat(defer): expose EXIT_CODE special variable to defer: (#1762)
Co-authored-by: Dor Sahar <dorsahar@icloud.com>
2024-08-14 22:53:14 -03:00
dependabot[bot]
35119c12ab chore(deps): bump axios from 1.6.2 to 1.7.4 in /website (#1760)
Bumps [axios](https://github.com/axios/axios) from 1.6.2 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.2...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 11:46:36 -03:00
Pete Davison
f6ff775d11 chore: changelog for #1758 2024-08-14 13:39:56 +00:00
Pete Davison
5e9851f42f Update minimum go version (#1758)
* feat: update minimum version to 1.22

* refactor: use int range iterator

* refactor: loop variables

* refactor: replace slicesext.FirstNonZero with cmp.Or

* refactor: use slices.Concat instead of append

* fix: unused param

* fix: linting
2024-08-14 08:37:05 -05:00
dependabot[bot]
51c569ef37 chore(deps): bump golang.org/x/term from 0.21.0 to 0.23.0 (#1751)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.21.0 to 0.23.0.
- [Commits](https://github.com/golang/term/compare/v0.21.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-11 11:31:48 -03:00
dependabot[bot]
1ca432a80d chore(deps): bump golang.org/x/sync from 0.7.0 to 0.8.0 (#1752)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.7.0 to 0.8.0.
- [Commits](https://github.com/golang/sync/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-11 11:28:10 -03:00
Ryan Halliday
e781b3d4e0 docs: update syslist links to the new location (#1747) 2024-08-09 13:14:03 +00:00
JonZeolla
81ff1cdea0 docs: fix special variables link (#1730) 2024-07-25 17:19:41 +00:00
Pete Davison
1f2cbfb932 chore: changelog for #1633 2024-07-16 22:48:15 +00:00
Valentin Maerten
4b6c79aca5 feat: experiment taskfile envs take precedence over os envs (#1633)
* feat: experiment taskfile envs take precedence over os envs

* fix test

* fix typo

Co-authored-by: Andrey Nering <andrey@nering.com.br>

* docs: add p about default

---------

Co-authored-by: Andrey Nering <andrey@nering.com.br>
2024-07-16 23:44:34 +01:00
Pete Davison
5739495739 chore: changelog for #1719 2024-07-16 20:07:02 +00:00
Valentin Maerten
9d72fa3250 ci: add new workflow to check if versioned_docs has been modified (#1719) 2024-07-16 21:03:50 +01:00
Pete Davison
4123ffc780 chore: update go-task/template to tagged version 2024-07-16 17:51:55 +00:00
Valentin Maerten
cdafc67bef docs: add CLI_SILENT and CLI_VERBOSE in the docs (#1717) 2024-07-08 21:18:21 +00:00
Valentin Maerten
9ee4f21d62 fix: --version when a version is provided with -ldflags (#1711) 2024-07-05 14:53:36 -03:00
Alessio Perugini
133086d647 docs: update setup-task version (#1710) 2024-07-05 11:38:44 +00:00
Pete Davison
88b095020e v3.38.0 2024-06-30 14:50:47 +00:00
Pete Davison
cc14996b71 chore: changelog for #1656 2024-06-28 17:04:02 +00:00
Valentin Maerten
375106c988 fix: list-task with multiline desc (#1656)
* fix: list-task with multiline desc

* feat: display all lines aligned  in a table

* fix: display experiments

* use ladicle/tabwrite to handle color

* delete empty lines

Co-authored-by: Andrey Nering <andrey@nering.com.br>

* remove all /n and replace by space

---------

Co-authored-by: Andrey Nering <andrey@nering.com.br>
2024-06-28 17:59:46 +01:00
Pete Davison
6ce6a38899 chore: changelog for #1639 2024-06-28 16:44:53 +00:00
Valentin Maerten
76030c9146 feat(remote): add a command to clear the cache (#1639)
* feat(remote): add a command to clear the cache

* Update cmd/task/task.go

Co-authored-by: Andrey Nering <andrey@nering.com.br>

* rebase

---------

Co-authored-by: Andrey Nering <andrey@nering.com.br>
2024-06-28 17:42:16 +01:00
Pete Davison
a71020eab5 chore: update PR template to use comments instead of quotes 2024-06-28 16:22:49 +00:00
Pete Davison
6bef2ff8a9 chore: changelog for #1699 2024-06-28 16:16:58 +00:00
Vincent Smith
413dcd28a8 Add verbose/silent variables (#1669) 2024-06-28 17:13:52 +01:00
Pete Davison
da6f5c66a0 chore: changelog for #1636 2024-06-28 16:09:40 +00:00
Valentin Maerten
6012da7a21 feat(remote): prefix checksums/cached files with the filename (#1636)
* feat(remote): add the task filename in the checksum / cache filename

* prefix the filename with the lastDir from the path
2024-06-28 17:07:43 +01:00
Pete Davison
46c5eafe35 chore: changelog for #1661 2024-06-28 16:02:56 +00:00
Valentin Maerten
830b745112 feat(remote): global tempDir when the path is absolute (#1661)
* feat(remote): global tempDir is the path is absolute

* --wip-- [skip ci]

* fix lint

* rename checksum to fingerprint

* chore: Empty-Commit to trigger CI

* feat: add TASK_REMOTE_DIR

* handle relative path for TASK_REMOTE_DIR

* Remove unneedded extra blank lines

Co-authored-by: Andrey Nering <andrey@nering.com.br>

* add docs about TASK_REMOTE_DIR

---------

Co-authored-by: Andrey Nering <andrey@nering.com.br>
2024-06-28 17:01:11 +01:00
Pete Davison
b52d4e4f40 chore: changelog for #1655 2024-06-28 15:53:03 +00:00
Pete Davison
3aaa3223a0 fix: run once in shared dependencies (#1655)
* fix: run once in shared dependencies

* feat: add test
2024-06-28 16:50:02 +01:00
Andrey Nering
a9ff58d0fe chore: add changelog entry for #1699 2024-06-27 11:23:09 -03:00
Meng Zhuo
eeaebaf8c7 chore(goreleaser): release riscv64 binaries on linux (#1699) 2024-06-27 14:19:54 +00:00
dependabot[bot]
2213141fcb chore(deps): bump braces from 3.0.2 to 3.0.3 in /website (#1697)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 14:12:03 +00:00
dependabot[bot]
19956889a7 chore(deps): bump ws from 7.5.9 to 7.5.10 in /website (#1696)
Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 11:06:58 -03:00
Valentin Maerten
4c580ebf18 docs: add pacstall installation methode 2024-06-13 20:57:30 -03:00
Valentin Maerten
3dccde270a docs: improve install script 2024-06-13 20:57:30 -03:00
Pete Davison
53dd0b138a docs: taskfile versions (#1666) 2024-06-13 20:49:21 -03:00
Pete Davison
ea85909e8b chore: update deps 2024-06-09 20:30:43 +00:00
Pete Davison
6bf6fe7ead docs: ETA FAQ 2024-06-09 20:12:24 +00:00
Andrey Nering
f39c6352ac chore(website): make carbon work on blog pages 2024-06-05 21:51:49 -03:00
Andrey Nering
4294cc92b9 chore(website): add stack overflow and answer overflow to page footer 2024-06-05 21:35:15 -03:00
Pete Davison
40d77156df chore: changelog for #1572 2024-06-03 09:40:33 +00:00
Alexander Arvidsson
856ba3b8c2 feat: colorize tasks in prefixed output (#1572)
* feat: Colorize tasks in prefixed output

* chore: comment and style changes

* fix code tag has spaces in api reference

* fix: migrate to use logger for colors

* fix: Add bright colors to the color sequence

* fix: make colorized prefix logger standard
2024-06-03 10:37:24 +01:00
Pete Davison
0810ef01b0 fix: more docs typos 2024-06-03 09:28:53 +00:00
Pete Davison
527bbc3bf5 fix: docs typos/links 2024-06-03 09:06:34 +00:00
Andrey Nering
912bbcab8e chore: make github detect task as a go project again 2024-05-22 18:28:37 -03:00
Pete Davison
aa45491510 chore: changelog for #1663 2024-05-20 21:02:30 +00:00
Valentin Maerten
1e25ceab29 fix: version check (#1663)
* fix: version check

* refactor following review
2024-05-20 21:48:05 +01:00
Pete Davison
a74b0bc679 chore: changelog for #1654 2024-05-16 15:35:21 +00:00
Pete Davison
a3fce1c302 feat: variable references (#1654)
* feat: add references to the base code instead of the maps experiment

* feat: add template functions to ref resolver

* feat: tests

* docs: variable references

* feat: remove json and yaml keys from map variable experiment

* chore: typo
2024-05-16 16:20:59 +01:00
Pete Davison
7958cf50b3 chore: changelog for #1653 2024-05-16 10:16:49 +00:00
Pete Davison
b0efbad591 docs: template reference (#1653)
* chore: deprecation warnings for template functions

* docs: update reference pages
2024-05-16 11:11:52 +01:00
Valentin Maerten
30e9c7d4cd chore: update actions version because node 16 is deprecated (#1650) 2024-05-15 22:59:23 -03:00
dependabot[bot]
baa5e2c378 chore(deps): bump golang.org/x/term from 0.19.0 to 0.20.0 (#1651)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.19.0 to 0.20.0.
- [Commits](https://github.com/golang/term/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-15 22:58:26 -03:00
Andrey Nering
cc97e2da1d chore: add changelog for #1619 2024-05-15 22:27:29 -03:00
Andrey Nering
a55e21bbb7 chore: move changelog entries to the right section 🤦‍♂️ 2024-05-15 22:25:14 -03:00
Pete Davison
8d138a5eea feat: better yaml parsing and error handling (#1619) 2024-05-16 01:24:02 +00:00
Andrey Nering
635e3f4e7d chore: add changelog and documentation for #1624 2024-05-15 22:00:49 -03:00
Pete Davison
252d549e3f feat: task executable variable (#1624) 2024-05-15 21:50:18 -03:00
Andrey Nering
182d43e8d8 chore: added changelog for #1657 2024-05-15 21:47:38 -03:00
Pete Davison
f35e51e4e5 feat: better release task 2024-05-15 21:32:33 -03:00
Pete Davison
fb3c64c46e fix: prompt response should go on same line as message 2024-05-15 21:32:33 -03:00
Pete Davison
7535467f45 fix: prompt check shouldn't run if dry flag is true 2024-05-15 21:32:33 -03:00
Pete Davison
3e5cd6cdfd fix: prompt check should come after preconditions and fingerprinting 2024-05-15 21:32:33 -03:00
Pete Davison
dcc060af89 fix: missing additionalProperties false in schema 2024-05-15 21:32:33 -03:00
Pete Davison
55593090fa fix: typo in changelog 2024-05-13 08:54:27 +00:00
Pete Davison
57c094f415 v3.37.2 2024-05-12 19:36:09 +00:00
Pete Davison
2f4876b71c chore: changelog for #1649 2024-05-12 19:33:39 +00:00
Pete Davison
725f929778 fix: included variable merging (#1649) 2024-05-12 20:32:09 +01:00
Pete Davison
8266b28b48 chore: changelog for #1648 2024-05-12 19:27:39 +00:00
Pete Davison
f5c7472f64 fix: nil schema panic (#1648) 2024-05-12 20:25:54 +01:00
Pete Davison
ced3e7a579 fix: var_subkey schema 2024-05-10 16:41:02 +00:00
Orel Lazri
36dd71b122 fix(docs): add references to experiments links (#1644) 2024-05-09 21:30:20 +00:00
133 changed files with 4377 additions and 1909 deletions

View File

@@ -8,6 +8,6 @@ charset = utf-8
trim_trailing_whitespace = true
indent_style = tab
[*.{md,yml,yaml,json,toml,htm,html,js,css,svg,sh,bash,fish}]
[*.{md,mdx,yml,yaml,json,toml,htm,html,js,css,svg,sh,bash,fish}]
indent_style = space
indent_size = 2

1
.gitattributes vendored
View File

@@ -1 +1,2 @@
* text=auto
*.mdx -linguist-detectable

2
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,3 @@
github: [andreynering, pd93]
github: [andreynering, pd93, vmaerten]
open_collective: task
custom: https://taskfile.dev/donate/

View File

@@ -1,5 +1,9 @@
> Thanks for your pull request, we really appreciate contributions!
>
> Please understand that it may take some time to be reviewed.
>
> Also, make sure to follow the [Contribution Guide](https://taskfile.dev/contributing/).
<!--
Thanks for your pull request, we really appreciate contributions!
Please understand that it may take some time to be reviewed.
Also, make sure to follow the [Contribution Guide](https://taskfile.dev/contributing/).
-->

View File

@@ -13,19 +13,19 @@ jobs:
name: Lint
strategy:
matrix:
go-version: [1.21.x, 1.22.x]
go-version: [1.22.x, 1.23.x]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@v5
with:
go-version: ${{matrix.go-version}}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
version: v1.55.2
version: v1.60.1
lint-jsonschema:
runs-on: ubuntu-latest
@@ -34,10 +34,25 @@ jobs:
with:
python-version: 3.12
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: install check-jsonschema
run: python -m pip install 'check-jsonschema==0.27.3'
- name: check-jsonschema (metaschema)
run: check-jsonschema --check-metaschema website/static/schema.json
check_doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get changed files in the docs folder
id: changed-files-specific
uses: tj-actions/changed-files@v44
with:
files: website/versioned_docs/**
- uses: actions/github-script@v7
if: steps.changed-files-specific.outputs.any_changed == 'true'
with:
script: |
core.setFailed('website/versioned_docs has changed. Instead you need to update the docs in the website/docs folder.')

View File

@@ -10,12 +10,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: 1.21.x
go-version: 1.22.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2

View File

@@ -13,18 +13,18 @@ jobs:
name: Test
strategy:
matrix:
go-version: [1.21.x, 1.22.x]
go-version: [1.22.x, 1.23.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}}
steps:
- name: Set up Go ${{matrix.go-version}}
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{matrix.go-version}}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Download Go modules
run: go mod download

View File

@@ -12,7 +12,7 @@ jobs:
if: github.repository == 'go-task/task'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Verify changed files
id: changed-files
@@ -25,7 +25,7 @@ jobs:
website/src/pages
- name: Install Task
uses: arduino/setup-task@v1
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View File

@@ -24,8 +24,7 @@ dist/
# editors
.idea/
.vscode/*
!.vscode/*-sample.json
.vscode/settings.json
.fleet/
# exuberant ctags

View File

@@ -11,11 +11,16 @@ builds:
- amd64
- arm
- arm64
- riscv64
goarm:
- '6'
ignore:
- goos: darwin
goarch: '386'
- goos: darwin
goarch: riscv64
- goos: windows
goarch: riscv64
env:
- CGO_ENABLED=0
mod_timestamp: '{{ .CommitTimestamp }}'

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"editorconfig.editorconfig",
"golang.go",
"task.vscode-task"
]
}

View File

@@ -1,5 +1,80 @@
# Changelog
## v3.39.0 - 2024-09-07
- Added
[Env Precedence Experiment](https://taskfile.dev/experiments/env-precedence)
(#1038, #1633 by @vmaerten).
- Added a CI lint job to ensure that the docs are updated correctly (#1719 by
@vmaerten).
- Updated minimum required Go version to 1.22 (#1758 by @pd93).
- Expose a new `EXIT_CODE` special variable on `defer:` when a command finishes
with a non-zero exit code (#1484, #1762 by @dorimon-1 and @andreynering).
- Expose a new `ALIAS` special variable, which will contain the alias used to
call the current task. Falls back to the task name. (#1764 by @DanStory).
- Fixed `TASK_REMOTE_DIR` environment variable not working when the path was
absolute. (#1715 by @vmaerten).
- Added an option to declare an included Taskfile as flattened (#1704 by
@vmaerten).
- Added a new
[`--completion` flag](https://taskfile.dev/installation/#setup-completions) to
output completion scripts for various shells (#293, #1157 by @pd93).
- This is now the preferred way to install completions.
- The completion scripts in the `completion` directory
[are now deprecated](https://taskfile.dev/deprecations/completion-scripts/).
- Added the ability to
[loop over a matrix of values](https://taskfile.dev/usage/#looping-over-a-matrix)
(#1766, #1767, #1784 by @pd93).
- Fixed a bug in fish completion where aliases were not displayed (#1781, #1782
by @vmaerten).
- Fixed panic when having a flattened included Taskfile that contains a
`default` task (#1777, #1778 by @vmaerten).
- Optimized file existence checks for remote Taskfiles (#1713 by @vmaerten).
## v3.38.0 - 2024-06-30
- Added `TASK_EXE` special variable (#1616, #1624 by @pd93 and @andreynering).
- Some YAML parsing errors will now show in a more user friendly way (#1619 by
@pd93).
- Prefixed outputs will now be colorized by default (#1572 by
@AlexanderArvidsson)
- [References](https://taskfile.dev/usage/#referencing-other-variables) are now
generally available (no experiments required) (#1654 by @pd93).
- Templating functions can now be used in references (#1645, #1654 by @pd93).
- Added a new
[templating reference page](https://taskfile.dev/reference/templating/) to the
documentation (#1614, #1653 by @pd93).
- If using the
[Map Variables experiment (1)](https://taskfile.dev/experiments/map-variables/?proposal=1),
references are available by
[prefixing a string with a `#`](https://taskfile.dev/experiments/map-variables/?proposal=1#references)
(#1654 by @pd93).
- If using the
[Map Variables experiment (2)](https://taskfile.dev/experiments/map-variables/?proposal=2),
the `yaml` and `json` keys are no longer available (#1654 by @pd93).
- Added a new `TASK_REMOTE_DIR` environment variable to configure where cached
remote Taskfiles are stored (#1661 by @vmaerten).
- Added a new `--clear-cache` flag to clear the cache of remote Taskfiles (#1639
by @vmaerten).
- Improved the readability of cached remote Taskfile filenames (#1636 by
@vmaerten).
- Starting releasing a binary for the `riscv64` architecture on Linux (#1699 by
@mengzhuo).
- Added `CLI_SILENT` and `CLI_VERBOSE` variables (#1480, #1669 by @Vince-Smith).
- Fixed a couple of bugs with the `prompt:` feature (#1657 by @pd93).
- Fixed JSON Schema to disallow invalid properties (#1657 by @pd93).
- Fixed version checks not working as intended (#872, #1663 by @vmaerten).
- Fixed a bug where included tasks were run multiple times even if `run: once`
was set (#852, #1655 by @pd93).
- Fixed some bugs related to column formatting in the terminal (#1350, #1637,
#1656 by @vmaerten).
## v3.37.2 - 2024-05-12
- Fixed a bug where an empty Taskfile would cause a panic (#1648 by @pd93).
- Fixed a bug where includes Taskfile variable were not being merged correctly
(#1643, #1649 by @pd93).
## v3.37.1 - 2024-05-09
- Fix bug where non-string values (numbers, bools) added to `env:` weren't been
@@ -14,7 +89,7 @@
- Refactored how Task reads, parses and merges Taskfiles using a DAG (#1563,
#1607 by @pd93).
- Fix a bug which stopped tasks from using `stdin` as input (#1593, #1623 by
@pd03).
@pd93).
- Fix error when a file or directory in the project contained a special char
like `&`, `(` or `)` (#1551, #1584 by @andreynering).
- Added alias `q` for template function `shellQuote` (#1601, #1603 by @vergenzt)

View File

@@ -123,10 +123,53 @@ tasks:
cmds:
- go install github.com/goreleaser/goreleaser@latest
release:
release:*:
desc: Prepare the project for a new release
summary: |
This task will do the following:
- Update the version and date in the CHANGELOG.md file
- Update the version in the package.json and package-lock.json files
- Copy the latest docs to the "current" version on the website
- Commit the changes
- Create a new tag
- Push the commit/tag to the repository
- Create a GitHub release
To use the task, simply run "task release:<version>" where "<version>" is is one of:
- "major" - Bumps the major number
- "minor" - Bumps the minor number
- "patch" - Bumps the patch number
- A semver compatible version number (e.g. "1.2.3")
vars:
VERSION:
sh: "go run ./cmd/release --version {{index .MATCH 0}}"
COMPLETE_MESSAGE: |
Creating release with GoReleaser: https://github.com/go-task/task/actions/workflows/release.yml
Please wait for the CI to finish and then do the following:
- Copy the changelog for v{{.VERSION}} to the GitHub release
- Publish the package to NPM with `task npm:publish`
- Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml
preconditions:
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
msg: "You must be on the main branch to release"
- sh: "[[ -z $(git diff --shortstat main) ]]"
msg: "You must have a clean working tree to release"
prompt: "Are you sure you want to release version {{.VERSION}}?"
cmds:
- go run ./cmd/release {{.CLI_ARGS}}
- cmd: echo "Releasing v{{.VERSION}}"
silent: true
- "go run ./cmd/release {{.VERSION}}"
- "git add --all"
- "git commit -m v{{.VERSION}}"
- "git push"
- "git tag v{{.VERSION}}"
- "git push origin tag v{{.VERSION}}"
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
silent: true
npm:publish:
desc: Publish release to npm

View File

@@ -11,6 +11,7 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/otiai10/copy"
"github.com/spf13/pflag"
)
const (
@@ -25,6 +26,16 @@ var (
versionRegex = regexp.MustCompile(`(?m)^ "version": "\d+\.\d+\.\d+",$`)
)
// Flags
var (
versionFlag bool
)
func init() {
pflag.BoolVarP(&versionFlag, "version", "v", false, "resolved version number")
pflag.Parse()
}
func main() {
if err := release(); err != nil {
fmt.Println(err)
@@ -33,7 +44,7 @@ func main() {
}
func release() error {
if len(os.Args) != 2 {
if len(pflag.Args()) != 1 {
return errors.New("error: expected version number")
}
@@ -42,11 +53,14 @@ func release() error {
return err
}
if err := bumpVersion(version, os.Args[1]); err != nil {
if err := bumpVersion(version, pflag.Arg(0)); err != nil {
return err
}
fmt.Println(version)
if versionFlag {
fmt.Println(version)
return nil
}
if err := changelog(version); err != nil {
return err

View File

@@ -17,6 +17,7 @@ import (
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/sort"
ver "github.com/go-task/task/v3/internal/version"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -58,7 +59,7 @@ func run() error {
entrypoint := flags.Entrypoint
if flags.Version {
fmt.Printf("Task version: %s\n", ver.GetVersion())
fmt.Printf("Task version: %s\n", ver.GetVersionWithSum())
return nil
}
@@ -82,6 +83,15 @@ func run() error {
return nil
}
if flags.Completion != "" {
script, err := task.Completion(flags.Completion)
if err != nil {
return err
}
fmt.Println(script)
return nil
}
if flags.Global {
home, err := os.UserHomeDir()
if err != nil {
@@ -125,16 +135,15 @@ func run() error {
OutputStyle: flags.Output,
TaskSorter: taskSorter,
}
listOptions := task.NewListOptions(flags.List, flags.ListAll, flags.ListJson, flags.NoStatus)
if err := listOptions.Validate(); err != nil {
return err
}
if err := e.Setup(); err != nil {
err := e.Setup()
if err != nil {
return err
}
if experiments.AnyVariables.Enabled {
logger.Warnf("The 'Any Variables' experiment flag is no longer required to use non-map variable types. If you wish to use map variables, please use 'TASK_X_MAP_VARIABLES' instead. See https://github.com/go-task/task/issues/1585\n")
}
@@ -145,6 +154,14 @@ func run() error {
return nil
}
if flags.ClearCache {
cache, err := taskfile.NewCache(e.TempDir.Remote)
if err != nil {
return err
}
return cache.Clear()
}
if (listOptions.ShouldListTasks()) && flags.Silent {
return e.ListTaskNames(flags.ListAll)
}
@@ -179,6 +196,8 @@ func run() error {
globals.Set("CLI_ARGS", ast.Var{Value: cliArgs})
globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
e.Taskfile.Vars.Merge(globals, nil)
if !flags.Watch {

34
completion.go Normal file
View File

@@ -0,0 +1,34 @@
package task
import (
_ "embed"
"fmt"
)
//go:embed completion/bash/task.bash
var completionBash string
//go:embed completion/fish/task.fish
var completionFish string
//go:embed completion/ps/task.ps1
var completionPowershell string
//go:embed completion/zsh/_task
var completionZsh string
func Completion(completion string) (string, error) {
// Get the file extension for the selected shell
switch completion {
case "bash":
return completionBash, nil
case "fish":
return completionFish, nil
case "powershell":
return completionPowershell, nil
case "zsh":
return completionZsh, nil
default:
return "", fmt.Errorf("unknown shell: %s", completion)
}
}

View File

@@ -10,7 +10,7 @@ function __task_get_tasks --description "Prints all available tasks with their d
end
# Grab names and descriptions (if any) of the tasks
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):\s*\(.*\)\s*(aliases.*/\1\t\2/' -e 's/\* \(.*\):\s*\(.*\)/\1\t\2/'| string split0)
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):\s*\(.*\)\s*(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):\s*\(.*\)/\1\t\2/'| string split0)
if test $output
echo $output
end

View File

@@ -0,0 +1,179 @@
package errors
import (
"bytes"
"embed"
"errors"
"fmt"
"regexp"
"strings"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/quick"
"github.com/alecthomas/chroma/v2/styles"
"github.com/fatih/color"
"gopkg.in/yaml.v3"
)
//go:embed themes/*.xml
var embedded embed.FS
var typeErrorRegex = regexp.MustCompile(`line \d+: (.*)`)
func init() {
r, err := embedded.Open("themes/task.xml")
if err != nil {
panic(err)
}
style, err := chroma.NewXMLStyle(r)
if err != nil {
panic(err)
}
styles.Register(style)
}
type (
TaskfileDecodeError struct {
Message string
Location string
Line int
Column int
Tag string
Snippet TaskfileSnippet
Err error
}
TaskfileSnippet struct {
Lines []string
StartLine int
EndLine int
Padding int
}
)
func NewTaskfileDecodeError(err error, node *yaml.Node) *TaskfileDecodeError {
// If the error is already a DecodeError, return it
taskfileInvalidErr := &TaskfileDecodeError{}
if errors.As(err, &taskfileInvalidErr) {
return taskfileInvalidErr
}
return &TaskfileDecodeError{
Line: node.Line,
Column: node.Column,
Tag: node.ShortTag(),
Err: err,
}
}
func (err *TaskfileDecodeError) Error() string {
buf := &bytes.Buffer{}
// Print the error message
if err.Message != "" {
fmt.Fprintln(buf, color.RedString("err: %s", err.Message))
} else {
// Extract the errors from the TypeError
te := &yaml.TypeError{}
if errors.As(err.Err, &te) {
if len(te.Errors) > 1 {
fmt.Fprintln(buf, color.RedString("errs:"))
for _, message := range te.Errors {
fmt.Fprintln(buf, color.RedString("- %s", extractTypeErrorMessage(message)))
}
} else {
fmt.Fprintln(buf, color.RedString("err: %s", extractTypeErrorMessage(te.Errors[0])))
}
} else {
// Otherwise print the error message normally
fmt.Fprintln(buf, color.RedString("err: %s", err.Err))
}
}
fmt.Fprintln(buf, color.RedString("file: %s:%d:%d", err.Location, err.Line, err.Column))
// Print the snippet
maxLineNumberDigits := digits(err.Snippet.EndLine)
lineNumberSpacer := strings.Repeat(" ", maxLineNumberDigits)
columnSpacer := strings.Repeat(" ", err.Column-1)
for i, line := range err.Snippet.Lines {
currentLine := err.Snippet.StartLine + i + 1
lineIndicator := " "
if currentLine == err.Line {
lineIndicator = ">"
}
columnIndicator := "^"
// Print each line
lineIndicator = color.RedString(lineIndicator)
columnIndicator = color.RedString(columnIndicator)
lineNumberFormat := fmt.Sprintf("%%%dd", maxLineNumberDigits)
lineNumber := fmt.Sprintf(lineNumberFormat, currentLine)
fmt.Fprintf(buf, "%s %s | %s", lineIndicator, lineNumber, line)
// Print the column indicator
if currentLine == err.Line {
fmt.Fprintf(buf, "\n %s | %s%s", lineNumberSpacer, columnSpacer, columnIndicator)
}
// If there are more lines to print, add a newline
if i < len(err.Snippet.Lines)-1 {
fmt.Fprintln(buf)
}
}
return buf.String()
}
func (err *TaskfileDecodeError) Unwrap() error {
return err.Err
}
func (err *TaskfileDecodeError) Code() int {
return CodeTaskfileDecode
}
func (err *TaskfileDecodeError) WithMessage(format string, a ...any) *TaskfileDecodeError {
err.Message = fmt.Sprintf(format, a...)
return err
}
func (err *TaskfileDecodeError) WithTypeMessage(t string) *TaskfileDecodeError {
err.Message = fmt.Sprintf("cannot unmarshal %s into %s", err.Tag, t)
return err
}
func (err *TaskfileDecodeError) WithFileInfo(location string, b []byte, padding int) *TaskfileDecodeError {
buf := &bytes.Buffer{}
if err := quick.Highlight(buf, string(b), "yaml", "terminal", "task"); err != nil {
buf.WriteString(string(b))
}
lines := strings.Split(buf.String(), "\n")
start := max(err.Line-1-padding, 0)
end := min(err.Line+padding, len(lines)-1)
err.Location = location
err.Snippet = TaskfileSnippet{
Lines: lines[start:end],
StartLine: start,
EndLine: end,
Padding: padding,
}
return err
}
func extractTypeErrorMessage(message string) string {
matches := typeErrorRegex.FindStringSubmatch(message)
if len(matches) == 2 {
return matches[1]
}
return message
}
func digits(number int) int {
count := 0
for number != 0 {
number /= 10
count += 1
}
return count
}

View File

@@ -12,14 +12,14 @@ const (
const (
CodeTaskfileNotFound int = iota + 100
CodeTaskfileAlreadyExists
CodeTaskfileInvalid
CodeTaskfileDecode
CodeTaskfileFetchFailed
CodeTaskfileNotTrusted
CodeTaskfileNotSecure
CodeTaskfileCacheNotFound
CodeTaskfileVersionCheckError
CodeTaskfileNetworkTimeout
_ // CodeTaskfileDuplicateInclude
CodeTaskfileInvalid
CodeTaskfileCycle
)
@@ -58,3 +58,8 @@ func Is(err, target error) bool {
func As(err error, target any) bool {
return errors.As(err, target)
}
// Unwrap wraps the standard errors.Unwrap function so that we don't need to alias that package.
func Unwrap(err error) error {
return errors.Unwrap(err)
}

View File

@@ -80,6 +80,19 @@ func (err *TaskNameConflictError) Code() int {
return CodeTaskNameConflict
}
type TaskNameFlattenConflictError struct {
TaskName string
Include string
}
func (err *TaskNameFlattenConflictError) Error() string {
return fmt.Sprintf(`task: Found multiple tasks (%s) included by "%s""`, err.TaskName, err.Include)
}
func (err *TaskNameFlattenConflictError) Code() int {
return CodeTaskNameConflict
}
// TaskCalledTooManyTimesError is returned when the maximum task call limit is
// exceeded. This is to prevent infinite loops and cyclic dependencies.
type TaskCalledTooManyTimesError struct {

17
errors/themes/task.xml Normal file
View File

@@ -0,0 +1,17 @@
<style name="task">
<entry type="Background" style="bg:#eee8d5"/>
<entry type="Keyword" style="#859900"/>
<entry type="KeywordConstant" style=""/>
<entry type="KeywordNamespace" style="#dc322f"/>
<entry type="KeywordType" style=""/>
<entry type="Name" style="#268bd2"/>
<entry type="NameBuiltin" style="#cb4b16"/>
<entry type="NameClass" style="#cb4b16"/>
<entry type="NameTag" style=""/>
<entry type="Literal" style="#2aa198"/>
<entry type="LiteralNumber" style=""/>
<entry type="OperatorWord" style="#859900"/>
<entry type="Comment" style="italic #93a1a1"/>
<entry type="Generic" style="#d33682"/>
<entry type="Text" style="#586e75"/>
</style>

22
go.mod
View File

@@ -1,16 +1,18 @@
module github.com/go-task/task/v3
go 1.21.0
go 1.22.0
require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/Ladicle/tabwriter v1.0.0
github.com/Masterminds/semver/v3 v3.3.0
github.com/alecthomas/chroma/v2 v2.14.0
github.com/davecgh/go-spew v1.1.1
github.com/dominikbraun/graph v0.23.0
github.com/fatih/color v1.16.0
github.com/fatih/color v1.17.0
github.com/go-task/slim-sprig/v3 v3.0.0
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90
github.com/go-task/template v0.1.0
github.com/joho/godotenv v1.5.1
github.com/mattn/go-zglob v0.0.4
github.com/mattn/go-zglob v0.0.6
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/otiai10/copy v1.14.0
github.com/radovskyb/watcher v1.0.7
@@ -18,18 +20,20 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
github.com/zeebo/xxh3 v1.0.2
golang.org/x/sync v0.7.0
golang.org/x/term v0.19.0
golang.org/x/sync v0.8.0
golang.org/x/term v0.24.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/sh/v3 v3.8.0
mvdan.cc/sh/v3 v3.9.0
)
require (
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/sys v0.25.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

50
go.sum
View File

@@ -1,21 +1,33 @@
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90 h1:JBbiZ2CXIZ9Upe3O2yI5+3ksWoa7hNVNi4BINs8TIrs=
github.com/go-task/template v0.0.0-20240422130016-8f6b279b1e90/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
github.com/go-task/template v0.1.0 h1:ym/r2G937RZA1bsgiWedNnY9e5kxDT+3YcoAnuIetTE=
github.com/go-task/template v0.1.0/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
@@ -29,10 +41,12 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM=
github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
github.com/mattn/go-zglob v0.0.6 h1:mP8RnmCgho4oaUYDIDn6GNxYk+qJGUs8fJLn+twYj2A=
github.com/mattn/go-zglob v0.0.6/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
@@ -55,18 +69,18 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/sh/v3 v3.8.0 h1:ZxuJipLZwr/HLbASonmXtcvvC9HXY9d2lXZHnKGjFc8=
mvdan.cc/sh/v3 v3.8.0/go.mod h1:w04623xkgBVo7/IUK89E0g8hBykgEpN0vgOj3RJr6MY=
mvdan.cc/sh/v3 v3.9.0 h1:it14fyjCdQUk4jf/aYxLO3FG8jFarR9GzMCtnlvvD7c=
mvdan.cc/sh/v3 v3.9.0/go.mod h1:cdBk8bgoiBI7lSZqK5JhUuq7OB64VQ7fgm85xelw3Nk=

View File

@@ -1,6 +1,7 @@
package task
import (
"cmp"
"fmt"
"github.com/go-task/task/v3/internal/hash"
@@ -8,11 +9,7 @@ import (
)
func (e *Executor) GetHash(t *ast.Task) (string, error) {
r := t.Run
if r == "" {
r = e.Taskfile.Run
}
r := cmp.Or(t.Run, e.Taskfile.Run)
var h hash.HashFunc
switch r {
case "always":

35
help.go
View File

@@ -7,8 +7,8 @@ import (
"io"
"os"
"strings"
"text/tabwriter"
"github.com/Ladicle/tabwriter"
"golang.org/x/sync/errgroup"
"github.com/go-task/task/v3/internal/editors"
@@ -105,7 +105,8 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
for _, task := range tasks {
e.Logger.FOutf(w, logger.Yellow, "* ")
e.Logger.FOutf(w, logger.Green, task.Task)
e.Logger.FOutf(w, logger.Default, ": \t%s", task.Desc)
desc := strings.ReplaceAll(task.Desc, "\n", " ")
e.Logger.FOutf(w, logger.Default, ": \t%s", desc)
if len(task.Aliases) > 0 {
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
}
@@ -159,23 +160,21 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
}
var g errgroup.Group
for i := range tasks {
task := tasks[i]
j := i
aliases := []string{}
if len(task.Aliases) > 0 {
aliases = task.Aliases
if len(tasks[i].Aliases) > 0 {
aliases = tasks[i].Aliases
}
g.Go(func() error {
o.Tasks[j] = editors.Task{
Name: task.Name(),
Desc: task.Desc,
Summary: task.Summary,
o.Tasks[i] = editors.Task{
Name: tasks[i].Name(),
Desc: tasks[i].Desc,
Summary: tasks[i].Summary,
Aliases: aliases,
UpToDate: false,
Location: &editors.Location{
Line: task.Location.Line,
Column: task.Location.Column,
Taskfile: task.Location.Taskfile,
Line: tasks[i].Location.Line,
Column: tasks[i].Location.Column,
Taskfile: tasks[i].Location.Taskfile,
},
}
@@ -185,12 +184,12 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
// Get the fingerprinting method to use
method := e.Taskfile.Method
if task.Method != "" {
method = task.Method
if tasks[i].Method != "" {
method = tasks[i].Method
}
upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), task,
upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), tasks[i],
fingerprint.WithMethod(method),
fingerprint.WithTempDir(e.TempDir),
fingerprint.WithTempDir(e.TempDir.Fingerprint),
fingerprint.WithDry(e.Dry),
fingerprint.WithLogger(e.Logger),
)
@@ -198,7 +197,7 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
return err
}
o.Tasks[j].UpToDate = upToDate
o.Tasks[i].UpToDate = upToDate
return nil
})

View File

@@ -3,14 +3,12 @@ package compiler
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"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/logger"
@@ -48,7 +46,7 @@ func (c *Compiler) FastGetVariables(t *ast.Task, call *ast.Call) (*ast.Vars, err
func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool) (*ast.Vars, error) {
result := GetEnviron()
if t != nil {
specialVars, err := c.getSpecialVars(t)
specialVars, err := c.getSpecialVars(t, call)
if err != nil {
return nil, err
}
@@ -77,18 +75,6 @@ func (c *Compiler) getVariables(t *ast.Task, call *ast.Call, evaluateShVars bool
if err := cache.Err(); err != nil {
return err
}
// Evaluate JSON
if newVar.Json != "" {
if err := json.Unmarshal([]byte(newVar.Json), &newVar.Value); err != nil {
return err
}
}
// Evaluate YAML
if newVar.Yaml != "" {
if err := yaml.Unmarshal([]byte(newVar.Yaml), &newVar.Value); err != nil {
return err
}
}
// If the variable is not dynamic, we can set it and return
if newVar.Value != nil || newVar.Sh == "" {
result.Set(k, ast.Var{Value: newVar.Value})
@@ -193,9 +179,11 @@ func (c *Compiler) ResetCache() {
c.dynamicCache = nil
}
func (c *Compiler) getSpecialVars(t *ast.Task) (map[string]string, error) {
func (c *Compiler) getSpecialVars(t *ast.Task, call *ast.Call) (map[string]string, error) {
return map[string]string{
"TASK": t.Task,
"ALIAS": call.Task,
"TASK_EXE": filepath.ToSlash(os.Args[0]),
"ROOT_TASKFILE": filepathext.SmartJoin(c.Dir, c.Entrypoint),
"ROOT_DIR": c.Dir,
"TASKFILE": t.Location.Taskfile,

View File

@@ -85,7 +85,7 @@ func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, erro
case reflect.Struct:
// Loop over each field and call traverseFunc recursively
for i := 0; i < v.NumField(); i += 1 {
for i := range v.NumField() {
if err := traverseFunc(copy.Field(i), v.Field(i)); err != nil {
return err
}
@@ -95,7 +95,7 @@ func TraverseStringsFunc[T any](v T, fn func(v string) (string, error)) (T, erro
// Create an empty copy from the original value's type
copy.Set(reflect.MakeSlice(v.Type(), v.Len(), v.Cap()))
// Loop over each element and call traverseFunc recursively
for i := 0; i < v.Len(); i += 1 {
for i := range v.Len() {
if err := traverseFunc(copy.Index(i), v.Index(i)); err != nil {
return err
}

9
internal/env/env.go vendored
View File

@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -11,15 +12,15 @@ func Get(t *ast.Task) []string {
if t.Env == nil {
return nil
}
environ := os.Environ()
for k, v := range t.Env.ToCacheMap() {
if !isTypeAllowed(v) {
continue
}
if _, alreadySet := os.LookupEnv(k); alreadySet {
continue
if !experiments.EnvPrecedence.Enabled {
if _, alreadySet := os.LookupEnv(k); alreadySet {
continue
}
}
environ = append(environ, fmt.Sprintf("%s=%v", k, v))
}

View File

@@ -90,14 +90,6 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
return r.Run(ctx, p)
}
// IsExitError returns true the given error is an exis status error
func IsExitError(err error) bool {
if _, ok := interp.IsExitStatus(err); ok {
return true
}
return false
}
// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
// if available.
func Expand(s string) (string, error) {

View File

@@ -7,8 +7,8 @@ import (
"path/filepath"
"slices"
"strings"
"text/tabwriter"
"github.com/Ladicle/tabwriter"
"github.com/joho/godotenv"
"github.com/spf13/pflag"
@@ -29,6 +29,7 @@ var (
RemoteTaskfiles Experiment
AnyVariables Experiment
MapVariables Experiment
EnvPrecedence Experiment
)
func init() {
@@ -37,6 +38,7 @@ func init() {
RemoteTaskfiles = New("REMOTE_TASKFILES")
AnyVariables = New("ANY_VARIABLES", "1", "2")
MapVariables = New("MAP_VARIABLES", "1", "2")
EnvPrecedence = New("ENV_PRECEDENCE")
}
func New(xName string, enabledValues ...string) Experiment {
@@ -104,5 +106,6 @@ func List(l *logger.Logger) error {
printExperiment(w, l, GentleForce)
printExperiment(w, l, RemoteTaskfiles)
printExperiment(w, l, MapVariables)
printExperiment(w, l, EnvPrecedence)
return w.Flush()
}

View File

@@ -38,6 +38,7 @@ var (
Version bool
Help bool
Init bool
Completion string
List bool
ListAll bool
ListJson bool
@@ -65,6 +66,7 @@ var (
Experiments bool
Download bool
Offline bool
ClearCache bool
Timeout time.Duration
)
@@ -79,6 +81,7 @@ func init() {
pflag.BoolVar(&Version, "version", false, "Show Task version.")
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
pflag.StringVar(&Completion, "completion", "", "Generates shell completion script.")
pflag.BoolVarP(&List, "list", "l", false, "Lists tasks with description of current Taskfile.")
pflag.BoolVarP(&ListAll, "list-all", "a", false, "Lists tasks with or without a description.")
pflag.BoolVarP(&ListJson, "json", "j", false, "Formats task list as JSON.")
@@ -119,6 +122,7 @@ func init() {
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
pflag.BoolVar(&Offline, "offline", false, "Forces Task to only use local or cached Taskfiles.")
pflag.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.")
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
}
pflag.Parse()
@@ -129,6 +133,10 @@ func Validate() error {
return errors.New("task: You can't set both --download and --offline flags")
}
if Download && ClearCache {
return errors.New("task: You can't set both --download and --clear-cache flags")
}
if Global && Dir != "" {
log.Fatal("task: You can't set both --global and --dir")
return nil

View File

@@ -15,7 +15,7 @@ func Empty(*ast.Task) (string, error) {
}
func Name(t *ast.Task) (string, error) {
return t.Task, nil
return fmt.Sprintf("%s:%s", t.Location.Taskfile, t.LocalName()), nil
}
func Hash(t *ast.Task) (string, error) {

View File

@@ -52,6 +52,30 @@ func Red() PrintFunc {
return color.New(envColor("TASK_COLOR_RED", color.FgRed)...).FprintfFunc()
}
func BrightBlue() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_BLUE", color.FgHiBlue)...).FprintfFunc()
}
func BrightGreen() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_GREEN", color.FgHiGreen)...).FprintfFunc()
}
func BrightCyan() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_CYAN", color.FgHiCyan)...).FprintfFunc()
}
func BrightYellow() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_YELLOW", color.FgHiYellow)...).FprintfFunc()
}
func BrightMagenta() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_MAGENTA", color.FgHiMagenta)...).FprintfFunc()
}
func BrightRed() PrintFunc {
return color.New(envColor("TASK_COLOR_BRIGHT_RED", color.FgHiRed)...).FprintfFunc()
}
func envColor(env string, defaultColor color.Attribute) []color.Attribute {
if os.Getenv("FORCE_COLOR") != "" {
color.NoColor = false
@@ -65,7 +89,7 @@ func envColor(env string, defaultColor color.Attribute) []color.Attribute {
// Otherwise, split by semicolons (ANSI color codes) and use them as is.
attributeStrs := strings.Split(override, ",")
if len(attributeStrs) == 3 {
attributeStrs = append([]string{"38", "2"}, attributeStrs...)
attributeStrs = slices.Concat([]string{"38", "2"}, attributeStrs)
} else {
attributeStrs = strings.Split(override, ";")
}
@@ -156,7 +180,7 @@ func (l *Logger) Prompt(color Color, prompt string, defaultValue string, continu
return errors.New("no continue values provided")
}
l.Outf(color, "%s [%s/%s]\n", prompt, strings.ToLower(continueValues[0]), strings.ToUpper(defaultValue))
l.Outf(color, "%s [%s/%s]: ", prompt, strings.ToLower(continueValues[0]), strings.ToUpper(defaultValue))
reader := bufio.NewReader(l.Stdin)
input, err := reader.ReadString('\n')

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -15,7 +16,7 @@ type Output interface {
type CloseFunc func(err error) error
// Build the Output for the requested ast.Output.
func BuildFor(o *ast.Output) (Output, error) {
func BuildFor(o *ast.Output, logger *logger.Logger) (Output, error) {
switch o.Name {
case "interleaved", "":
if err := checkOutputGroupUnset(o); err != nil {
@@ -32,7 +33,7 @@ func BuildFor(o *ast.Output) (Output, error) {
if err := checkOutputGroupUnset(o); err != nil {
return nil, err
}
return Prefixed{}, nil
return NewPrefixed(logger), nil
default:
return nil, fmt.Errorf(`task: output style %q not recognized`, o.Name)
}

View File

@@ -7,9 +7,11 @@ import (
"io"
"testing"
"github.com/fatih/color"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/omap"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/templater"
@@ -107,7 +109,11 @@ func TestGroupErrorOnlyShowsOutputOnError(t *testing.T) {
func TestPrefixed(t *testing.T) {
var b bytes.Buffer
var o output.Output = output.Prefixed{}
l := &logger.Logger{
Color: false,
}
var o output.Output = output.NewPrefixed(l)
w, _, cleanup := o.WrapWriter(&b, io.Discard, "prefix", nil)
t.Run("simple use cases", func(t *testing.T) {
@@ -132,3 +138,33 @@ func TestPrefixed(t *testing.T) {
assert.Equal(t, "[prefix] Test!\n", b.String())
})
}
func TestPrefixedWithColor(t *testing.T) {
color.NoColor = false
var b bytes.Buffer
l := &logger.Logger{
Color: true,
}
var o output.Output = output.NewPrefixed(l)
writers := make([]io.Writer, 16)
for i := range writers {
writers[i], _, _ = o.WrapWriter(&b, io.Discard, fmt.Sprintf("prefix-%d", i), nil)
}
t.Run("colors should loop", func(t *testing.T) {
for i, w := range writers {
b.Reset()
color := output.PrefixColorSequence[i%len(output.PrefixColorSequence)]
var prefix bytes.Buffer
l.FOutf(&prefix, color, fmt.Sprintf("prefix-%d", i))
fmt.Fprintln(w, "foo\nbar")
assert.Equal(t, fmt.Sprintf("[%s] foo\n[%s] bar\n", prefix.String(), prefix.String()), b.String())
}
})
}

View File

@@ -6,20 +6,36 @@ import (
"io"
"strings"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater"
)
type Prefixed struct{}
type Prefixed struct {
logger *logger.Logger
seen map[string]uint
counter *uint
}
func (Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
pw := &prefixWriter{writer: stdOut, prefix: prefix}
func NewPrefixed(logger *logger.Logger) Prefixed {
var counter uint
return Prefixed{
seen: make(map[string]uint),
counter: &counter,
logger: logger,
}
}
func (p Prefixed) WrapWriter(stdOut, _ io.Writer, prefix string, _ *templater.Cache) (io.Writer, io.Writer, CloseFunc) {
pw := &prefixWriter{writer: stdOut, prefix: prefix, prefixed: &p}
return pw, pw, func(error) error { return pw.close() }
}
type prefixWriter struct {
writer io.Writer
prefix string
buff bytes.Buffer
writer io.Writer
prefixed *Prefixed
prefix string
buff bytes.Buffer
}
func (pw *prefixWriter) Write(p []byte) (int, error) {
@@ -56,6 +72,11 @@ func (pw *prefixWriter) writeOutputLines(force bool) error {
}
}
var PrefixColorSequence = []logger.Color{
logger.Yellow, logger.Blue, logger.Magenta, logger.Cyan, logger.Green, logger.Red,
logger.BrightYellow, logger.BrightBlue, logger.BrightMagenta, logger.BrightCyan, logger.BrightGreen, logger.BrightRed,
}
func (pw *prefixWriter) writeLine(line string) error {
if line == "" {
return nil
@@ -63,6 +84,27 @@ func (pw *prefixWriter) writeLine(line string) error {
if !strings.HasSuffix(line, "\n") {
line += "\n"
}
_, err := fmt.Fprintf(pw.writer, "[%s] %s", pw.prefix, line)
idx, ok := pw.prefixed.seen[pw.prefix]
if !ok {
idx = *pw.prefixed.counter
pw.prefixed.seen[pw.prefix] = idx
*pw.prefixed.counter++
}
if _, err := fmt.Fprint(pw.writer, "["); err != nil {
return nil
}
color := PrefixColorSequence[idx%uint(len(PrefixColorSequence))]
pw.prefixed.logger.FOutf(pw.writer, color, pw.prefix)
if _, err := fmt.Fprint(pw.writer, "] "); err != nil {
return nil
}
_, err := fmt.Fprint(pw.writer, line)
return err
}

View File

@@ -2,6 +2,7 @@ package templater
import (
"bytes"
"fmt"
"maps"
"strings"
@@ -40,7 +41,15 @@ func ResolveRef(ref string, cache *Cache) any {
cache.cacheMap = cache.Vars.ToCacheMap()
}
val, err := template.ResolveRef(ref, cache.cacheMap)
if ref == "." {
return cache.cacheMap
}
t, err := template.New("resolver").Funcs(templateFuncs).Parse(fmt.Sprintf("{{%s}}", ref))
if err != nil {
cache.err = err
return nil
}
val, err := t.Resolve(cache.cacheMap)
if err != nil {
cache.err = err
return nil
@@ -119,8 +128,6 @@ func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) ast.Var
Live: v.Live,
Ref: v.Ref,
Dir: v.Dir,
Json: ReplaceWithExtra(v.Json, cache, extra),
Yaml: ReplaceWithExtra(v.Yaml, cache, extra),
}
}

View File

@@ -5,21 +5,27 @@ import (
"runtime/debug"
)
var version = ""
func GetVersion() string {
if version != "" {
return version
}
var (
version = ""
sum = ""
)
func init() {
info, ok := debug.ReadBuildInfo()
if !ok || info.Main.Version == "" {
return "unknown"
version = "unknown"
} else {
if version == "" {
version = info.Main.Version
}
sum = info.Main.Sum
}
ver := info.Main.Version
if info.Main.Sum != "" {
ver += fmt.Sprintf(" (%s)", info.Main.Sum)
}
return ver
}
func GetVersion() string {
return version
}
func GetVersionWithSum() string {
return fmt.Sprintf("%s (%s)", version, sum)
}

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
"version": "3.37.1",
"version": "3.39.0",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
"version": "3.37.1",
"version": "3.39.0",
"description": "A task runner / simpler Make alternative written in Go",
"scripts": {
"postinstall": "go-npm install",

View File

@@ -1,13 +1,11 @@
package task
import (
"context"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/taskfile/ast"
)
func (e *Executor) areTaskRequiredVarsSet(ctx context.Context, t *ast.Task, call *ast.Call) error {
func (e *Executor) areTaskRequiredVarsSet(t *ast.Task, call *ast.Call) error {
if t.Requires == nil || len(t.Requires.Vars) == 0 {
return nil
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"sync"
@@ -69,7 +70,7 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
e.Download,
e.Offline,
e.Timeout,
e.TempDir,
e.TempDir.Remote,
e.Logger,
)
graph, err := reader.Read()
@@ -95,7 +96,7 @@ func (e *Executor) setupFuzzyModel() {
words = append(words, taskName)
for _, task := range e.Taskfile.Tasks.Values() {
words = append(words, task.Aliases...)
words = slices.Concat(words, task.Aliases)
}
}
@@ -104,12 +105,15 @@ func (e *Executor) setupFuzzyModel() {
}
func (e *Executor) setupTempDir() error {
if e.TempDir != "" {
if e.TempDir != (TempDir{}) {
return nil
}
if os.Getenv("TASK_TEMP_DIR") == "" {
e.TempDir = filepathext.SmartJoin(e.Dir, ".task")
e.TempDir = TempDir{
Remote: filepathext.SmartJoin(e.Dir, ".task"),
Fingerprint: filepathext.SmartJoin(e.Dir, ".task"),
}
} else if filepath.IsAbs(os.Getenv("TASK_TEMP_DIR")) || strings.HasPrefix(os.Getenv("TASK_TEMP_DIR"), "~") {
tempDir, err := execext.Expand(os.Getenv("TASK_TEMP_DIR"))
if err != nil {
@@ -117,9 +121,28 @@ func (e *Executor) setupTempDir() error {
}
projectDir, _ := filepath.Abs(e.Dir)
projectName := filepath.Base(projectDir)
e.TempDir = filepathext.SmartJoin(tempDir, projectName)
e.TempDir = TempDir{
Remote: tempDir,
Fingerprint: filepathext.SmartJoin(tempDir, projectName),
}
} else {
e.TempDir = filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR"))
e.TempDir = TempDir{
Remote: filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR")),
Fingerprint: filepathext.SmartJoin(e.Dir, os.Getenv("TASK_TEMP_DIR")),
}
}
if os.Getenv("TASK_REMOTE_DIR") != "" {
if filepath.IsAbs(os.Getenv("TASK_REMOTE_DIR")) || strings.HasPrefix(os.Getenv("TASK_REMOTE_DIR"), "~") {
remoteTempDir, err := execext.Expand(os.Getenv("TASK_REMOTE_DIR"))
if err != nil {
return err
}
e.TempDir.Remote = remoteTempDir
} else {
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task")
}
}
return nil
@@ -155,7 +178,7 @@ func (e *Executor) setupOutput() error {
}
var err error
e.Output, err = output.BuildFor(&e.OutputStyle)
e.Output, err = output.BuildFor(&e.OutputStyle, e.Logger)
return err
}

View File

@@ -8,24 +8,25 @@ import (
"github.com/go-task/task/v3/internal/logger"
)
const interruptSignalsCount = 3
// NOTE(@andreynering): This function intercepts SIGINT and SIGTERM signals
// so the Task process is not killed immediately and processes running have
// time to do cleanup work.
func (e *Executor) InterceptInterruptSignals() {
ch := make(chan os.Signal, 3)
ch := make(chan os.Signal, interruptSignalsCount)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
go func() {
for i := 1; i <= 3; i++ {
for i := range interruptSignalsCount {
sig := <-ch
if i < 3 {
e.Logger.Outf(logger.Yellow, "task: Signal received: %q\n", sig)
continue
if i+1 >= interruptSignalsCount {
e.Logger.Errf(logger.Red, "task: Signal received for the third time: %q. Forcing shutdown\n", sig)
os.Exit(1)
}
e.Logger.Errf(logger.Red, "task: Signal received for the third time: %q. Forcing shutdown\n", sig)
os.Exit(1)
e.Logger.Outf(logger.Yellow, "task: Signal received: %q\n", sig)
}
}()
}

View File

@@ -20,9 +20,7 @@ import (
"time"
)
var (
SLEEPIT, _ = filepath.Abs("./bin/sleepit")
)
var SLEEPIT, _ = filepath.Abs("./bin/sleepit")
func TestSignalSentToProcessGroup(t *testing.T) {
task, err := getTaskPath()
@@ -147,7 +145,7 @@ func TestSignalSentToProcessGroup(t *testing.T) {
// where the negative PID means the corresponding process group. Note that
// this negative PID works only as long as the caller of the kill(2) system
// call has a different PID, which is the case for this test.
for i := 1; i <= tc.sendSigs; i++ {
for range tc.sendSigs - 1 {
if err := syscall.Kill(-sut.Process.Pid, syscall.SIGINT); err != nil {
t.Fatalf("sending INT signal to the process group: %v", err)
}

View File

@@ -27,7 +27,7 @@ func (e *Executor) Status(ctx context.Context, calls ...*ast.Call) error {
// Check if the task is up-to-date
isUpToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
fingerprint.WithMethod(method),
fingerprint.WithTempDir(e.TempDir),
fingerprint.WithTempDir(e.TempDir.Fingerprint),
fingerprint.WithDry(e.Dry),
fingerprint.WithLogger(e.Logger),
)
@@ -46,7 +46,7 @@ func (e *Executor) statusOnError(t *ast.Task) error {
if method == "" {
method = e.Taskfile.Method
}
checker, err := fingerprint.NewSourcesChecker(method, e.TempDir, e.Dry)
checker, err := fingerprint.NewSourcesChecker(method, e.TempDir.Fingerprint, e.Dry)
if err != nil {
return err
}

68
task.go
View File

@@ -11,6 +11,8 @@ import (
"sync/atomic"
"time"
"mvdan.cc/sh/v3/interp"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/compiler"
"github.com/go-task/task/v3/internal/env"
@@ -34,13 +36,18 @@ const (
MaximumTaskCall = 1000
)
type TempDir struct {
Remote string
Fingerprint string
}
// Executor executes a Taskfile
type Executor struct {
Taskfile *ast.Taskfile
Dir string
Entrypoint string
TempDir string
TempDir TempDir
Force bool
ForceAll bool
Insecure bool
@@ -183,16 +190,6 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
release := e.acquireConcurrencyLimit()
defer release()
if t.Prompt != "" {
if err := e.Logger.Prompt(logger.Yellow, t.Prompt, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) {
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
} else if errors.Is(err, logger.ErrPromptCancelled) {
return &errors.TaskCancelledByUserError{TaskName: call.Task}
} else if err != nil {
return err
}
}
return e.startExecution(ctx, t, func(ctx context.Context) error {
e.Logger.VerboseErrf(logger.Magenta, "task: %q started\n", call.Task)
if err := e.runDeps(ctx, t); err != nil {
@@ -205,7 +202,7 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
return err
}
if err := e.areTaskRequiredVarsSet(ctx, t, call); err != nil {
if err := e.areTaskRequiredVarsSet(t, call); err != nil {
return err
}
@@ -222,7 +219,7 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
upToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
fingerprint.WithMethod(method),
fingerprint.WithTempDir(e.TempDir),
fingerprint.WithTempDir(e.TempDir.Fingerprint),
fingerprint.WithDry(e.Dry),
fingerprint.WithLogger(e.Logger),
)
@@ -238,13 +235,25 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
}
}
if t.Prompt != "" && !e.Dry {
if err := e.Logger.Prompt(logger.Yellow, t.Prompt, "n", "y", "yes"); errors.Is(err, logger.ErrNoTerminal) {
return &errors.TaskCancelledNoTerminalError{TaskName: call.Task}
} else if errors.Is(err, logger.ErrPromptCancelled) {
return &errors.TaskCancelledByUserError{TaskName: call.Task}
} else if err != nil {
return err
}
}
if err := e.mkdir(t); err != nil {
e.Logger.Errf(logger.Red, "task: cannot make directory %q: %v\n", t.Dir, err)
}
var deferredExitCode uint8
for i := range t.Cmds {
if t.Cmds[i].Defer {
defer e.runDeferred(t, call, i)
defer e.runDeferred(t, call, i, &deferredExitCode)
continue
}
@@ -253,9 +262,13 @@ func (e *Executor) RunTask(ctx context.Context, call *ast.Call) error {
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v\n", err2)
}
if execext.IsExitError(err) && t.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
continue
exitCode, isExitError := interp.IsExitStatus(err)
if isExitError {
if t.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
continue
}
deferredExitCode = exitCode
}
if call.Indirect {
@@ -307,10 +320,21 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
return g.Wait()
}
func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int) {
func (e *Executor) runDeferred(t *ast.Task, call *ast.Call, i int, deferredExitCode *uint8) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cmd := t.Cmds[i]
vars, _ := e.Compiler.FastGetVariables(t, call)
cache := &templater.Cache{Vars: vars}
extra := map[string]any{}
if deferredExitCode != nil && *deferredExitCode > 0 {
extra["EXIT_CODE"] = fmt.Sprintf("%d", *deferredExitCode)
}
cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
if err := e.runCommand(ctx, t, call, i); err != nil {
e.Logger.VerboseErrf(logger.Yellow, "task: ignored error in deferred cmd: %s\n", err.Error())
}
@@ -367,7 +391,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *ast.Call,
if closeErr := close(err); closeErr != nil {
e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr)
}
if execext.IsExitError(err) && cmd.IgnoreError {
if _, isExitError := interp.IsExitStatus(err); isExitError && cmd.IgnoreError {
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v\n", t.Name(), err)
return nil
}
@@ -489,14 +513,12 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) {
// Compile the list of tasks
for i := range tasks {
idx := i
task := tasks[idx]
g.Go(func() error {
compiledTask, err := e.FastCompiledTask(&ast.Call{Task: task.Task})
compiledTask, err := e.FastCompiledTask(&ast.Call{Task: tasks[i].Task})
if err != nil {
return err
}
tasks[idx] = compiledTask
tasks[i] = compiledTask
return nil
})
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -60,17 +61,19 @@ func (fct fileContentTest) Run(t *testing.T) {
for f := range fct.Files {
_ = os.Remove(filepathext.SmartJoin(fct.Dir, f))
}
e := &task.Executor{
Dir: fct.Dir,
TempDir: filepathext.SmartJoin(fct.Dir, ".task"),
Dir: fct.Dir,
TempDir: task.TempDir{
Remote: filepathext.SmartJoin(fct.Dir, ".task"),
Fingerprint: filepathext.SmartJoin(fct.Dir, ".task"),
},
Entrypoint: fct.Entrypoint,
Stdout: io.Discard,
Stderr: io.Discard,
}
require.NoError(t, e.Setup(), "e.Setup()")
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: fct.Target}), "e.Run(target)")
for name, expectContent := range fct.Files {
t.Run(fct.name(name), func(t *testing.T) {
path := filepathext.SmartJoin(e.Dir, name)
@@ -95,7 +98,17 @@ func TestEmptyTask(t *testing.T) {
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
}
func TestEmptyTaskfile(t *testing.T) {
e := &task.Executor{
Dir: "testdata/empty_taskfile",
Stdout: io.Discard,
Stderr: io.Discard,
}
require.Error(t, e.Setup(), "e.Setup()")
}
func TestEnv(t *testing.T) {
t.Setenv("QUX", "from_os")
tt := fileContentTest{
Dir: "testdata/env",
Target: "default",
@@ -104,9 +117,21 @@ func TestEnv(t *testing.T) {
"local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
"global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
"multiple_type.txt": "FOO='1' BAR='true' BAZ='1.1'\n",
"not-overriden.txt": "QUX='from_os'\n",
},
}
tt.Run(t)
t.Setenv("TASK_X_ENV_PRECEDENCE", "1")
experiments.EnvPrecedence = experiments.New("ENV_PRECEDENCE")
ttt := fileContentTest{
Dir: "testdata/env",
Target: "overriden",
TrimSpace: false,
Files: map[string]string{
"overriden.txt": "QUX='from_taskfile'\n",
},
}
ttt.Run(t)
}
func TestVars(t *testing.T) {
@@ -263,11 +288,14 @@ func TestStatus(t *testing.T) {
var buff bytes.Buffer
e := &task.Executor{
Dir: dir,
TempDir: filepathext.SmartJoin(dir, ".task"),
Stdout: &buff,
Stderr: &buff,
Silent: true,
Dir: dir,
TempDir: task.TempDir{
Remote: filepathext.SmartJoin(dir, ".task"),
Fingerprint: filepathext.SmartJoin(dir, ".task"),
},
Stdout: &buff,
Stderr: &buff,
Silent: true,
}
require.NoError(t, e.Setup())
// gen-foo creates foo.txt, and will always fail it's status check.
@@ -459,7 +487,10 @@ func TestStatusChecksum(t *testing.T) {
}
var buff bytes.Buffer
tempdir := filepathext.SmartJoin(dir, ".task")
tempdir := task.TempDir{
Remote: filepathext.SmartJoin(dir, ".task"),
Fingerprint: filepathext.SmartJoin(dir, ".task"),
}
e := task.Executor{
Dir: dir,
TempDir: tempdir,
@@ -476,7 +507,7 @@ func TestStatusChecksum(t *testing.T) {
// Capture the modification time, so we can ensure the checksum file
// is not regenerated when the hash hasn't changed.
s, err := os.Stat(filepathext.SmartJoin(tempdir, "checksum/"+test.task))
s, err := os.Stat(filepathext.SmartJoin(tempdir.Fingerprint, "checksum/"+test.task))
require.NoError(t, err)
time := s.ModTime()
@@ -484,7 +515,7 @@ func TestStatusChecksum(t *testing.T) {
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.task}))
assert.Equal(t, `task: Task "`+test.task+`" is up to date`+"\n", buff.String())
s, err = os.Stat(filepathext.SmartJoin(tempdir, "checksum/"+test.task))
s, err = os.Stat(filepathext.SmartJoin(tempdir.Fingerprint, "checksum/"+test.task))
require.NoError(t, err)
assert.Equal(t, time, s.ModTime())
})
@@ -794,7 +825,8 @@ func TestListDescInterpolation(t *testing.T) {
t.Error(err)
}
assert.Contains(t, buff.String(), "bar")
assert.Contains(t, buff.String(), "foo-var")
assert.Contains(t, buff.String(), "bar-var")
}
func TestStatusVariables(t *testing.T) {
@@ -805,8 +837,11 @@ func TestStatusVariables(t *testing.T) {
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
TempDir: filepathext.SmartJoin(dir, ".task"),
Dir: dir,
TempDir: task.TempDir{
Remote: filepathext.SmartJoin(dir, ".task"),
Fingerprint: filepathext.SmartJoin(dir, ".task"),
},
Stdout: &buff,
Stderr: &buff,
Silent: false,
@@ -954,11 +989,14 @@ func TestDryChecksum(t *testing.T) {
_ = os.Remove(checksumFile)
e := task.Executor{
Dir: dir,
TempDir: filepathext.SmartJoin(dir, ".task"),
Stdout: io.Discard,
Stderr: io.Discard,
Dry: true,
Dir: dir,
TempDir: task.TempDir{
Remote: filepathext.SmartJoin(dir, ".task"),
Fingerprint: filepathext.SmartJoin(dir, ".task"),
},
Stdout: io.Discard,
Stderr: io.Discard,
Dry: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
@@ -1033,7 +1071,7 @@ func TestIncludesIncorrect(t *testing.T) {
err := e.Setup()
require.Error(t, err)
assert.Contains(t, err.Error(), "task: Failed to parse testdata/includes_incorrect/incomplete.yml:")
assert.Contains(t, err.Error(), "Failed to parse testdata/includes_incorrect/incomplete.yml:", err.Error())
}
func TestIncludesEmptyMain(t *testing.T) {
@@ -1192,6 +1230,45 @@ func TestIncludesInternal(t *testing.T) {
}
}
func TestIncludesFlatten(t *testing.T) {
const dir = "testdata/includes_flatten"
tests := []struct {
name string
taskfile string
task string
expectedErr bool
expectedOutput string
}{
{name: "included flatten", taskfile: "Taskfile.yml", task: "gen", expectedOutput: "gen from included\n"},
{name: "included flatten with default", taskfile: "Taskfile.yml", task: "default", expectedOutput: "default from included flatten\n"},
{name: "included flatten can call entrypoint tasks", taskfile: "Taskfile.yml", task: "from_entrypoint", expectedOutput: "from entrypoint\n"},
{name: "included flatten with deps", taskfile: "Taskfile.yml", task: "with_deps", expectedOutput: "gen from included\nwith_deps from included\n"},
{name: "included flatten nested", taskfile: "Taskfile.yml", task: "from_nested", expectedOutput: "from nested\n"},
{name: "included flatten multiple same task", taskfile: "Taskfile.multiple.yml", task: "gen", expectedErr: true, expectedOutput: "task: Found multiple tasks (gen) included by \"included\"\""},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Entrypoint: dir + "/" + test.taskfile,
Stdout: &buff,
Stderr: &buff,
Silent: true,
}
err := e.Setup()
if test.expectedErr {
assert.EqualError(t, err, test.expectedOutput)
} else {
require.NoError(t, err)
_ = e.Run(context.Background(), &ast.Call{Task: test.task})
assert.Equal(t, test.expectedOutput, buff.String())
}
})
}
}
func TestIncludesInterpolation(t *testing.T) {
const dir = "testdata/includes_interpolation"
tests := []struct {
@@ -1228,6 +1305,34 @@ func TestIncludesInterpolation(t *testing.T) {
}
}
func TestIncludedTaskfileVarMerging(t *testing.T) {
const dir = "testdata/included_taskfile_var_merging"
tests := []struct {
name string
task string
expectedOutput string
}{
{"foo", "foo:pwd", "included_taskfile_var_merging/foo\n"},
{"bar", "bar:pwd", "included_taskfile_var_merging/bar\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,
}
require.NoError(t, e.Setup())
err := e.Run(context.Background(), &ast.Call{Task: test.task})
require.NoError(t, err)
assert.Contains(t, buff.String(), test.expectedOutput)
})
}
}
func TestInternalTask(t *testing.T) {
const dir = "testdata/internal_task"
tests := []struct {
@@ -1627,6 +1732,26 @@ func TestRunOnlyRunsJobsHashOnce(t *testing.T) {
tt.Run(t)
}
func TestRunOnceSharedDeps(t *testing.T) {
const dir = "testdata/run_once_shared_deps"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
ForceAll: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "build"}))
rx := regexp.MustCompile(`task: \[service-[a,b]:library:build\] echo "build library"`)
matches := rx.FindAllStringSubmatch(buff.String(), -1)
assert.Len(t, matches, 1)
assert.Contains(t, buff.String(), `task: [service-a:build] echo "build a"`)
assert.Contains(t, buff.String(), `task: [service-b:build] echo "build b"`)
}
func TestDeferredCmds(t *testing.T) {
const dir = "testdata/deferred"
var buff bytes.Buffer
@@ -1652,6 +1777,34 @@ task-1 ran successfully
assert.Contains(t, buff.String(), expectedOutputOrder)
}
func TestExitCodeZero(t *testing.T) {
const dir = "testdata/exit_code"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "exit-zero"}))
assert.Equal(t, "EXIT_CODE=", strings.TrimSpace(buff.String()))
}
func TestExitCodeOne(t *testing.T) {
const dir = "testdata/exit_code"
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
}
require.NoError(t, e.Setup())
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "exit-one"}))
assert.Equal(t, "EXIT_CODE=1", strings.TrimSpace(buff.String()))
}
func TestIgnoreNilElements(t *testing.T) {
tests := []struct {
name string
@@ -2223,6 +2376,10 @@ func TestForCmds(t *testing.T) {
name: "loop-explicit",
expectedOutput: "a\nb\nc\n",
},
{
name: "loop-matrix",
expectedOutput: "windows/amd64\nwindows/arm64\nlinux/amd64\nlinux/arm64\ndarwin/amd64\ndarwin/arm64\n",
},
{
name: "loop-sources",
expectedOutput: "bar\nfoo\n",
@@ -2280,6 +2437,17 @@ func TestForDeps(t *testing.T) {
name: "loop-explicit",
expectedOutputContains: []string{"a\n", "b\n", "c\n"},
},
{
name: "loop-matrix",
expectedOutputContains: []string{
"windows/amd64\n",
"windows/arm64\n",
"linux/amd64\n",
"linux/arm64\n",
"darwin/amd64\n",
"darwin/arm64\n",
},
},
{
name: "loop-sources",
expectedOutputContains: []string{"bar\n", "foo\n"},
@@ -2389,3 +2557,48 @@ func TestWildcard(t *testing.T) {
})
}
}
func TestReference(t *testing.T) {
tests := []struct {
name string
call string
expectedOutput string
}{
{
name: "reference in command",
call: "ref-cmd",
expectedOutput: "1\n",
},
{
name: "reference in dependency",
call: "ref-dep",
expectedOutput: "1\n",
},
{
name: "reference using templating resolver",
call: "ref-resolver",
expectedOutput: "1\n",
},
{
name: "reference using templating resolver and dynamic var",
call: "ref-resolver-sh",
expectedOutput: "Alice has 3 children called Bob, Charlie, and Diane\n",
},
}
for _, test := range tests {
t.Run(test.call, func(t *testing.T) {
var buff bytes.Buffer
e := task.Executor{
Dir: "testdata/var_references",
Stdout: &buff,
Stderr: &buff,
Silent: true,
Force: true,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.call}))
assert.Equal(t, test.expectedOutput, buff.String())
})
}
}

View File

@@ -1,10 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
)
@@ -46,7 +45,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var cmd string
if err := node.Decode(&cmd); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
c.Cmd = cmd
return nil
@@ -110,8 +109,8 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: invalid keys in command", node.Line)
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in command")
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into command", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("command")
}

View File

@@ -1,9 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
// Dep is a task dependency
@@ -32,7 +32,7 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var task string
if err := node.Decode(&task); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
d.Task = task
return nil
@@ -45,7 +45,7 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
Silent bool
}
if err := node.Decode(&taskCall); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
d.Task = taskCall.Task
d.For = taskCall.For
@@ -54,5 +54,5 @@ func (d *Dep) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("cannot unmarshal %s into dependency", node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("dependency")
}

View File

@@ -1,19 +1,20 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
"github.com/go-task/task/v3/internal/omap"
)
type For struct {
From string
List []any
Var string
Split string
As string
From string
List []any
Matrix omap.OrderedMap[string, []any]
Var string
Split string
As string
}
func (f *For) UnmarshalYAML(node *yaml.Node) error {
@@ -22,7 +23,7 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var from string
if err := node.Decode(&from); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
f.From = from
return nil
@@ -30,28 +31,35 @@ func (f *For) UnmarshalYAML(node *yaml.Node) error {
case yaml.SequenceNode:
var list []any
if err := node.Decode(&list); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
f.List = list
return nil
case yaml.MappingNode:
var forStruct struct {
Var string
Split string
As string
Matrix omap.OrderedMap[string, []any]
Var string
Split string
As string
}
if err := node.Decode(&forStruct); err == nil && forStruct.Var != "" {
f.Var = forStruct.Var
f.Split = forStruct.Split
f.As = forStruct.As
return nil
if err := node.Decode(&forStruct); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}
return fmt.Errorf("yaml: line %d: invalid keys in for", node.Line)
if forStruct.Var == "" && forStruct.Matrix.Len() == 0 {
return errors.NewTaskfileDecodeError(nil, node).WithMessage("invalid keys in for")
}
if forStruct.Var != "" && forStruct.Matrix.Len() != 0 {
return errors.NewTaskfileDecodeError(nil, node).WithMessage("cannot use both var and matrix in for")
}
f.Matrix = forStruct.Matrix
f.Var = forStruct.Var
f.Split = forStruct.Split
f.As = forStruct.As
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into for", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("for")
}
func (f *For) DeepCopy() *For {
@@ -59,10 +67,11 @@ func (f *For) DeepCopy() *For {
return nil
}
return &For{
From: f.From,
List: deepcopy.Slice(f.List),
Var: f.Var,
Split: f.Split,
As: f.As,
From: f.From,
List: deepcopy.Slice(f.List),
Matrix: f.Matrix.DeepCopy(),
Var: f.Var,
Split: f.Split,
As: f.As,
}
}

View File

@@ -1,9 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
type Glob struct {
@@ -13,20 +13,22 @@ type Glob struct {
func (g *Glob) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.ScalarNode:
g.Glob = node.Value
return nil
case yaml.MappingNode:
var glob struct {
Exclude string
}
if err := node.Decode(&glob); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
g.Glob = glob.Exclude
g.Negate = true
return nil
default:
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
}
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("glob")
}

View File

@@ -1,10 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
omap "github.com/go-task/task/v3/internal/omap"
)
@@ -18,6 +17,7 @@ type Include struct {
Aliases []string
AdvancedImport bool
Vars *Vars
Flatten bool
}
// Includes represents information about included tasksfiles
@@ -38,7 +38,7 @@ func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
var v Include
if err := valueNode.Decode(&v); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Namespace = keyNode.Value
includes.Set(keyNode.Value, &v)
@@ -46,7 +46,7 @@ func (includes *Includes) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfiles", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("includes")
}
// Len returns the length of the map
@@ -71,7 +71,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var str string
if err := node.Decode(&str); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
include.Taskfile = str
return nil
@@ -82,11 +82,12 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
Dir string
Optional bool
Internal bool
Flatten bool
Aliases []string
Vars *Vars
}
if err := node.Decode(&includedTaskfile); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
include.Taskfile = includedTaskfile.Taskfile
include.Dir = includedTaskfile.Dir
@@ -95,10 +96,11 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
include.Aliases = includedTaskfile.Aliases
include.AdvancedImport = true
include.Vars = includedTaskfile.Vars
include.Flatten = includedTaskfile.Flatten
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into included taskfile", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("include")
}
// DeepCopy creates a new instance of IncludedTaskfile and copies
@@ -115,5 +117,6 @@ func (include *Include) DeepCopy() *Include {
Internal: include.Internal,
AdvancedImport: include.AdvancedImport,
Vars: include.Vars.DeepCopy(),
Flatten: include.Flatten,
}
}

View File

@@ -1,9 +1,9 @@
package ast
import (
"fmt"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
// Output of the Task output
@@ -25,7 +25,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var name string
if err := node.Decode(&name); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
s.Name = name
return nil
@@ -35,10 +35,10 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
Group *OutputGroup
}
if err := node.Decode(&tmp); err != nil {
return fmt.Errorf("task: output style must be a string or mapping with a \"group\" key: %w", err)
return errors.NewTaskfileDecodeError(err, node)
}
if tmp.Group == nil {
return fmt.Errorf("task: output style must have the \"group\" key when in mapping form")
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`output style must have the "group" key when in mapping form`)
}
*s = Output{
Name: "group",
@@ -47,7 +47,7 @@ func (s *Output) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into output", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("output")
}
// OutputGroup is the style options specific to the Group style.

View File

@@ -6,6 +6,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/goext"
)
@@ -30,7 +31,7 @@ type ErrInvalidPlatform struct {
}
func (err *ErrInvalidPlatform) Error() string {
return fmt.Sprintf(`task: Invalid platform "%s"`, err.Platform)
return fmt.Sprintf(`invalid platform "%s"`, err.Platform)
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
@@ -39,14 +40,14 @@ func (p *Platform) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var platform string
if err := node.Decode(&platform); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
if err := p.parsePlatform(platform); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into platform", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("platform")
}
// parsePlatform takes a string representing an OS/Arch combination (or either on their own)

View File

@@ -26,10 +26,10 @@ func TestPlatformParsing(t *testing.T) {
{Input: "windows/amd64", ExpectedOS: "windows", ExpectedArch: "amd64"},
{Input: "windows/arm64", ExpectedOS: "windows", ExpectedArch: "arm64"},
{Input: "invalid", Error: `task: Invalid platform "invalid"`},
{Input: "invalid/invalid", Error: `task: Invalid platform "invalid/invalid"`},
{Input: "windows/invalid", Error: `task: Invalid platform "windows/invalid"`},
{Input: "invalid/amd64", Error: `task: Invalid platform "invalid/amd64"`},
{Input: "invalid", Error: `invalid platform "invalid"`},
{Input: "invalid/invalid", Error: `invalid platform "invalid/invalid"`},
{Input: "windows/invalid", Error: `invalid platform "windows/invalid"`},
{Input: "invalid/amd64", Error: `invalid platform "invalid/amd64"`},
}
for _, test := range tests {

View File

@@ -1,14 +1,12 @@
package ast
import (
"errors"
"fmt"
"gopkg.in/yaml.v3"
)
// ErrCantUnmarshalPrecondition is returned for invalid precond YAML.
var ErrCantUnmarshalPrecondition = errors.New("task: Can't unmarshal precondition value")
"github.com/go-task/task/v3/errors"
)
// Precondition represents a precondition necessary for a task to run
type Precondition struct {
@@ -33,7 +31,7 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var cmd string
if err := node.Decode(&cmd); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
p.Sh = cmd
p.Msg = fmt.Sprintf("`%s` failed", cmd)
@@ -45,7 +43,7 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
Msg string
}
if err := node.Decode(&sh); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
p.Sh = sh.Sh
p.Msg = sh.Msg
@@ -55,5 +53,5 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into precondition", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("precondition")
}

View File

@@ -7,42 +7,45 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy"
)
// Task represents a task
type Task struct {
Task string
Cmds []*Cmd
Deps []*Dep
Label string
Desc string
Prompt string
Summary string
Requires *Requires
Aliases []string
Sources []*Glob
Generates []*Glob
Status []string
Preconditions []*Precondition
Dir string
Set []string
Shopt []string
Vars *Vars
Env *Vars
Dotenv []string
Silent bool
Interactive bool
Internal bool
Method string
Prefix string
IgnoreError bool
Run string
Task string
Cmds []*Cmd
Deps []*Dep
Label string
Desc string
Prompt string
Summary string
Requires *Requires
Aliases []string
Sources []*Glob
Generates []*Glob
Status []string
Preconditions []*Precondition
Dir string
Set []string
Shopt []string
Vars *Vars
Env *Vars
Dotenv []string
Silent bool
Interactive bool
Internal bool
Method string
Prefix string
IgnoreError bool
Run string
Platforms []*Platform
Watch bool
Location *Location
// Populated during merging
Namespace string
IncludeVars *Vars
IncludedTaskfileVars *Vars
Platforms []*Platform
Location *Location
Watch bool
}
func (t *Task) Name() string {
@@ -52,6 +55,13 @@ func (t *Task) Name() string {
return t.Task
}
func (t *Task) LocalName() string {
name := t.Task
name = strings.TrimPrefix(name, t.Namespace)
name = strings.TrimPrefix(name, ":")
return name
}
// WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values.
func (t *Task) WildcardMatch(name string) (bool, []string) {
// Convert the name into a regex string
@@ -83,7 +93,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
case yaml.ScalarNode:
var cmd Cmd
if err := node.Decode(&cmd); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
t.Cmds = append(t.Cmds, &cmd)
return nil
@@ -92,7 +102,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
case yaml.SequenceNode:
var cmds []*Cmd
if err := node.Decode(&cmds); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
t.Cmds = cmds
return nil
@@ -130,11 +140,11 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
Watch bool
}
if err := node.Decode(&task); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
if task.Cmd != nil {
if task.Cmds != nil {
return fmt.Errorf("yaml: line %d: task cannot have both cmd and cmds", node.Line)
return errors.NewTaskfileDecodeError(nil, node).WithMessage("task cannot have both cmd and cmds")
}
t.Cmds = []*Cmd{task.Cmd}
} else {
@@ -169,7 +179,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into task", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("task")
}
// DeepCopy creates a new instance of Task and copies
@@ -209,6 +219,7 @@ func (t *Task) DeepCopy() *Task {
Platforms: deepcopy.Slice(t.Platforms),
Location: t.Location.DeepCopy(),
Requires: t.Requires.DeepCopy(),
Namespace: t.Namespace,
}
return c
}

View File

@@ -1,12 +1,13 @@
package ast
import (
"errors"
"fmt"
"time"
"github.com/Masterminds/semver/v3"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
)
// NamespaceSeparator contains the character that separates namespaces
@@ -54,8 +55,7 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
}
t1.Vars.Merge(t2.Vars, include)
t1.Env.Merge(t2.Env, include)
t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
return nil
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
}
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
@@ -77,7 +77,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
Interval time.Duration
}
if err := node.Decode(&taskfile); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
tf.Version = taskfile.Version
tf.Output = taskfile.Output
@@ -101,5 +101,5 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into taskfile", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("taskfile")
}

View File

@@ -2,10 +2,12 @@ package ast
import (
"fmt"
"slices"
"strings"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/omap"
)
@@ -45,43 +47,48 @@ func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
return matchingTasks
}
func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) {
_ = t2.Range(func(k string, v *Task) error {
func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) error {
err := t2.Range(func(name string, v *Task) error {
// We do a deep copy of the task struct here to ensure that no data can
// be changed elsewhere once the taskfile is merged.
task := v.DeepCopy()
// Set the task to internal if EITHER the included task or the included
// taskfile are marked as internal
task.Internal = task.Internal || (include != nil && include.Internal)
// Add namespaces to task dependencies
for _, dep := range task.Deps {
if dep != nil && dep.Task != "" {
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
}
}
// Add namespaces to task commands
for _, cmd := range task.Cmds {
if cmd != nil && cmd.Task != "" {
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
}
}
// Add namespaces to task aliases
for i, alias := range task.Aliases {
task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
}
// Add namespace aliases
if include != nil {
for _, namespaceAlias := range include.Aliases {
task.Aliases = append(task.Aliases, taskNameWithNamespace(task.Task, namespaceAlias))
for _, alias := range v.Aliases {
task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias))
taskName := name
if !include.Flatten {
// Add namespaces to task dependencies
for _, dep := range task.Deps {
if dep != nil && dep.Task != "" {
dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
}
}
// Add namespaces to task commands
for _, cmd := range task.Cmds {
if cmd != nil && cmd.Task != "" {
cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
}
}
// Add namespaces to task aliases
for i, alias := range task.Aliases {
task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
}
// Add namespace aliases
if include != nil {
for _, namespaceAlias := range include.Aliases {
task.Aliases = append(task.Aliases, taskNameWithNamespace(task.Task, namespaceAlias))
for _, alias := range v.Aliases {
task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias))
}
}
}
taskName = taskNameWithNamespace(name, include.Namespace)
task.Namespace = include.Namespace
task.Task = taskName
}
if include.AdvancedImport {
@@ -90,27 +97,32 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) {
task.IncludeVars = &Vars{}
}
task.IncludeVars.Merge(include.Vars, nil)
task.IncludedTaskfileVars = includedTaskfileVars
task.IncludedTaskfileVars = includedTaskfileVars.DeepCopy()
}
if t1.Get(taskName) != nil {
return &errors.TaskNameFlattenConflictError{
TaskName: taskName,
Include: include.Namespace,
}
}
// Add the task to the merged taskfile
taskNameWithNamespace := taskNameWithNamespace(k, include.Namespace)
task.Task = taskNameWithNamespace
t1.Set(taskNameWithNamespace, task)
t1.Set(taskName, task)
return nil
})
// If the included Taskfile has a default task and the parent namespace has
// If the included Taskfile has a default task, being not flattened and the parent namespace has
// no task with a matching name, we can add an alias so that the user can
// run the included Taskfile's default task without specifying its full
// name. If the parent namespace has aliases, we add another alias for each
// of them.
if t2.Get("default") != nil && t1.Get(include.Namespace) == nil {
if t2.Get("default") != nil && t1.Get(include.Namespace) == nil && !include.Flatten {
defaultTaskName := fmt.Sprintf("%s:default", include.Namespace)
t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Namespace)
t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Aliases...)
t1.Get(defaultTaskName).Aliases = slices.Concat(t1.Get(defaultTaskName).Aliases, include.Aliases)
}
return err
}
func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
@@ -118,7 +130,7 @@ func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
case yaml.MappingNode:
tasks := omap.New[string, *Task]()
if err := node.Decode(&tasks); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
// nolint: errcheck
@@ -150,7 +162,7 @@ func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
return nil
}
return fmt.Errorf("yaml: line %d: cannot unmarshal %s into tasks", node.Line, node.ShortTag())
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("tasks")
}
func taskNameWithNamespace(taskName string, namespace string) string {

View File

@@ -1,11 +1,11 @@
package ast
import (
"fmt"
"strings"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/internal/omap"
)
@@ -83,8 +83,6 @@ type Var struct {
Live any
Sh string
Ref string
Json string
Yaml string
Dir string
}
@@ -95,7 +93,7 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
if experiments.MapVariables.Value == "1" {
var value any
if err := node.Decode(&value); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
// If the value is a string and it starts with $, then it's a shell command
if str, ok := value.(string); ok {
@@ -103,6 +101,10 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
v.Sh = str
return nil
}
if str, ok = strings.CutPrefix(str, "#"); ok {
v.Ref = str
return nil
}
}
v.Value = value
return nil
@@ -114,30 +116,26 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
case yaml.MappingNode:
key := node.Content[0].Value
switch key {
case "sh", "ref", "map", "json", "yaml":
case "sh", "ref", "map":
var m struct {
Sh string
Ref string
Map any
Json string
Yaml string
Sh string
Ref string
Map any
}
if err := node.Decode(&m); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Sh = m.Sh
v.Ref = m.Ref
v.Value = m.Map
v.Json = m.Json
v.Yaml = m.Yaml
return nil
default:
return fmt.Errorf(`yaml: line %d: %q is not a valid variable type. Try "sh", "ref", "map", "json", "yaml" or using a scalar value`, node.Line, key)
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map" or using a scalar value`, key)
}
default:
var value any
if err := node.Decode(&value); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Value = value
return nil
@@ -148,22 +146,27 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.MappingNode:
if len(node.Content) > 2 || node.Content[0].Value != "sh" {
return fmt.Errorf(`task: line %d: maps cannot be assigned to variables`, node.Line)
key := node.Content[0].Value
switch key {
case "sh", "ref":
var m struct {
Sh string
Ref string
}
if err := node.Decode(&m); err != nil {
return errors.NewTaskfileDecodeError(err, node)
}
v.Sh = m.Sh
v.Ref = m.Ref
return nil
default:
return errors.NewTaskfileDecodeError(nil, node).WithMessage("maps cannot be assigned to variables")
}
var sh struct {
Sh string
}
if err := node.Decode(&sh); err != nil {
return err
}
v.Sh = sh.Sh
return nil
default:
var value any
if err := node.Decode(&value); err != nil {
return err
return errors.NewTaskfileDecodeError(err, node)
}
v.Value = value
return nil

View File

@@ -50,9 +50,23 @@ func (c *Cache) key(node Node) string {
}
func (c *Cache) cacheFilePath(node Node) string {
return filepath.Join(c.dir, fmt.Sprintf("%s.yaml", c.key(node)))
return c.filePath(node, "yaml")
}
func (c *Cache) checksumFilePath(node Node) string {
return filepath.Join(c.dir, fmt.Sprintf("%s.checksum", c.key(node)))
return c.filePath(node, "checksum")
}
func (c *Cache) filePath(node Node, suffix string) string {
lastDir, filename := node.FilenameAndLastDir()
prefix := filename
// Means it's not "", nor "." nor "/", so it's a valid directory
if len(lastDir) > 1 {
prefix = fmt.Sprintf("%s-%s", lastDir, filename)
}
return filepath.Join(c.dir, fmt.Sprintf("%s.%s.%s", prefix, c.key(node), suffix))
}
func (c *Cache) Clear() error {
return os.RemoveAll(c.dir)
}

View File

@@ -20,6 +20,7 @@ type Node interface {
Remote() bool
ResolveEntrypoint(entrypoint string) (string, error)
ResolveDir(dir string) (string, error)
FilenameAndLastDir() (string, string)
}
func NewRootNode(

View File

@@ -112,3 +112,7 @@ func (node *FileNode) ResolveDir(dir string) (string, error) {
entrypointDir := filepath.Dir(node.Entrypoint)
return filepathext.SmartJoin(entrypointDir, path), nil
}
func (node *FileNode) FilenameAndLastDir() (string, string) {
return "", filepath.Base(node.Entrypoint)
}

View File

@@ -17,7 +17,9 @@ import (
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
type HTTPNode struct {
*BaseNode
URL *url.URL
URL *url.URL
logger *logger.Logger
timeout time.Duration
}
func NewHTTPNode(
@@ -36,18 +38,12 @@ func NewHTTPNode(
if url.Scheme == "http" && !insecure {
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
}
ctx, cf := context.WithTimeout(context.Background(), timeout)
defer cf()
url, err = RemoteExists(ctx, l, url)
if err != nil {
return nil, err
}
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil, &errors.TaskfileNetworkTimeoutError{URI: url.String(), Timeout: timeout}
}
return &HTTPNode{
BaseNode: base,
URL: url,
timeout: timeout,
logger: l,
}, nil
}
@@ -60,6 +56,11 @@ func (node *HTTPNode) Remote() bool {
}
func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
url, err := RemoteExists(ctx, node.logger, node.URL, node.timeout)
if err != nil {
return nil, err
}
node.URL = url
req, err := http.NewRequest("GET", node.URL.String(), nil)
if err != nil {
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
@@ -67,6 +68,9 @@ func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.URL.String(), Timeout: node.timeout}
}
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
}
defer resp.Body.Close()
@@ -110,3 +114,8 @@ func (node *HTTPNode) ResolveDir(dir string) (string, error) {
entrypointDir := filepath.Dir(node.Dir())
return filepathext.SmartJoin(entrypointDir, path), nil
}
func (node *HTTPNode) FilenameAndLastDir() (string, string) {
dir, filename := filepath.Split(node.URL.Path)
return filepath.Base(dir), filename
}

View File

@@ -72,3 +72,7 @@ func (node *StdinNode) ResolveDir(dir string) (string, error) {
return filepathext.SmartJoin(node.Dir(), path), nil
}
func (node *StdinNode) FilenameAndLastDir() (string, string) {
return "", "__stdin__"
}

View File

@@ -109,6 +109,7 @@ func (r *Reader) include(node Node) error {
Dir: templater.Replace(include.Dir, cache),
Optional: include.Optional,
Internal: include.Internal,
Flatten: include.Flatten,
Aliases: include.Aliases,
AdvancedImport: include.AdvancedImport,
Vars: include.Vars,
@@ -207,8 +208,9 @@ func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
// Read the file
b, err = node.Read(ctx)
var taskfileNetworkTimeoutError *errors.TaskfileNetworkTimeoutError
// If we timed out then we likely have a network issue
if node.Remote() && errors.Is(ctx.Err(), context.DeadlineExceeded) {
if node.Remote() && errors.As(err, &taskfileNetworkTimeoutError) {
// If a download was requested, then we can't use a cached copy
if r.download {
return nil, &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: r.timeout}
@@ -263,23 +265,33 @@ func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
}
}
var t ast.Taskfile
if err := yaml.Unmarshal(b, &t); err != nil {
var tf ast.Taskfile
if err := yaml.Unmarshal(b, &tf); err != nil {
// Decode the taskfile and add the file info the any errors
taskfileInvalidErr := &errors.TaskfileDecodeError{}
if errors.As(err, &taskfileInvalidErr) {
return nil, taskfileInvalidErr.WithFileInfo(node.Location(), b, 2)
}
return nil, &errors.TaskfileInvalidError{URI: filepathext.TryAbsToRel(node.Location()), Err: err}
}
// Check that the Taskfile is set and has a schema version
if tf.Version == nil {
return nil, &errors.TaskfileVersionCheckError{URI: node.Location()}
}
// Set the taskfile/task's locations
t.Location = node.Location()
for _, task := range t.Tasks.Values() {
tf.Location = node.Location()
for _, task := range tf.Tasks.Values() {
// If the task is not defined, create a new one
if task == nil {
task = &ast.Task{}
}
// Set the location of the taskfile for each task
if task.Location.Taskfile == "" {
task.Location.Taskfile = t.Location
task.Location.Taskfile = tf.Location
}
}
return &t, nil
return &tf, nil
}

View File

@@ -8,6 +8,7 @@ import (
"path/filepath"
"slices"
"strings"
"time"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext"
@@ -40,7 +41,7 @@ var (
// at the given URL with any of the default Taskfile files names. If any of
// these match a file, the first matching path will be returned. If no files are
// found, an error will be returned.
func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL) (*url.URL, error) {
func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL, timeout time.Duration) (*url.URL, error) {
// Create a new HEAD request for the given URL to check if the resource exists
req, err := http.NewRequest("HEAD", u.String(), nil)
if err != nil {
@@ -50,6 +51,9 @@ func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL) (*url.URL,
// Request the given URL
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil, &errors.TaskfileNetworkTimeoutError{URI: u.String(), Timeout: timeout}
}
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
}
defer resp.Body.Close()

13
testdata/desc/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
version: 3
tasks:
build:
aliases:
- b
desc: |
Multi-line escription with alias which is super long long long long long long
another line
third line long long long long long long long long
test:
aliases:
- t
desc: Single line description with alias

View File

@@ -8,12 +8,14 @@ env:
FOO: foo
BAR: bar
BAZ: "{{.BAZ}}"
QUX: from_taskfile
tasks:
default:
cmds:
- task: local
- task: global
- task: not-overriden
- task: multiple_type
local:
@@ -40,3 +42,11 @@ tasks:
BAZ: 1.1
cmds:
- echo "FOO='$FOO' BAR='$BAR' BAZ='$BAZ'" > multiple_type.txt
not-overriden:
cmds:
- echo "QUX='$QUX'" > not-overriden.txt
overriden:
cmds:
- echo "QUX='$QUX'" > overriden.txt

17
testdata/exit_code/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
version: '3'
silent: true
vars:
PREFIX: EXIT_CODE=
tasks:
exit-zero:
cmds:
- defer: echo {{.PREFIX}}{{.EXIT_CODE}}
- exit 0
exit-one:
cmds:
- defer: echo {{.PREFIX}}{{.EXIT_CODE}}
- exit 1

View File

@@ -7,6 +7,14 @@ tasks:
- for: ["a", "b", "c"]
cmd: echo "{{.ITEM}}"
loop-matrix:
cmds:
- for:
matrix:
OS: ["windows", "linux", "darwin"]
ARCH: ["amd64", "arm64"]
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
# Loop over the task's sources
loop-sources:
sources:

View File

@@ -9,6 +9,16 @@ tasks:
vars:
TEXT: "{{.ITEM}}"
loop-matrix:
deps:
- for:
matrix:
OS: ["windows", "linux", "darwin"]
ARCH: ["amd64", "arm64"]
task: echo
vars:
TEXT: "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
# Loop over the task's sources
loop-sources:
sources:

View File

@@ -0,0 +1,12 @@
version: "3"
includes:
foo:
taskfile: ./foo/Taskfile.yaml
bar:
taskfile: ./bar/Taskfile.yaml
tasks:
stub:
cmds:
- echo 0

View File

@@ -0,0 +1,11 @@
version: "3"
vars:
DIR: bar
tasks:
pwd:
dir: ./{{ .DIR }}
cmds:
- echo "{{ .DIR }}"
- pwd

View File

@@ -0,0 +1,11 @@
version: "3"
vars:
DIR: foo
tasks:
pwd:
dir: ./{{ .DIR }}
cmds:
- echo "{{ .DIR }}"
- pwd

1
testdata/includes_flatten/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.txt

View File

@@ -0,0 +1,12 @@
version: '3'
includes:
included:
taskfile: ./included
flatten: true
tasks:
gen:
cmds:
- echo "gen multiple"

View File

@@ -0,0 +1,3 @@
version: '3'
tasks:
default: echo "default from included flatten"

15
testdata/includes_flatten/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
version: '3'
includes:
included:
taskfile: ./included
dir: ./included
flatten: true
with_default:
taskfile: ./Taskfile.with_default.yml
flatten: true
tasks:
from_entrypoint: echo "from entrypoint"

View File

@@ -0,0 +1,23 @@
version: '3'
includes:
nested:
taskfile: ../nested
flatten: true
tasks:
gen:
cmds:
- echo "gen from included"
with_deps:
deps:
- gen
cmds:
- echo "with_deps from included"
pwd:
desc: Print working directory
cmds:
- pwd

View File

@@ -0,0 +1,6 @@
version: '3'
tasks:
from_nested:
cmds:
- echo "from nested"

View File

@@ -1,8 +1,12 @@
version: '3'
vars:
FOO: bar
FOO: foo
BAR: bar
tasks:
foo:
desc: "task has desc with {{.FOO}} var"
desc: "task has desc with {{.FOO}}-var"
bar:
desc: "task has desc with {{.BAR}}-var"

View File

@@ -0,0 +1,11 @@
version: '3'
includes:
service-a: ./service-a
service-b: ./service-b
tasks:
build:
deps:
- service-a:build
- service-b:build

View File

@@ -0,0 +1,9 @@
version: '3'
tasks:
build:
run: once
cmds:
- echo "build library"
sources:
- src/**/*

View File

@@ -0,0 +1,15 @@
version: '3'
includes:
library:
taskfile: ../library/Taskfile.yml
dir: ../library
tasks:
build:
run: once
deps: [library:build]
cmds:
- echo "build a"
sources:
- src/**/*

View File

@@ -0,0 +1 @@
package main

View File

@@ -0,0 +1,15 @@
version: '3'
includes:
library:
taskfile: ../library/Taskfile.yml
dir: ../library
tasks:
build:
run: once
deps: [library:build]
cmds:
- echo "build b"
sources:
- src/**/*

View File

@@ -0,0 +1 @@
package main

View File

@@ -6,8 +6,16 @@ includes:
dir: ./included
tasks:
print-task: echo {{.TASK}}
print-task:
aliases: [echo-task]
cmds:
- echo {{.TASK}}
print-root-dir: echo {{.ROOT_DIR}}
print-taskfile: echo {{.TASKFILE}}
print-taskfile-dir: echo {{.TASKFILE_DIR}}
print-task-version: echo {{.TASK_VERSION}}
print-task-alias:
aliases: [echo-task-alias]
cmds:
- echo "{{.ALIAS}}"
print-task-alias-default: echo "{{.ALIAS}}"

View File

@@ -1,8 +1,16 @@
version: '3'
tasks:
print-task: echo {{.TASK}}
print-task:
aliases: [echo-task]
cmds:
- echo {{.TASK}}
print-root-dir: echo {{.ROOT_DIR}}
print-taskfile: echo {{.TASKFILE}}
print-taskfile-dir: echo {{.TASKFILE_DIR}}
print-task-version: echo {{.TASK_VERSION}}
print-task-alias:
aliases: [echo-task-alias]
cmds:
- echo "{{.ALIAS}}"
print-task-alias-default: echo "{{.ALIAS}}"

74
testdata/var_references/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
version: '3'
vars:
GLOBAL_VAR: [1, 2, 2, 2, 3, 3, 4, 5]
tasks:
default:
- task: ref-cmd
- task: ref-dep
- task: ref-resolver
- task: ref-resolver-sh
ref-cmd:
vars:
VAR_REF:
ref: .GLOBAL_VAR
cmds:
- task: print-first
vars:
VAR:
ref: .VAR_REF
ref-dep:
vars:
VAR_REF:
ref: .GLOBAL_VAR
deps:
- task: print-first
vars:
VAR:
ref: .VAR_REF
ref-resolver:
vars:
VAR_REF:
ref: .GLOBAL_VAR
cmds:
- task: print-var
vars:
VAR:
ref: (index .VAR_REF 0)
ref-resolver-sh:
vars:
JSON_STRING:
sh: echo '{"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}'
JSON:
ref: "fromJson .JSON_STRING"
VAR_REF:
ref: .JSON
cmds:
- task: print-story
vars:
VAR:
ref: .VAR_REF
print-var:
cmds:
- echo "{{.VAR}}"
print-first:
cmds:
- echo "{{index .VAR 0}}"
print-story:
cmds:
- >-
echo "{{.VAR.name}} has {{len .VAR.children}} children called
{{- $children := .VAR.children -}}
{{- range $i, $child := $children -}}
{{- if lt $i (sub (len $children) 1)}} {{$child.name -}},
{{- else}} and {{$child.name -}}
{{- end -}}
{{- end -}}"

View File

@@ -10,7 +10,6 @@ tasks:
- task: ref-dep
- task: ref-resolver
- task: json
- task: yaml
map:
vars:
@@ -93,25 +92,13 @@ tasks:
JSON_STRING:
sh: cat example.json
JSON:
json: "{{.JSON_STRING}}"
ref: "fromJson .JSON_STRING"
cmds:
- task: print-story
vars:
VAR:
ref: .JSON
yaml:
vars:
YAML_STRING:
sh: cat example.yaml
YAML:
yaml: "{{.YAML_STRING}}"
cmds:
- task: print-story
vars:
VAR:
ref: .YAML
print-var:
cmds:
- echo "{{.VAR}}"

View File

@@ -11,6 +11,7 @@ import (
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/fingerprint"
"github.com/go-task/task/v3/internal/omap"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile/ast"
)
@@ -72,6 +73,7 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
Location: origTask.Location,
Requires: origTask.Requires,
Watch: origTask.Watch,
Namespace: origTask.Namespace,
}
new.Dir, err = execext.Expand(new.Dir)
if err != nil {
@@ -160,6 +162,12 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
}
continue
}
// Defer commands are replaced in a lazy manner because
// we need to include EXIT_CODE.
if cmd.Defer {
new.Cmds = append(new.Cmds, cmd.DeepCopy())
continue
}
newCmd := cmd.DeepCopy()
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
newCmd.Task = templater.Replace(cmd.Task, cache)
@@ -221,8 +229,8 @@ func (e *Executor) compiledTask(call *ast.Call, evaluateShVars bool) (*ast.Task,
}
if len(origTask.Status) > 0 {
timestampChecker := fingerprint.NewTimestampChecker(e.TempDir, e.Dry)
checksumChecker := fingerprint.NewChecksumChecker(e.TempDir, e.Dry)
timestampChecker := fingerprint.NewTimestampChecker(e.TempDir.Fingerprint, e.Dry)
checksumChecker := fingerprint.NewChecksumChecker(e.TempDir.Fingerprint, e.Dry)
for _, checker := range []fingerprint.SourcesCheckable{timestampChecker, checksumChecker} {
value, err := checker.Value(&new)
@@ -264,9 +272,13 @@ func itemsFromFor(
) ([]any, []string, error) {
var keys []string // The list of keys to loop over (only if looping over a map)
var values []any // The list of values to loop over
// Get the list from a matrix
if f.Matrix.Len() != 0 {
return asAnySlice(product(f.Matrix)), nil, nil
}
// Get the list from the explicit for list
if f.List != nil && len(f.List) > 0 {
values = f.List
if len(f.List) > 0 {
return f.List, nil, nil
}
// Get the list from the task sources
if f.From == "sources" {
@@ -315,3 +327,39 @@ func itemsFromFor(
}
return values, keys, nil
}
// product generates the cartesian product of the input map of slices.
func product(inputMap omap.OrderedMap[string, []any]) []map[string]any {
if inputMap.Len() == 0 {
return nil
}
// Start with an empty product result
result := []map[string]any{{}}
// Iterate over each slice in the slices
_ = inputMap.Range(func(key string, slice []any) error {
var newResult []map[string]any
// For each combination in the current result
for _, combination := range result {
// Append each element from the current slice to the combinations
for _, item := range slice {
newComb := make(map[string]any, len(combination))
// Copy the existing combination
for k, v := range combination {
newComb[k] = v
}
// Add the current item with the corresponding key
newComb[key] = item
newResult = append(newResult, newComb)
}
}
// Update result with the new combinations
result = newResult
return nil
})
return result
}

View File

@@ -103,7 +103,7 @@ tasks:
You can use any of the following list-based functions: `first`, `rest`, `last`,
`initial`, `append`, `prepend`, `concat`, `reverse`, `uniq`, `without`, `has`,
`compact`, `slice` and `chunk`. Check out the [slim-sprg lists
`compact`, `slice` and `chunk`. Check out the [slim-sprig lists
documentation][slim-sprig-list] for more information.
### Looping over variables using `for`

View File

@@ -2,3 +2,5 @@ export const GITHUB_URL = 'https://github.com/go-task/task';
export const TWITTER_URL = 'https://twitter.com/taskfiledev';
export const MASTODON_URL = 'https://fosstodon.org/@task';
export const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
export const STACK_OVERFLOW = 'https://stackoverflow.com/questions/tagged/taskfile';
export const ANSWER_OVERFLOW = 'https://www.answeroverflow.com/c/974121106208354339';

View File

@@ -5,6 +5,81 @@ sidebar_position: 14
# Changelog
## v3.39.0 - 2024-09-07
- Added
[Env Precedence Experiment](https://taskfile.dev/experiments/env-precedence)
(#1038, #1633 by @vmaerten).
- Added a CI lint job to ensure that the docs are updated correctly (#1719 by
@vmaerten).
- Updated minimum required Go version to 1.22 (#1758 by @pd93).
- Expose a new `EXIT_CODE` special variable on `defer:` when a command finishes
with a non-zero exit code (#1484, #1762 by @dorimon-1 and @andreynering).
- Expose a new `ALIAS` special variable, which will contain the alias used to
call the current task. Falls back to the task name. (#1764 by @DanStory).
- Fixed `TASK_REMOTE_DIR` environment variable not working when the path was
absolute. (#1715 by @vmaerten).
- Added an option to declare an included Taskfile as flattened (#1704 by
@vmaerten).
- Added a new
[`--completion` flag](https://taskfile.dev/installation/#setup-completions) to
output completion scripts for various shells (#293, #1157 by @pd93).
- This is now the preferred way to install completions.
- The completion scripts in the `completion` directory
[are now deprecated](https://taskfile.dev/deprecations/completion-scripts/).
- Added the ability to
[loop over a matrix of values](https://taskfile.dev/usage/#looping-over-a-matrix)
(#1766, #1767, #1784 by @pd93).
- Fixed a bug in fish completion where aliases were not displayed (#1781, #1782
by @vmaerten).
- Fixed panic when having a flattened included Taskfile that contains a
`default` task (#1777, #1778 by @vmaerten).
- Optimized file existence checks for remote Taskfiles (#1713 by @vmaerten).
## v3.38.0 - 2024-06-30
- Added `TASK_EXE` special variable (#1616, #1624 by @pd93 and @andreynering).
- Some YAML parsing errors will now show in a more user friendly way (#1619 by
@pd93).
- Prefixed outputs will now be colorized by default (#1572 by
@AlexanderArvidsson)
- [References](https://taskfile.dev/usage/#referencing-other-variables) are now
generally available (no experiments required) (#1654 by @pd93).
- Templating functions can now be used in references (#1645, #1654 by @pd93).
- Added a new
[templating reference page](https://taskfile.dev/reference/templating/) to the
documentation (#1614, #1653 by @pd93).
- If using the
[Map Variables experiment (1)](https://taskfile.dev/experiments/map-variables/?proposal=1),
references are available by
[prefixing a string with a `#`](https://taskfile.dev/experiments/map-variables/?proposal=1#references)
(#1654 by @pd93).
- If using the
[Map Variables experiment (2)](https://taskfile.dev/experiments/map-variables/?proposal=2),
the `yaml` and `json` keys are no longer available (#1654 by @pd93).
- Added a new `TASK_REMOTE_DIR` environment variable to configure where cached
remote Taskfiles are stored (#1661 by @vmaerten).
- Added a new `--clear-cache` flag to clear the cache of remote Taskfiles (#1639
by @vmaerten).
- Improved the readability of cached remote Taskfile filenames (#1636 by
@vmaerten).
- Starting releasing a binary for the `riscv64` architecture on Linux (#1699 by
@mengzhuo).
- Added `CLI_SILENT` and `CLI_VERBOSE` variables (#1480, #1669 by @Vince-Smith).
- Fixed a couple of bugs with the `prompt:` feature (#1657 by @pd93).
- Fixed JSON Schema to disallow invalid properties (#1657 by @pd93).
- Fixed version checks not working as intended (#872, #1663 by @vmaerten).
- Fixed a bug where included tasks were run multiple times even if `run: once`
was set (#852, #1655 by @pd93).
- Fixed some bugs related to column formatting in the terminal (#1350, #1637,
#1656 by @vmaerten).
## v3.37.2 - 2024-05-12
- Fixed a bug where an empty Taskfile would cause a panic (#1648 by @pd93).
- Fixed a bug where includes Taskfile variable were not being merged correctly
(#1643, #1649 by @pd93).
## v3.37.1 - 2024-05-09
- Fix bug where non-string values (numbers, bools) added to `env:` weren't been
@@ -19,7 +94,7 @@ sidebar_position: 14
- Refactored how Task reads, parses and merges Taskfiles using a DAG (#1563,
#1607 by @pd93).
- Fix a bug which stopped tasks from using `stdin` as input (#1593, #1623 by
@pd03).
@pd93).
- Fix error when a file or directory in the project contained a special char
like `&`, `(` or `)` (#1551, #1584 by @andreynering).
- Added alias `q` for template function `shellQuote` (#1601, #1603 by @vergenzt)

View File

@@ -148,6 +148,8 @@ If you have questions, feel free to ask them in the `#help` forum channel on our
---
{/* prettier-ignore-start */}
[experiments]: /experiments
[experiments-workflow]: /experiments#workflow
[task]: https://github.com/go-task/task
[vscode-task]: https://github.com/go-task/vscode-task
[go]: https://go.dev

View File

@@ -0,0 +1,25 @@
---
slug: /deprecations/completion-scripts/
---
# Completion Scripts
:::warning
This deprecation breaks the following functionality:
- Any direct references to the completion scripts in the Task git repository
:::
Direct use of the completion scripts in the `completion/*` directory of the
[github.com/go-task/task][task] Git repository is deprecated. Any shell
configuration that directly refers to these scripts will potentially break in
the future as the scripts may be moved or deleted entirely. Any configuration
should be updated to use the [new method for generating shell
completions][completions] instead.
{/* prettier-ignore-start */}
[completions]: ../installation.mdx#setup-completions
[task]: https://github.com/go-task/task
{/* prettier-ignore-end */}

View File

@@ -0,0 +1,23 @@
---
slug: /deprecations/template-functions/
---
# Template Functions
:::warning
This deprecation breaks the following functionality:
- A small set of templating functions
:::
The following templating functions are deprecated. Any replacement functions are
listed besides the function being removed.
| Deprecated function | Replaced by |
| ------------------- | ----------- |
| `IsSH` | - |
| `FromSlash` | `fromSlash` |
| `ToSlash` | `toSlash` |
| `ExeExt` | `exeExt` |

Some files were not shown because too many files have changed in this diff Show More