mirror of
https://github.com/go-task/task.git
synced 2025-11-27 22:38:20 +02:00
feat: add task-level dotenv support (#904)
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## Unreleased
|
## 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`
|
- 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)).
|
([#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/)
|
- 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. |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `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`. |
|
| `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"
|
- 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
|
## Including other Taskfiles
|
||||||
|
|
||||||
If you want to share tasks between different projects (Taskfiles), you can use
|
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.",
|
"description": "A set of environment variables that will be made available to shell commands.",
|
||||||
"$ref": "#/definitions/3/env"
|
"$ref": "#/definitions/3/env"
|
||||||
},
|
},
|
||||||
|
"dotenv": {
|
||||||
|
"description": "A list of `.env` file paths to be parsed.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"silent": {
|
"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.",
|
"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",
|
"type": "boolean",
|
||||||
|
|||||||
48
task_test.go
48
task_test.go
@@ -1410,6 +1410,54 @@ func TestDotenvHasEnvVarInPath(t *testing.T) {
|
|||||||
tt.Run(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) {
|
func TestExitImmediately(t *testing.T) {
|
||||||
const dir = "testdata/exit_immediately"
|
const dir = "testdata/exit_immediately"
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type Task struct {
|
|||||||
Dir string
|
Dir string
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
Env *Vars
|
Env *Vars
|
||||||
|
Dotenv []string
|
||||||
Silent bool
|
Silent bool
|
||||||
Interactive bool
|
Interactive bool
|
||||||
Internal bool
|
Internal bool
|
||||||
@@ -65,6 +66,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
Dir string
|
Dir string
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
Env *Vars
|
Env *Vars
|
||||||
|
Dotenv []string
|
||||||
Silent bool
|
Silent bool
|
||||||
Interactive bool
|
Interactive bool
|
||||||
Internal bool
|
Internal bool
|
||||||
@@ -89,6 +91,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
t.Dir = task.Dir
|
t.Dir = task.Dir
|
||||||
t.Vars = task.Vars
|
t.Vars = task.Vars
|
||||||
t.Env = task.Env
|
t.Env = task.Env
|
||||||
|
t.Dotenv = task.Dotenv
|
||||||
t.Silent = task.Silent
|
t.Silent = task.Silent
|
||||||
t.Interactive = task.Interactive
|
t.Interactive = task.Interactive
|
||||||
t.Internal = task.Internal
|
t.Internal = task.Internal
|
||||||
@@ -117,6 +120,7 @@ func (t *Task) DeepCopy() *Task {
|
|||||||
Dir: t.Dir,
|
Dir: t.Dir,
|
||||||
Vars: t.Vars.DeepCopy(),
|
Vars: t.Vars.DeepCopy(),
|
||||||
Env: t.Env.DeepCopy(),
|
Env: t.Env.DeepCopy(),
|
||||||
|
Dotenv: deepCopySlice(t.Dotenv),
|
||||||
Silent: t.Silent,
|
Silent: t.Silent,
|
||||||
Interactive: t.Interactive,
|
Interactive: t.Interactive,
|
||||||
Internal: t.Internal,
|
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
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
"github.com/go-task/task/v3/internal/execext"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
"github.com/go-task/task/v3/internal/status"
|
"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),
|
Dir: r.Replace(origTask.Dir),
|
||||||
Vars: nil,
|
Vars: nil,
|
||||||
Env: nil,
|
Env: nil,
|
||||||
|
Dotenv: r.ReplaceSlice(origTask.Dotenv),
|
||||||
Silent: origTask.Silent,
|
Silent: origTask.Silent,
|
||||||
Interactive: origTask.Interactive,
|
Interactive: origTask.Interactive,
|
||||||
Internal: origTask.Internal,
|
Internal: origTask.Internal,
|
||||||
@@ -76,8 +80,28 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
|||||||
new.Prefix = new.Task
|
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 = &taskfile.Vars{}
|
||||||
new.Env.Merge(r.ReplaceVars(e.Taskfile.Env))
|
new.Env.Merge(r.ReplaceVars(e.Taskfile.Env))
|
||||||
|
new.Env.Merge(r.ReplaceVars(dotenvEnvs))
|
||||||
new.Env.Merge(r.ReplaceVars(origTask.Env))
|
new.Env.Merge(r.ReplaceVars(origTask.Env))
|
||||||
if evaluateShVars {
|
if evaluateShVars {
|
||||||
err = new.Env.Range(func(k string, v taskfile.Var) error {
|
err = new.Env.Range(func(k string, v taskfile.Var) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user