mirror of
https://github.com/go-task/task.git
synced 2025-01-04 03:48:02 +02:00
feat: add task-level dotenv support (#904)
This commit is contained in:
parent
9cf930454d
commit
99d7338c29
@ -2,6 +2,8 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Add task-level `dotenv` support
|
||||
([#389](https://github.com/go-task/task/issues/389), [#904](https://github.com/go-task/task/pull/904)).
|
||||
- It's now possible to use global level variables on `includes`
|
||||
([#942](https://github.com/go-task/task/issues/942), [#943](https://github.com/go-task/task/pull/943)).
|
||||
- The website got a brand new [translation to Chinese](https://task-zh.readthedocs.io/zh_CN/latest/)
|
||||
|
@ -130,6 +130,7 @@ includes:
|
||||
| `dir` | `string` | | The directory in which this task should run. Defaults to the current working directory. |
|
||||
| `vars` | [`map[string]Variable`](#variable) | | A set of variables that can be used in the task. |
|
||||
| `env` | [`map[string]Variable`](#variable) | | A set of environment variables that will be made available to shell commands. |
|
||||
| `dotenv` | `[]string` | | A list of `.env` file paths to be parsed. |
|
||||
| `silent` | `bool` | `false` | Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`. When combined with the `--list` flag, task descriptions will be hidden. |
|
||||
| `interactive` | `bool` | `false` | Tells task that the command is interactive. |
|
||||
| `internal` | `bool` | `false` | Stops a task from being callable on the command line. It will also be omitted from the output when used with `--list`. |
|
||||
|
@ -118,6 +118,45 @@ tasks:
|
||||
- echo "Using $KEYNAME and endpoint $ENDPOINT"
|
||||
```
|
||||
|
||||
Dotenv files can also be specified at the task level:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
env:
|
||||
ENV: testing
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']
|
||||
cmds:
|
||||
- echo "Using $KEYNAME and endpoint $ENDPOINT"
|
||||
```
|
||||
|
||||
Environment variables specified explicitly at the task-level will override
|
||||
variables defined in dotfiles:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
env:
|
||||
ENV: testing
|
||||
|
||||
tasks:
|
||||
greet:
|
||||
dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']
|
||||
env:
|
||||
KEYNAME: DIFFERENT_VALUE
|
||||
cmds:
|
||||
- echo "Using $KEYNAME and endpoint $ENDPOINT"
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
Please note that you are not currently able to use the `dotenv` key inside included Taskfiles.
|
||||
|
||||
:::
|
||||
|
||||
## Including other Taskfiles
|
||||
|
||||
If you want to share tasks between different projects (Taskfiles), you can use
|
||||
|
7
docs/static/schema.json
vendored
7
docs/static/schema.json
vendored
@ -116,6 +116,13 @@
|
||||
"description": "A set of environment variables that will be made available to shell commands.",
|
||||
"$ref": "#/definitions/3/env"
|
||||
},
|
||||
"dotenv": {
|
||||
"description": "A list of `.env` file paths to be parsed.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"silent": {
|
||||
"description": "Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`. When combined with the `--list` flag, task descriptions will be hidden.",
|
||||
"type": "boolean",
|
||||
|
48
task_test.go
48
task_test.go
@ -1410,6 +1410,54 @@ func TestDotenvHasEnvVarInPath(t *testing.T) {
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestTaskDotenv(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv_task/default",
|
||||
Target: "dotenv",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"dotenv.txt": "foo",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestTaskDotenvFail(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv_task/default",
|
||||
Target: "no-dotenv",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"no-dotenv.txt": "global",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestTaskDotenvOverriddenByEnv(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv_task/default",
|
||||
Target: "dotenv-overridden-by-env",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"dotenv-overridden-by-env.txt": "overridden",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestTaskDotenvWithVarName(t *testing.T) {
|
||||
tt := fileContentTest{
|
||||
Dir: "testdata/dotenv_task/default",
|
||||
Target: "dotenv-with-var-name",
|
||||
TrimSpace: true,
|
||||
Files: map[string]string{
|
||||
"dotenv-with-var-name.txt": "foo",
|
||||
},
|
||||
}
|
||||
tt.Run(t)
|
||||
}
|
||||
|
||||
func TestExitImmediately(t *testing.T) {
|
||||
const dir = "testdata/exit_immediately"
|
||||
|
||||
|
@ -19,6 +19,7 @@ type Task struct {
|
||||
Dir string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
@ -65,6 +66,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
Dir string
|
||||
Vars *Vars
|
||||
Env *Vars
|
||||
Dotenv []string
|
||||
Silent bool
|
||||
Interactive bool
|
||||
Internal bool
|
||||
@ -89,6 +91,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
t.Dir = task.Dir
|
||||
t.Vars = task.Vars
|
||||
t.Env = task.Env
|
||||
t.Dotenv = task.Dotenv
|
||||
t.Silent = task.Silent
|
||||
t.Interactive = task.Interactive
|
||||
t.Internal = task.Internal
|
||||
@ -117,6 +120,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
Dir: t.Dir,
|
||||
Vars: t.Vars.DeepCopy(),
|
||||
Env: t.Env.DeepCopy(),
|
||||
Dotenv: deepCopySlice(t.Dotenv),
|
||||
Silent: t.Silent,
|
||||
Interactive: t.Interactive,
|
||||
Internal: t.Internal,
|
||||
|
1
testdata/dotenv_task/default/.env
vendored
Normal file
1
testdata/dotenv_task/default/.env
vendored
Normal file
@ -0,0 +1 @@
|
||||
FOO=foo
|
1
testdata/dotenv_task/default/.gitignore
vendored
Normal file
1
testdata/dotenv_task/default/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.txt
|
28
testdata/dotenv_task/default/Taskfile.yml
vendored
Normal file
28
testdata/dotenv_task/default/Taskfile.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
version: '3'
|
||||
|
||||
env:
|
||||
FOO: global
|
||||
|
||||
tasks:
|
||||
dotenv:
|
||||
dotenv: ['.env']
|
||||
cmds:
|
||||
- echo "$FOO" > dotenv.txt
|
||||
|
||||
dotenv-overridden-by-env:
|
||||
dotenv: ['.env']
|
||||
env:
|
||||
FOO: overridden
|
||||
cmds:
|
||||
- echo "$FOO" > dotenv-overridden-by-env.txt
|
||||
|
||||
dotenv-with-var-name:
|
||||
vars:
|
||||
DOTENV: .env
|
||||
dotenv: ['{{.DOTENV}}']
|
||||
cmds:
|
||||
- echo "$FOO" > dotenv-with-var-name.txt
|
||||
|
||||
no-dotenv:
|
||||
cmds:
|
||||
- echo "$FOO" > no-dotenv.txt
|
24
variables.go
24
variables.go
@ -1,8 +1,11 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/status"
|
||||
@ -55,6 +58,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
Dir: r.Replace(origTask.Dir),
|
||||
Vars: nil,
|
||||
Env: nil,
|
||||
Dotenv: r.ReplaceSlice(origTask.Dotenv),
|
||||
Silent: origTask.Silent,
|
||||
Interactive: origTask.Interactive,
|
||||
Internal: origTask.Internal,
|
||||
@ -76,8 +80,28 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
new.Prefix = new.Task
|
||||
}
|
||||
|
||||
dotenvEnvs := &taskfile.Vars{}
|
||||
if len(new.Dotenv) > 0 {
|
||||
for _, dotEnvPath := range new.Dotenv {
|
||||
dotEnvPath = filepathext.SmartJoin(new.Dir, dotEnvPath)
|
||||
if _, err := os.Stat(dotEnvPath); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
envs, err := godotenv.Read(dotEnvPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range envs {
|
||||
if _, ok := dotenvEnvs.Mapping[key]; !ok {
|
||||
dotenvEnvs.Set(key, taskfile.Var{Static: value})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new.Env = &taskfile.Vars{}
|
||||
new.Env.Merge(r.ReplaceVars(e.Taskfile.Env))
|
||||
new.Env.Merge(r.ReplaceVars(dotenvEnvs))
|
||||
new.Env.Merge(r.ReplaceVars(origTask.Env))
|
||||
if evaluateShVars {
|
||||
err = new.Env.Range(func(k string, v taskfile.Var) error {
|
||||
|
Loading…
Reference in New Issue
Block a user