diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9bc9ff90..2eb5e1de 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
# Changelog
-## Unreleased
+## v3.43.0 - 2025-04-21
- Significant improvements were made to the watcher. We migrated from
[watcher](https://github.com/radovskyb/watcher) to
diff --git a/internal/version/version.txt b/internal/version/version.txt
index e339122b..a9184766 100644
--- a/internal/version/version.txt
+++ b/internal/version/version.txt
@@ -1 +1 @@
-3.42.1
+3.43.0
diff --git a/package-lock.json b/package-lock.json
index b2448dbf..80998be1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
- "version": "3.42.1",
+ "version": "3.43.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
diff --git a/package.json b/package.json
index ffd5d164..97daa4b3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
- "version": "3.42.1",
+ "version": "3.43.0",
"description": "A task runner / simpler Make alternative written in Go",
"scripts": {
"postinstall": "go-npm install",
diff --git a/website/docs/changelog.mdx b/website/docs/changelog.mdx
index 3e39dd94..5e216c8c 100644
--- a/website/docs/changelog.mdx
+++ b/website/docs/changelog.mdx
@@ -5,6 +5,77 @@ sidebar_position: 14
# Changelog
+## v3.43.0 - 2025-04-21
+
+- Significant improvements were made to the watcher. We migrated from
+ [watcher](https://github.com/radovskyb/watcher) to
+ [fsnotify](https://github.com/fsnotify/fsnotify). The former library used
+ polling, which means Task had a high CPU usage when watching too many files.
+ `fsnotify` uses proper the APIs from each operating system to watch files,
+ which means a much better performance. The default interval changed from 5
+ seconds to 100 milliseconds, because now it configures the wait time for
+ duplicated events, instead of the polling time (#2048 by @andreynering, #1508,
+ #985, #1179).
+- The [Map Variables experiment](https://github.com/go-task/task/issues/1585)
+ was made generally available so you can now
+ [define map variables in your Taskfiles!](https://taskfile.dev/usage/#variables)
+ (#1585, #1547, #2081 by @pd93).
+- Wildcards can now
+ [match multiple tasks](https://taskfile.dev/usage/#wildcard-arguments) (#2072,
+ #2121 by @pd93).
+- Added the ability to
+ [loop over the files specified by the `generates` keyword](https://taskfile.dev/usage/#looping-over-your-tasks-sources-or-generated-files).
+ This works the same way as looping over sources (#2151 by @sedyh).
+- Added the ability to resolve variables when defining an include variable
+ (#2108, #2113 by @pd93).
+- A few changes have been made to the
+ [Remote Taskfiles experiment](https://github.com/go-task/task/issues/1317)
+ (#1402, #2176 by @pd93):
+ - Cached files are now prioritized over remote ones.
+ - Added an `--expiry` flag which sets the TTL for a remote file cache. By
+ default the value will be 0 (caching disabled). If Task is running in
+ offline mode or fails to make a connection, it will fallback on the cache.
+- `.taskrc` files can now be used from subdirectories and will be searched for
+ recursively up the file tree in the same way that Taskfiles are (#2159, #2166
+ by @pd93).
+- The default taskfile (output when using the `--init` flag) is now an embedded
+ file in the binary instead of being stored in the code (#2112 by @pd93).
+- Improved the way we report the Task version when using the `--version` flag or
+ `{{.TASK_VERSION}}` variable. This should now be more consistent and easier
+ for package maintainers to use (#2131 by @pd93).
+- Fixed a bug where globstar (`**`) matching in `sources` only resolved the
+ first result (#2073, #2075 by @pd93).
+- Fixed a bug where sorting tasks by "none" would use the default sorting
+ instead of leaving tasks in the order they were defined (#2124, #2125 by
+ @trulede).
+- Fixed Fish completion on newer Fish versions (#2130 by @atusy).
+- Fixed a bug where undefined/null variables resolved to an empty string instead
+ of `nil` (#1911, #2144 by @pd93).
+- The `USER_WORKING_DIR` special now will now properly account for the `--dir`
+ (`-d`) flag, if given (#2102, #2103 by @jaynis, #2186 by @andreynering).
+- Fix Fish completions when `--global` (`-g`) is given (#2134 by @atusy).
+- Fixed variables not available when using `defer:` (#1909, #2173 by @vmaerten).
+
+#### Package API
+
+- The [`Executor`](https://pkg.go.dev/github.com/go-task/task/v3#Executor) now
+ uses the functional options pattern (#2085, #2147, #2148 by @pd93).
+- The functional options for the
+ [`taskfile.Reader`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader)
+ and
+ [`taskfile.Snippet`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Snippet)
+ types no longer have the `Reader`/`Snippet` respective prefixes (#2148 by
+ @pd93).
+- [`taskfile.Reader`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader)
+ no longer accepts a
+ [`taskfile.Node`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Node).
+ Instead nodes are passed directly into the
+ [`Reader.Read`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader.Read)
+ method (#2169 by @pd93).
+- [`Reader.Read`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader.Read)
+ also now accepts a [`context.Context`](https://pkg.go.dev/context#Context)
+ (#2176 by @pd93).
+
## v3.42.1 - 2025-03-10
- Fixed a bug where some special variables caused a type error when used global
diff --git a/website/versioned_docs/version-latest/changelog.mdx b/website/versioned_docs/version-latest/changelog.mdx
index 3e39dd94..5e216c8c 100644
--- a/website/versioned_docs/version-latest/changelog.mdx
+++ b/website/versioned_docs/version-latest/changelog.mdx
@@ -5,6 +5,77 @@ sidebar_position: 14
# Changelog
+## v3.43.0 - 2025-04-21
+
+- Significant improvements were made to the watcher. We migrated from
+ [watcher](https://github.com/radovskyb/watcher) to
+ [fsnotify](https://github.com/fsnotify/fsnotify). The former library used
+ polling, which means Task had a high CPU usage when watching too many files.
+ `fsnotify` uses proper the APIs from each operating system to watch files,
+ which means a much better performance. The default interval changed from 5
+ seconds to 100 milliseconds, because now it configures the wait time for
+ duplicated events, instead of the polling time (#2048 by @andreynering, #1508,
+ #985, #1179).
+- The [Map Variables experiment](https://github.com/go-task/task/issues/1585)
+ was made generally available so you can now
+ [define map variables in your Taskfiles!](https://taskfile.dev/usage/#variables)
+ (#1585, #1547, #2081 by @pd93).
+- Wildcards can now
+ [match multiple tasks](https://taskfile.dev/usage/#wildcard-arguments) (#2072,
+ #2121 by @pd93).
+- Added the ability to
+ [loop over the files specified by the `generates` keyword](https://taskfile.dev/usage/#looping-over-your-tasks-sources-or-generated-files).
+ This works the same way as looping over sources (#2151 by @sedyh).
+- Added the ability to resolve variables when defining an include variable
+ (#2108, #2113 by @pd93).
+- A few changes have been made to the
+ [Remote Taskfiles experiment](https://github.com/go-task/task/issues/1317)
+ (#1402, #2176 by @pd93):
+ - Cached files are now prioritized over remote ones.
+ - Added an `--expiry` flag which sets the TTL for a remote file cache. By
+ default the value will be 0 (caching disabled). If Task is running in
+ offline mode or fails to make a connection, it will fallback on the cache.
+- `.taskrc` files can now be used from subdirectories and will be searched for
+ recursively up the file tree in the same way that Taskfiles are (#2159, #2166
+ by @pd93).
+- The default taskfile (output when using the `--init` flag) is now an embedded
+ file in the binary instead of being stored in the code (#2112 by @pd93).
+- Improved the way we report the Task version when using the `--version` flag or
+ `{{.TASK_VERSION}}` variable. This should now be more consistent and easier
+ for package maintainers to use (#2131 by @pd93).
+- Fixed a bug where globstar (`**`) matching in `sources` only resolved the
+ first result (#2073, #2075 by @pd93).
+- Fixed a bug where sorting tasks by "none" would use the default sorting
+ instead of leaving tasks in the order they were defined (#2124, #2125 by
+ @trulede).
+- Fixed Fish completion on newer Fish versions (#2130 by @atusy).
+- Fixed a bug where undefined/null variables resolved to an empty string instead
+ of `nil` (#1911, #2144 by @pd93).
+- The `USER_WORKING_DIR` special now will now properly account for the `--dir`
+ (`-d`) flag, if given (#2102, #2103 by @jaynis, #2186 by @andreynering).
+- Fix Fish completions when `--global` (`-g`) is given (#2134 by @atusy).
+- Fixed variables not available when using `defer:` (#1909, #2173 by @vmaerten).
+
+#### Package API
+
+- The [`Executor`](https://pkg.go.dev/github.com/go-task/task/v3#Executor) now
+ uses the functional options pattern (#2085, #2147, #2148 by @pd93).
+- The functional options for the
+ [`taskfile.Reader`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader)
+ and
+ [`taskfile.Snippet`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Snippet)
+ types no longer have the `Reader`/`Snippet` respective prefixes (#2148 by
+ @pd93).
+- [`taskfile.Reader`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader)
+ no longer accepts a
+ [`taskfile.Node`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Node).
+ Instead nodes are passed directly into the
+ [`Reader.Read`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader.Read)
+ method (#2169 by @pd93).
+- [`Reader.Read`](https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader.Read)
+ also now accepts a [`context.Context`](https://pkg.go.dev/context#Context)
+ (#2176 by @pd93).
+
## v3.42.1 - 2025-03-10
- Fixed a bug where some special variables caused a type error when used global
diff --git a/website/versioned_docs/version-latest/experiments/map_variables.mdx b/website/versioned_docs/version-latest/experiments/map_variables.mdx
deleted file mode 100644
index 477714d0..00000000
--- a/website/versioned_docs/version-latest/experiments/map_variables.mdx
+++ /dev/null
@@ -1,245 +0,0 @@
----
-slug: /experiments/map-variables/
----
-
-import Tabs from '@theme/Tabs';
-import TabItem from '@theme/TabItem';
-
-# Map Variables (#1585)
-
-:::caution
-
-All experimental features are subject to breaking changes and/or removal _at any
-time_. We strongly recommend that you do not use these features in a production
-environment. They are intended for testing and feedback only.
-
-:::
-
-Currently, Task supports all variable types except for maps. This experiment
-adds two different proposals for map variables. Click on the tabs below to
-switch between them.
-
-
-
-
-
-:::warning
-
-This experiment proposal breaks the following functionality:
-
-- Dynamically defined variables (using the `sh` keyword)
-
-:::
-
-:::info
-
-To enable this experiment, set the environment variable:
-`TASK_X_MAP_VARIABLES=1`. Check out [our guide to enabling experiments
-][enabling-experiments] for more information.
-
-:::
-
-This proposal removes support for the `sh` and `ref` keywords in favour of a new
-syntax for dynamically defined variables and references. This allows you to
-define a map directly as you would for any other type:
-
-```yaml
-version: 3
-
-tasks:
- foo:
- vars:
- FOO: {a: 1, b: 2, c: 3} # <-- Directly defined map on the `FOO` key
- cmds:
- - 'echo {{.FOO.a}}'
-```
-
-## Migration
-
-Taskfiles with dynamically defined variables via the `sh` subkey or references
-defined with `ref` will no longer work with this experiment enabled. In order to
-keep using these features, you will need to migrate your Taskfile to use the new
-syntax.
-
-### Dynamic Variables
-
-Previously, you had to define dynamic variables using the `sh` subkey. With this
-experiment enabled, you will need to remove the `sh` subkey and define your
-command as a string that begins with a `$`. This will instruct Task to interpret
-the string as a command instead of a literal value and the variable will be
-populated with the output of the command. For example:
-
-
-
-
-
-```yaml
-version: 3
-
-tasks:
- foo:
- vars:
- CALCULATED_VAR:
- sh: 'echo hello'
- cmds:
- - 'echo {{.CALCULATED_VAR}}'
-```
-
-
-
-
-```yaml
-version: 3
-
-tasks:
- foo:
- vars:
- CALCULATED_VAR: '$echo hello' # <-- Prefix dynamic variable with a `$`
- cmds:
- - 'echo {{.CALCULATED_VAR}}'
-```
-
-
-
-### References
-
-
-
-
-
-```yaml
-version: 3
-
-tasks:
- foo:
- vars:
- VAR: 42
- VAR_REF:
- ref: '.FOO'
- cmds:
- - 'echo {{.VAR_REF}}'
-```
-
-
-
-
-```yaml
-version: 3
-
-tasks:
- foo:
- vars:
- VAR: 42
- VAR_REF: '#.FOO' # <-- Prefix reference with a `#`
- cmds:
- - 'echo {{.VAR_REF}}'
-```
-
-
-
-If your current Taskfile contains a string variable that begins with a `$` or a
-`#`, you will now need to escape it with a backslash (`\`) to stop Task from
-interpreting it as a command or reference.
-
-
-
-
-:::info
-
-To enable this experiment, set the environment variable:
-`TASK_X_MAP_VARIABLES=2`. Check out [our guide to enabling experiments
-][enabling-experiments] for more information.
-
-:::
-
-This proposal maintains backwards-compatibility and the `sh` subkey and adds
-another new `map` subkey for defining map variables:
-
-```yaml
-version: 3
-
-tasks:
- foo:
- vars:
- FOO:
- map: {a: 1, b: 2, c: 3} # <-- Defined using the `map' subkey instead of directly on 'FOO'
- BAR: true # <-- Other types of variables are still defined directly on the key
- BAZ:
- sh: 'echo Hello Task' # <-- The `sh` subkey is still supported
- QUX:
- ref: '.BAZ' # <-- The `ref` subkey is still supported
- cmds:
- - 'echo {{.FOO.a}}'
-```
-
-
-
-## Looping over maps
-
-This experiment also adds support for looping over maps using the `for` keyword,
-just like arrays. In addition to the `{{.ITEM}}` variable being populated when
-looping over a map, we also make an additional `{{.KEY}}` variable available
-that holds the string value of the map key.
-
-
-
-
-
-```yaml
-version: 3
-
-tasks:
- foo:
- vars:
- MAP: {a: 1, b: 2, c: 3}
- cmds:
- - for:
- var: MAP
- cmd: 'echo "{{.KEY}}: {{.ITEM}}"'
-```
-
-
-
-
-```yaml
-version: 3
-
-tasks:
- foo:
- vars:
- map:
- MAP: {a: 1, b: 2, c: 3}
- cmds:
- - for:
- var: MAP
- cmd: 'echo "{{.KEY}}: {{.ITEM}}"'
-```
-
-:::note
-
-Remember that maps are unordered, so
-the order in which the items are looped over is random.
-
-:::
-
-
-
-{/* prettier-ignore-start */}
-[enabling-experiments]: ./experiments.mdx#enabling-experiments
-{/* prettier-ignore-end */}
diff --git a/website/versioned_docs/version-latest/experiments/remote_taskfiles.mdx b/website/versioned_docs/version-latest/experiments/remote_taskfiles.mdx
index 45e6617e..b9c2d1f2 100644
--- a/website/versioned_docs/version-latest/experiments/remote_taskfiles.mdx
+++ b/website/versioned_docs/version-latest/experiments/remote_taskfiles.mdx
@@ -2,6 +2,9 @@
slug: /experiments/remote-taskfiles/
---
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
# Remote Taskfiles (#1317)
:::caution
@@ -20,33 +23,151 @@ To enable this experiment, set the environment variable:
:::
-This experiment allows you to specify a remote Taskfile URL when including a
-Taskfile. For example:
+:::danger
+Never run remote Taskfiles from sources that you do not trust.
+:::
-```yaml
-version: '3'
+This experiment allows you to use Taskfiles which are stored in remote
+locations. This applies to both the root Taskfile (aka. Entrypoint) and also
+when including Taskfiles.
-includes:
- my-remote-namespace: https://raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml
-```
+Task uses "nodes" to reference remote Taskfiles. There are a few different types
+of node which you can use:
-This works exactly the same way that including a local file does. Any tasks in
-the remote Taskfile will be available to run from your main Taskfile via the
-namespace `my-remote-namespace`. For example, if the remote file contains the
-following:
+
+
+
+`https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml`
+
+This is the most basic type of remote node and works by downloading the file
+from the specified URL. The file must be a valid Taskfile and can be of any
+name. If a file is not found at the specified URL, Task will append each of the
+[supported file names][supported-file-names] in turn until it finds a valid
+file. If it still does not find a valid Taskfile, an error is returned.
+
+
+
+
+`https://github.com/go-task/task.git//website/static/Taskfile.yml?ref=main`
+
+This type of node works by downloading the file from a Git repository over
+HTTP/HTTPS. The first part of the URL is the base URL of the Git repository.
+This is the same URL that you would use to clone the repo over HTTP.
+
+- You can optionally add the path to the Taskfile in the repository by appending
+`//` to the URL.
+- You can also optionally specify a branch or tag to use by appending
+`?ref=[` to the end of the URL. If you omit a reference, the default branch
+will be used.
+
+]
+
+
+`git@github.com/go-task/task.git//website/static/Taskfile.yml?ref=main`
+
+This type of node works by downloading the file from a Git repository over SSH.
+The first part of the URL is the user and base URL of the Git repository. This
+is the same URL that you would use to clone the repo over SSH.
+
+To use Git over SSH, you need to make sure that your SSH agent has your private
+SSH keys added so that they can be used during authentication.
+
+- You can optionally add the path to the Taskfile in the repository by appending
+`//` to the URL.
+- You can also optionally specify a branch or tag to use by appending
+`?ref=[` to the end of the URL. If you omit a reference, the default branch
+will be used.
+
+]
+
+
+Task has an [example remote Taskfile][example-remote-taskfile] in our repository
+that you can use for testing and that we will use throughout this document:
```yaml
version: '3'
tasks:
- hello:
- silent: true
+ default:
cmds:
- - echo "Hello from the remote Taskfile!"
+ - task: hello
+
+ hello:
+ cmds:
+ - echo "Hello Task!"
```
-and you run `task my-remote-namespace:hello`, it will print the text: "Hello
-from the remote Taskfile!" to your console.
+## Specifying a remote entrypoint
+
+By default, Task will look for one of the [supported file
+names][supported-file-names] on your local filesystem. If you want to use a
+remote file instead, you can pass its URI into the `--taskfile`/`-t` flag just
+like you would to specify a different local file. For example:
+
+
+
+```shell
+$ task --taskfile https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml
+task: [hello] echo "Hello Task!"
+Hello Task!
+```
+
+
+```shell
+$ task --taskfile https://github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
+task: [hello] echo "Hello Task!"
+Hello Task!
+```
+
+
+```shell
+$ task --taskfile git@github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
+task: [hello] echo "Hello Task!"
+Hello Task!
+```
+
+
+
+## Including remote Taskfiles
+
+Including a remote file works exactly the same way that including a local file
+does. You just need to replace the local path with a remote URI. Any tasks in
+the remote Taskfile will be available to run from your main Taskfile.
+
+
+
+```yaml
+version: '3'
+
+includes:
+ my-remote-namespace: https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml
+```
+
+
+```yaml
+version: '3'
+
+includes:
+ my-remote-namespace: https://github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
+```
+
+
+```yaml
+version: '3'
+
+includes:
+ my-remote-namespace: git@github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
+```
+
+
+
+```shell
+$ task my-remote-namespace:hello
+task: [hello] echo "Hello Task!"
+Hello Task!
+```
+
+### Authenticating using environment variables
The Taskfile location is processed by the templating system, so you can
reference environment variables in your URL if you need to add authentication.
@@ -59,19 +180,6 @@ includes:
my-remote-namespace: https://{{.TOKEN}}@raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml
```
-`TOKEN=my-token task my-remote-namespace:hello` will be resolved by Task to
-`https://my-token@raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml`
-
-## Git nodes
-
-You can also include a Taskfile from a Git node. We currently support ssh-style and http / https addresses like `git@example.com/foo/bar.git//Taskfiles.yml?ref=v1` and `https://example.com/foo/bar.git//Taskfiles.yml?ref=v1`.
-
-You need to follow this pattern : `.git//?ref=[`.
-The `ref` parameter, optional, can be a branch name or a tag, if not provided it'll pick up the default branch.
-The `path` is the path to the Taskfile in the repository.
-
-If you want to use the SSH protocol, you need to make sure that your ssh-agent has your private ssh keys added so that they can be used during authentication.
-
## Security
Running commands from sources that you do not control is always a potential
@@ -104,20 +212,26 @@ flag. Before enabling this flag, you should:
Task currently supports both `http` and `https` URLs. However, the `http`
requests will not execute by default unless you run the task with the
`--insecure` flag. This is to protect you from accidentally running a remote
-Taskfile that is via an unencrypted connection. Sources that are not protected
-by TLS are vulnerable to [man-in-the-middle attacks][man-in-the-middle-attacks]
-and should be avoided unless you know what you are doing.
+Taskfile that is downloaded via an unencrypted connection. Sources that are not
+protected by TLS are vulnerable to [man-in-the-middle
+attacks][man-in-the-middle-attacks] and should be avoided unless you know what
+you are doing.
## Caching & Running Offline
Whenever you run a remote Taskfile, the latest copy will be downloaded from the
-internet and cached locally. If for whatever reason, you lose access to the
-internet, you will still be able to run your tasks by specifying the `--offline`
-flag. This will tell Task to use the latest cached version of the file instead
-of trying to download it. You are able to use the `--download` flag to update
-the cached version of the remote files without running any tasks. You are able
-to use the `--clear-cache` flag to clear all cached version of the remote files
-without running any tasks.
+internet and cached locally. This cached file will be used for all future
+invocations of the Taskfile until the cache expires. Once it expires, Task will
+download the latest copy of the file and update the cache. By default, the cache
+is set to expire immediately. This means that Task will always fetch the latest
+version. However, the cache expiry duration can be modified by setting the
+`--expiry` flag.
+
+If for any reason you lose access to the internet or you are running Task in
+offline mode (via the `--offline` flag or `TASK_OFFLINE` environment variable),
+Task will run the any available cached files _even if they are expired_. This
+means that you should never be stuck without the ability to run your tasks as
+long as you have downloaded a remote Taskfile at least once.
By default, Task will timeout requests to download remote files after 10 seconds
and look for a cached copy instead. This timeout can be configured by setting
@@ -129,7 +243,14 @@ By default, the cache is stored in the Task temp directory, represented by the
override the location of the cache by setting the `TASK_REMOTE_DIR` environment
variable. This way, you can share the cache between different projects.
+You can force Task to ignore the cache and download the latest version
+by using the `--download` flag.
+
+You can use the `--clear-cache` flag to clear all cached remote files.
+
{/* prettier-ignore-start */}
[enabling-experiments]: ./experiments.mdx#enabling-experiments
[man-in-the-middle-attacks]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
+[supported-file-names]: https://taskfile.dev/usage/#supported-file-names
+[example-remote-taskfile]: https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml
{/* prettier-ignore-end */}
diff --git a/website/versioned_docs/version-latest/getting_started.mdx b/website/versioned_docs/version-latest/getting_started.mdx
index bef3482e..e79108d2 100644
--- a/website/versioned_docs/version-latest/getting_started.mdx
+++ b/website/versioned_docs/version-latest/getting_started.mdx
@@ -62,8 +62,8 @@ the commands.
## Calling a task
-To call the task, you simply invoke `task` followed by the name of the task you
-want to run. In this case, the name of the task is `default`, so you should run:
+To call the task, invoke `task` followed by the name of the task you want to
+run. In this case, the name of the task is `default`, so you should run:
```shell
task default
diff --git a/website/versioned_docs/version-latest/installation.mdx b/website/versioned_docs/version-latest/installation.mdx
index 223f274c..b216aa3d 100644
--- a/website/versioned_docs/version-latest/installation.mdx
+++ b/website/versioned_docs/version-latest/installation.mdx
@@ -181,6 +181,11 @@ to install a specific version:
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d v3.36.0
```
+Parameters are order specific, to set both installation directory and version:
+```shell
+sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin v3.42.1
+```
+
### GitHub Actions
If you want to install Task in GitHub Actions you can try using
diff --git a/website/versioned_docs/version-latest/reference/cli.mdx b/website/versioned_docs/version-latest/reference/cli.mdx
index 4afe84b8..e5094a9a 100644
--- a/website/versioned_docs/version-latest/reference/cli.mdx
+++ b/website/versioned_docs/version-latest/reference/cli.mdx
@@ -24,7 +24,7 @@ If `--` is given, all remaining arguments will be assigned to a special
| ----- | --------------------------- | -------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-c` | `--color` | `bool` | `true` | Colored output. Enabled by default. Set flag to `false` or use `NO_COLOR=1` to disable. |
| `-C` | `--concurrency` | `int` | `0` | Limit number tasks to run concurrently. Zero means unlimited. |
-| `-d` | `--dir` | `string` | Working directory | Sets directory of execution. |
+| `-d` | `--dir` | `string` | Working directory | Sets the directory in which Task will execute and look for a Taskfile. |
| `-n` | `--dry` | `bool` | `false` | Compiles and prints tasks in the order that they would be run, without executing them. |
| `-x` | `--exit-code` | `bool` | `false` | Pass-through the exit code of the task command. |
| `-f` | `--force` | `bool` | `false` | Forces execution even when the task is up-to-date. |
diff --git a/website/versioned_docs/version-latest/reference/package.mdx b/website/versioned_docs/version-latest/reference/package.mdx
index 1f753897..4777f5df 100644
--- a/website/versioned_docs/version-latest/reference/package.mdx
+++ b/website/versioned_docs/version-latest/reference/package.mdx
@@ -23,7 +23,145 @@ changelog entry for breaking changes to the package API.
Task is primarily a CLI tool that is agnostic of any programming language.
However, it is written in Go and therefore can also be used as a Go package too.
This can be useful if you are already using Go in your project and you need to
-extend Task's functionality in some way.
+extend Task's functionality in some way. In this document, we describe the
+public API surface of Task and how to use it. This may also be useful if you
+want to contribute to Task or understand how it works in more detail.
-The full generated documentation for the package API is available on
-[pkg.go.dev](https://pkg.go.dev/github.com/go-task/task/v3).
+## Key packages
+
+The following packages make up the most important parts of Task's package API.
+Below we have listed what they are for and some of the key types available:
+
+### [`github.com/go-task/task/v3`]
+
+The core task package provides most of the main functionality for Task including
+fetching and executing tasks from a Taskfile. At this time, the vast majority of
+the this package's functionality is exposed via the [`task.Executor`] which
+allows the user to fetch and execute tasks from a Taskfile.
+
+:::note
+This is the package which is most likely to be the subject of breaking changes
+as we refine the API.
+:::
+
+### [`github.com/go-task/task/v3/taskfile`]
+
+The `taskfile` package provides utilities for _reading_ Taskfiles from various
+sources. These sources can be local files, remote files, or even in-memory
+strings (via stdin).
+
+- [`taskfile.Node`] - A reference to the location of a Taskfile. A `Node` is an
+ interface that has several implementations:
+ - [`taskfile.FileNode`] - Local files
+ - [`taskfile.HTTPNode`] - Remote files via HTTP/HTTPS
+ - [`taskfile.GitNode`] - Remote files via Git
+ - [`taskfile.StdinNode`] - In-memory strings (via stdin)
+- [`taskfile.Reader`] - Accepts a `Node` and reads the Taskfile from it.
+- [`taskfile.Snippet`] - Mostly used for rendering Taskfile errors. A snippet
+ stores a small part of a taskfile around a given line number and column. The
+ output can be syntax highlighted for CLIs and include line/column indicators.
+
+### [`github.com/go-task/task/v3/taskfile/ast`]
+
+AST stands for ["Abstract Syntax Tree"][ast]. An AST allows us to easily
+represent the Taskfile syntax in Go. This package provides a way to parse
+Taskfile YAML into an AST and store them in memory.
+
+- [`ast.TaskfileGraph`] - Represents a set of Taskfiles and their dependencies
+ between one another.
+- [`ast.Taskfile`] - Represents a single Taskfile or a set of merged Taskfiles.
+The `Taskfile` type contains all of the subtypes for the Taskfile syntax, such
+as `tasks`, `includes`, `vars`, etc. These are not listed here for brevity.
+
+### [`github.com/go-task/task/v3/errors`]
+
+Contains all of the error types used in Task. All of these types implement the
+[`errors.TaskError`] interface which wraps Go's standard [`error`] interface.
+This allows you to call the `Code` method on the error to obtain the unique exit
+code for any error.
+
+## Reading Taskfiles
+
+Start by importing the `github.com/go-task/task/v3/taskfile` package. This
+provides all of the functions you need to read a Taskfile into memory:
+
+```go
+import (
+ "github.com/go-task/task/v3/taskfile"
+)
+```
+
+Reading Taskfiles is done by using a [`taskfile.Reader`] and an implementation
+of [`taskfile.Node`]. In this example we will read a local file by using the
+[`taskfile.FileNode`] type. You can create this by calling the
+[`taskfile.NewFileNode`] function:
+
+```go
+node := taskfile.NewFileNode("Taskfile.yml", "./path/to/dir")
+```
+
+and then create a your reader by calling the [`taskfile.NewReader`] function and
+passing any functional options you want to use. For example, you could pass a
+debug function to the reader which will be called with debug messages:
+
+```go
+reader := taskfile.NewReader(
+ taskfile.WithDebugFunc(func(s string) {
+ slog.Debug(s)
+ }),
+)
+```
+
+Now that everything is set up, you can read the Taskfile (and any included
+Taskfiles) by calling the `Read` method on the reader and pass the `Node` as an
+argument:
+
+```go
+ctx := context.Background()
+tfg, err := reader.Read(ctx, node)
+// handle error
+```
+
+This returns an instance of [`ast.TaskfileGraph`] which is a "Directed Acyclic
+Graph" (DAG) of all the parsed Taskfiles. We use this graph to store and resolve
+the `includes` directives in Taskfiles. However most of the time, you will want
+a merged Taskfile. To do this, simply call the `Merge` method on the Taskfile
+graph:
+
+```go
+tf, err := tfg.Merge()
+// handle error
+```
+
+This compiles the DAG into a single [`ast.Taskfile`] containing all the
+namespaces and tasks from all the Taskfiles we read.
+
+:::note
+We plan to remove AST merging in the future as it is unnecessarily complex and
+causes lots of issues with scoping.
+:::
+
+{/* prettier-ignore-start */}
+[`github.com/go-task/task/v3`]: https://pkg.go.dev/github.com/go-task/task/v3
+[`github.com/go-task/task/v3/taskfile`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile
+[`github.com/go-task/task/v3/taskfile/ast`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile/ast
+[`github.com/go-task/task/v3/errors`]: https://pkg.go.dev/github.com/go-task/task/v3/errors
+
+[`ast.TaskfileGraph`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile/ast#TaskfileGraph
+[`ast.Taskfile`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile/ast#Taskfile
+[`taskfile.Node`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Node
+[`taskfile.FileNode`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#FileNode
+[`taskfile.HTTPNode`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#HTTPNode
+[`taskfile.GitNode`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#GitNode
+[`taskfile.StdinNode`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#StdinNode
+[`taskfile.NewFileNode`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#NewFileNode
+[`taskfile.Reader`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Reader
+[`taskfile.NewReader`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#NewReader
+[`taskfile.Snippet`]: https://pkg.go.dev/github.com/go-task/task/v3/taskfile#Snippet
+[`task.Executor`]: https://pkg.go.dev/github.com/go-task/task/v3#Executor
+[`task.Formatter`]: https://pkg.go.dev/github.com/go-task/task/v3#Formatter
+[`errors.TaskError`]: https://pkg.go.dev/github.com/go-task/task/v3/errors#TaskError
+[`error`]: https://pkg.go.dev/builtin#error
+
+[ast]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
+{/* prettier-ignore-end */}
diff --git a/website/versioned_docs/version-latest/reference/schema.mdx b/website/versioned_docs/version-latest/reference/schema.mdx
index 29dc58c8..f426e3fa 100644
--- a/website/versioned_docs/version-latest/reference/schema.mdx
+++ b/website/versioned_docs/version-latest/reference/schema.mdx
@@ -196,9 +196,12 @@ If defined as a string this is a shell command, otherwise it is a map defining a
The `for` parameter can be defined as a string, a list of strings or a map. If
it is defined as a string, you can give it any of the following values:
-- `source` - Will run the command for each source file defined on the task.
+- `sources` - Will run the command for each source file defined on the task.
(Glob patterns will be resolved, so `*.go` will run for every Go file that
matches).
+- `generates` - Will run the command for each file defined in the task's generates
+ list. (Glob patterns will be resolved, so `*.txt` will run for every text file
+ that matches).
If it is defined as a list of strings, the command will be run for each value.
diff --git a/website/versioned_docs/version-latest/reference/templating.mdx b/website/versioned_docs/version-latest/reference/templating.mdx
index 98d45bae..fc5e4fb0 100644
--- a/website/versioned_docs/version-latest/reference/templating.mdx
+++ b/website/versioned_docs/version-latest/reference/templating.mdx
@@ -115,7 +115,7 @@ special variable will be overridden.
| `TASKFILE` | The absolute path of the included Taskfile. |
| `TASKFILE_DIR` | The absolute path of the included Taskfile directory. |
| `TASK_DIR` | The absolute path of the directory where the task is executed. |
-| `USER_WORKING_DIR` | The absolute path of the directory `task` was called from. |
+| `USER_WORKING_DIR` | The absolute path of the directory `task` was called from, or the value of `--dir` (`-d`) if given. |
| `CHECKSUM` | The checksum of the files listed in `sources`. Only available within the `status` prop and if method is set to `checksum`. |
| `TIMESTAMP` | The date object of the greatest timestamp of the files listed in `sources`. Only available within the `status` prop and if method is set to `timestamp`. |
| `TASK_VERSION` | The current version of task. |
diff --git a/website/versioned_docs/version-latest/usage.mdx b/website/versioned_docs/version-latest/usage.mdx
index 0bfa5086..e672cf82 100644
--- a/website/versioned_docs/version-latest/usage.mdx
+++ b/website/versioned_docs/version-latest/usage.mdx
@@ -61,6 +61,12 @@ In this example, we can run `cd ` and `task up` and as long as the
`` directory contains a `docker-compose.yml`, the Docker composition
will be brought up.
+:::info
+
+`.USER_WORKING_DIR` will contain the value of the `--dir` (`-d`) flag, if given.
+
+:::
+
### Running a global Taskfile
If you call Task with the `--global` (alias `-g`) flag, it will look for your
@@ -309,45 +315,38 @@ You can flatten the included Taskfile tasks into the main Taskfile by using the
It means that the included Taskfile tasks will be available without the namespace.
-
+
+
-
+```yaml
+version: '3'
- ```yaml
- version: '3'
+includes:
+ lib:
+ taskfile: ./Included.yml
+ flatten: true
- includes:
- lib:
- taskfile: ./Included.yml
- flatten: true
+tasks:
+ greet:
+ cmds:
+ - echo "Greet"
+ - task: foo
+```
- tasks:
- greet:
- cmds:
- - echo "Greet"
- - task: foo
- ```
+
+
+```yaml
+version: '3'
-
-
-
- ```yaml
- version: '3'
-
- tasks:
- foo:
- cmds:
- - echo "Foo"
- ```
-
-
-
+tasks:
+ foo:
+ cmds:
+ - echo "Foo"
+```
+
+
If you run `task -a` it will print :
@@ -368,43 +367,37 @@ Foo
If multiple tasks have the same name, an error will be thrown:
-
+
+
-
+```yaml
+version: '3'
+includes:
+ lib:
+ taskfile: ./Included.yml
+ flatten: true
- ```yaml
- version: '3'
- includes:
- lib:
- taskfile: ./Included.yml
- flatten: true
+tasks:
+ greet:
+ cmds:
+ - echo "Greet"
+ - task: foo
+```
- tasks:
- greet:
- cmds:
- - echo "Greet"
- - task: foo
- ```
+
+
+```yaml
+version: '3'
-
-
+tasks:
+ greet:
+ cmds:
+ - echo "Foo"
+```
- ```yaml
- version: '3'
-
- tasks:
- greet:
- cmds:
- - echo "Foo"
- ```
-
-
-
+
+
If you run `task -a` it will print:
```text
@@ -420,35 +413,29 @@ You can do this by using the [`excludes` option](#exclude-tasks-from-being-inclu
You can exclude tasks from being included by using the `excludes` option. This option takes the list of tasks to be excluded from this include.
-
+
+
-
- ```yaml
- version: '3'
- includes:
- included:
- taskfile: ./Included.yml
- excludes: [foo]
- ```
+```yaml
+version: '3'
+ includes:
+ included:
+ taskfile: ./Included.yml
+ excludes: [foo]
+```
+
+
-
-
+```yaml
+version: '3'
- ```yaml
- version: '3'
+tasks:
+ foo: echo "Foo"
+ bar: echo "Bar"
+```
- tasks:
- foo: echo "Foo"
- bar: echo "Bar"
- ```
-
-
-
+
`task included:foo` will throw an error because the `foo` task is excluded but `task included:bar` will work and display `Bar`.
@@ -1113,53 +1100,38 @@ variable types are supported:
- `int`
- `float`
- `array`
+- `map`
:::note
-Maps are not supported by default, but there is an
-[experiment][map-variables] that can be enabled to add support. If
-you're interested in this functionality, we would appreciate your feedback.
-:pray:
-
-In the meantime, it is technically possible to define a map using a `ref` resolver and a templating function. For example:
-
-```yaml
-version: '3'
-
-tasks:
- task-with-map:
- vars:
- FOO:
- ref: dict "a" "1" "b" "2" "c" "3"
- cmds:
- - echo {{.FOO}}
-```
-
-```txt
-map[a:1 b:2 c:3]
-```
-
-OR by using the same technique with JSON:
-
-```yaml
-version: '3'
-
-tasks:
- task-with-map:
- vars:
- JSON: '{"a": 1, "b": 2, "c": 3}'
- FOO:
- ref: "fromJson .JSON"
- cmds:
- - echo {{.FOO}}
-```
-
-```txt
-map[a:1 b:2 c:3]
-```
+Defining a map requires that you use a special `map` subkey (see example below).
:::
+```yaml
+version: 3
+
+tasks:
+ foo:
+ vars:
+ STRING: 'Hello, World!'
+ BOOL: true
+ INT: 42
+ FLOAT: 3.14
+ ARRAY: [1, 2, 3]
+ MAP:
+ map: {A: 1, B: 2, C: 3}
+ cmds:
+ - 'echo {{.STRING}}' # Hello, World!
+ - 'echo {{.BOOL}}' # true
+ - 'echo {{.INT}}' # 42
+ - 'echo {{.FLOAT}}' # 3.14
+ - 'echo {{.ARRAY}}' # [1 2 3]
+ - 'echo {{.ARRAY.0}}' # 1
+ - 'echo {{.MAP}}' # map[A:1 B:2 C:3]
+ - 'echo {{.MAP.A}}' # 1
+```
+
Variables can be set in many places in a Taskfile. When executing
[templates][templating-reference], Task will look for variables in the order
listed below (most important first):
@@ -1270,13 +1242,8 @@ a value from one task to another. However, the templating engine is only able to
output strings. If you want to pass something other than a string to another
task then you will need to use a reference (`ref`) instead.
-
-
-
+
+
```yaml
version: 3
@@ -1295,7 +1262,7 @@ tasks:
```
-
+
```yaml
version: 3
@@ -1314,7 +1281,8 @@ tasks:
- 'echo {{index .FOO 0}}' # <-- FOO is still a map so the task outputs 'A' as expected
```
-
+
+
This also works the same way when calling `deps` and when defining
a variable and can be used in any combination:
@@ -1339,8 +1307,8 @@ tasks:
```
All references use the same templating syntax as regular templates, so in
-addition to simply calling `.FOO`, you can also pass subkeys (`.FOO.BAR`) or
-indexes (`index .FOO 0`) and use functions (`len .FOO`) as described in the
+addition to calling `.FOO`, you can also pass subkeys (`.FOO.BAR`) or indexes
+(`index .FOO 0`) and use functions (`len .FOO`) as described in the
[templating-reference][templating-reference]:
```yaml
@@ -1360,6 +1328,29 @@ tasks:
- 'echo {{.FOO}}' # <-- FOO is just the letter 'A'
```
+### Parsing JSON/YAML into map variables
+
+If you have a raw JSON or YAML string that you want to process in Task, you can
+use a combination of the `ref` keyword and the `fromJson` or `fromYaml`
+templating functions to parse the string into a map variable. For example:
+
+```yaml
+version: '3'
+
+tasks:
+ task-with-map:
+ vars:
+ JSON: '{"a": 1, "b": 2, "c": 3}'
+ FOO:
+ ref: "fromJson .JSON"
+ cmds:
+ - echo {{.FOO}}
+```
+
+```txt
+map[a:1 b:2 c:3]
+```
+
## Looping over values
Task allows you to loop over certain values and execute a command for each.
@@ -1433,9 +1424,13 @@ tasks:
cmd: echo "{{.ITEM.OS}}/{{.ITEM.ARCH}}"
```
-### Looping over your task's sources
+### Looping over your task's sources or generated files
-You are also able to loop over the sources of your task:
+You are also able to loop over the sources of your task or the files it
+generates:
+
+
+
```yaml
version: '3'
@@ -1450,14 +1445,37 @@ tasks:
cmd: cat {{ .ITEM }}
```
-This will also work if you use globbing syntax in your sources. For example, if
-you specify a source for `*.txt`, the loop will iterate over all files that
-match that glob.
+
+
-Source paths will always be returned as paths relative to the task directory. If
-you need to convert this to an absolute path, you can use the built-in
-`joinPath` function. There are some [special variables](/reference/templating/#special-variables)
-that you may find useful for this.
+```yaml
+version: '3'
+
+tasks:
+ default:
+ generates:
+ - foo.txt
+ - bar.txt
+ cmds:
+ - for: generates
+ cmd: cat {{ .ITEM }}
+```
+
+
+
+
+This will also work if you use globbing syntax in `sources` or `generates`. For
+example, if you specify a source for `*.txt`, the loop will iterate over all
+files that match that glob.
+
+Paths will always be returned as paths relative to the task directory. If you
+need to convert this to an absolute path, you can use the built-in `joinPath`
+function. There are some [special
+variables](/reference/templating/#special-variables) that you may find useful
+for this.
+
+
+
```yaml
version: '3'
@@ -1475,11 +1493,33 @@ tasks:
cmd: cat {{joinPath .MY_DIR .ITEM}}
```
+
+
+
+```yaml
+version: '3'
+
+tasks:
+ default:
+ vars:
+ MY_DIR: /path/to/dir
+ dir: '{{.MY_DIR}}'
+ generates:
+ - foo.txt
+ - bar.txt
+ cmds:
+ - for: generates
+ cmd: cat {{joinPath .MY_DIR .ITEM}}
+```
+
+
+
+
### Looping over variables
-To loop over the contents of a variable, you simply need to specify the variable
-you want to loop over. By default, string variables will be split on any
-whitespace characters.
+To loop over the contents of a variable, use the `var` key followed by the name
+of the variable you want to loop over. By default, string variables will be
+split on any whitespace characters.
```yaml
version: '3'
@@ -1508,7 +1548,7 @@ tasks:
cmd: cat {{.ITEM}}
```
-You can also loop over arrays directly and maps:
+You can also loop over arrays and maps directly:
```yaml
version: 3
@@ -1674,36 +1714,45 @@ clear what they contain:
version: '3'
tasks:
- echo-*:
+ start:*:*:
vars:
- TEXT: '{{index .MATCH 0}}'
+ SERVICE: "{{index .MATCH 0}}"
+ REPLICAS: "{{index .MATCH 1}}"
cmds:
- - echo {{.TEXT}}
+ - echo "Starting {{.SERVICE}} with {{.REPLICAS}} replicas"
- run-*-*:
+ start:*:
vars:
- ARG_1: '{{index .MATCH 0}}'
- ARG_2: '{{index .MATCH 1}}'
+ SERVICE: "{{index .MATCH 0}}"
cmds:
- - echo {{.ARG_1}} {{.ARG_2}}
+ - echo "Starting {{.SERVICE}}"
```
+This call matches the `start:*` task and the string "foo" is captured by the
+wildcard and stored in the `.MATCH` variable. We then index the `.MATCH` array
+and store the result in the `.SERVICE` variable which is then echoed out in the
+cmds:
+
```shell
-# This call matches the "echo-*" task and the string "hello" is captured by the
-# wildcard and stored in the .MATCH variable. We then index the .MATCH array and
-# store the result in the .TEXT variable which is then echoed out in the cmds.
-$ task echo-hello
-hello
-# You can use whitespace in your arguments as long as you quote the task name
-$ task "echo-hello world"
-hello world
-# And you can pass multiple arguments
-$ task run-foo-bar
-foo bar
+$ task start:foo
+Starting foo
```
-If multiple matching tasks are found, an error occurs. If you are using included
-Taskfiles, tasks in parent files will be considered first.
+You can use whitespace in your arguments as long as you quote the task name:
+
+```shell
+$ task "start:foo bar"
+Starting foo bar
+```
+
+If multiple matching tasks are found, the first one listed in the Taskfile will
+be used. If you are using included Taskfiles, tasks in parent files will be
+considered first.
+
+```shell
+$ task start:foo:3
+Starting foo with 3 replicas
+```
## Doing task cleanup with `defer`
@@ -2285,9 +2334,11 @@ With the flags `--watch` or `-w` task will watch for file changes and run the
task again. This requires the `sources` attribute to be given, so task knows
which files to watch.
-The default watch interval is 5 seconds, but it's possible to change it by
-either setting `interval: '500ms'` in the root of the Taskfile or by passing it
-as an argument like `--interval=500ms`.
+The default watch interval is 100 milliseconds, but it's possible to change it
+by either setting `interval: '500ms'` in the root of the Taskfile or by passing
+it as an argument like `--interval=500ms`.
+This interval is the time Task will wait for duplicated events. It will only run
+the task again once, even if multiple changes happen within the interval.
Also, it's possible to set `watch: true` in a given task and it'll automatically
run in watch mode:
@@ -2317,6 +2368,5 @@ if called by another task, either directly or as a dependency.
{/* prettier-ignore-start */}
[gotemplate]: https://golang.org/pkg/text/template/
-[map-variables]: ./experiments/map_variables.mdx
[templating-reference]: ./reference/templating.mdx
{/* prettier-ignore-end */}
]