Compare commits

...

16 Commits

Author SHA1 Message Date
Andrey Nering
1072ff5950 v3.19.0 2022-12-05 22:28:15 -03:00
Andrey Nering
983f6fff5d Upgrade @go-task/go-npm to support pnpm 2022-12-05 22:24:10 -03:00
Pete Davison
b3627fcb18 Run Taskfiles from sub/child directories (#920) 2022-12-05 21:58:20 -03:00
Pete Davison
99d7338c29 feat: add task-level dotenv support (#904) 2022-12-05 21:25:16 -03:00
Andrey Nering
9cf930454d Add CHANGELOG entry for #492, #493 2022-12-05 20:56:47 -03:00
Andrey Nering
4b4962e8c6 Merge pull request #943 from go-task/fix-interpolation-in-includes
fix: interpolate includes taskfile and dir
2022-12-05 20:54:04 -03:00
Andrey Nering
f2afa77114 Website: Update Community > JSON Schema section 2022-11-27 19:50:05 -03:00
Andrey Nering
3aa647c89b Website: Update Chinese translation link label 2022-11-27 19:49:07 -03:00
Andrey Nering
45ab4dc718 Website: Link to the Chinese translation
Closes #921
2022-11-26 18:24:31 -03:00
Andrey Nering
d1850e8fd2 Add Gold Sponsors section 2022-11-24 13:05:24 -03:00
Pete Davison
f1d516cf2a fix: interpolate includes taskfile and dir 2022-11-23 17:58:08 +00:00
Andrey Nering
d55282b53c Merge pull request #934 from go-task/dependabot/npm_and_yarn/docs/loader-utils-2.0.4
Bump loader-utils from 2.0.3 to 2.0.4 in /docs
2022-11-19 22:41:44 -03:00
Andrey Nering
ef9f7af0c5 Merge pull request #938 from rrrix/jetbrains-json-schema-validator-workaround
JetBrains JSON Schema Validator Error Workaround
2022-11-19 22:15:01 -03:00
Rick Bowen
8823887bb4 JetBrains JSON Schema Validator Error Workaround
`schema.json` Workaround for these two JetBrains issues:

- [IDEA-236928 json schema: False positive "Schema validation: Validates to more than one variant" with "oneOf"](https://youtrack.jetbrains.com/issue/IDEA-236928)
- [IDEA-265710 JSON schema validation fails if anyOf is nested in oneOf](https://youtrack.jetbrains.com/issue/IDEA-265710)

There is no material effect on the actual schema validation, as the `oneOf`/`allOf` evaluates only one list entry.

`allOf([1])` is `1`, and `oneOf([1])` is also `1`.

Fixes #847
2022-11-17 16:22:51 -08:00
Andrey Nering
593980e45a Upgrade @go-task/go-npm 2022-11-17 21:04:53 -03:00
dependabot[bot]
081dc16312 Bump loader-utils from 2.0.3 to 2.0.4 in /docs
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.3...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-16 05:26:21 +00:00
32 changed files with 522 additions and 52 deletions

View File

@@ -1,6 +1,20 @@
# Changelog
## v3.18.0
## v3.19.0 - 2022-12-05
- Installation via npm now supports [pnpm](https://pnpm.io/) as well
([go-task/go-npm#2](https://github.com/go-task/go-npm/issues/2), [go-task/go-npm#3](https://github.com/go-task/go-npm/pull/3)).
- It's now possible to run Taskfiles from subdirectories! A new `USER_WORKING_DIR` special
variable was added to add even more flexibility for monorepos
([#289](https://github.com/go-task/task/issues/289), [#920](https://github.com/go-task/task/pull/920)).
- 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/)
by [@DeronW](https://github.com/DeronW). Thanks!
## v3.18.0 - 2022-11-12
- Show aliases on `task --list --silent` (`task --ls`). This means that aliases
will be completed by the completion scripts
@@ -12,7 +26,7 @@
aliased to `docs`
([#661](https://github.com/go-task/task/issues/661), [#815](https://github.com/go-task/task/pull/815)).
## v3.17.0
## v3.17.0 - 2022-10-14
- Add a "Did you mean ...?" suggestion when a task does not exits another one
with a similar name is found

View File

@@ -13,3 +13,15 @@
<a href="https://taskfile.dev/installation/">Installation</a> | <a href="https://taskfile.dev/usage/">Documentation</a> | <a href="https://twitter.com/taskfiledev">Twitter</a> | <a href="https://discord.gg/6TY36E39UK">Discord</a>
</p>
</div>
## Gold Sponsors
<div align="center">
| [Appwrite][appwrite] |
| - |
| [![Appwrite](/docs/static/img/appwrite.svg)][appwrite] |
</div>
[appwrite]: https://appwrite.io/?utm_source=task_github&utm_medium=social&utm_campaign=task_oss_fund

11
docs/constants.js Normal file
View File

@@ -0,0 +1,11 @@
const GITHUB_URL = 'https://github.com/go-task/task';
const TWITTER_URL = 'https://twitter.com/taskfiledev';
const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
const CHINESE_URL = 'https://task-zh.readthedocs.io/zh_CN/latest/';
module.exports = {
GITHUB_URL,
TWITTER_URL,
DISCORD_URL,
CHINESE_URL
};

View File

@@ -55,6 +55,7 @@ There are some special variables that is available on the templating system:
| `TASK` | The name of the current task. |
| `ROOT_DIR` | The absolute path of the root Taskfile. |
| `TASKFILE_DIR` | The absolute path of the included Taskfile. |
| `USER_WORKING_DIR` | The absolute path of the directory `task` was called from. |
| `CHECKSUM` | The checksum of the files listed in `sources`. Only available within the `status` prop and if method is set to `checksum`. |
| `TIMESTAMP` | The date object of the greatest timestamp of the files listes in `sources`. Only available within the `status` prop and if method is set to `timestamp`. |
@@ -130,6 +131,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`. |

View File

@@ -5,7 +5,21 @@ sidebar_position: 6
# Changelog
## v3.18.0
## v3.19.0 - 2022-12-05
- Installation via npm now supports [pnpm](https://pnpm.io/) as well
([go-task/go-npm#2](https://github.com/go-task/go-npm/issues/2), [go-task/go-npm#3](https://github.com/go-task/go-npm/pull/3)).
- It's now possible to run Taskfiles from subdirectories! A new `USER_WORKING_DIR` special
variable was added to add even more flexibility for monorepos
([#289](https://github.com/go-task/task/issues/289), [#920](https://github.com/go-task/task/pull/920)).
- 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/)
by [@DeronW](https://github.com/DeronW). Thanks!
## v3.18.0 - 2022-11-12
- Show aliases on `task --list --silent` (`task --ls`). This means that aliases
will be completed by the completion scripts
@@ -17,7 +31,7 @@ sidebar_position: 6
aliased to `docs`
([#661](https://github.com/go-task/task/issues/661), [#815](https://github.com/go-task/task/pull/815)).
## v3.17.0
## v3.17.0 - 2022-10-14
- Add a "Did you mean ...?" suggestion when a task does not exits another one
with a similar name is found

View File

@@ -9,17 +9,22 @@ Some of the work to improve the Task ecosystem is done by the community, be
it installation methods or integrations with code editor. I (the author) am
thankful for everyone that helps me to improve the overall experience.
## Translations
[@DeronW](https://github.com/DeronW) maintains the
[Chinese translation](https://task-zh.readthedocs.io/zh_CN/latest/) of the
website [on this repository](https://github.com/DeronW/task).
## Editor Integrations
### JSON Schema
[@KROSF](https://github.com/KROSF) worked on a JSON Schema [into this Gist](https://gist.github.com/KROSF/c5435acf590acd632f71bb720f685895),
which later was made officially available by [@Crandel](https://github.com/Crandel)
at [https://json.schemastore.org/taskfile.json](https://json.schemastore.org/taskfile.json).
Further improvements are possible by opening pull requests changing
[this file](https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/taskfile.json).
Some code editors, like Visual Studio Code, make use of Schema Store
automatically.
Initial work on the schema was made by [@KROSF](https://github.com/KROSF)
on [this Gist](https://gist.github.com/KROSF/c5435acf590acd632f71bb720f685895).
The schema is currently available at
https://taskfile.dev/schema.json and linked at https://json.schemastore.org/taskfile.json
so it is be used automatically many code editors, like VSCode.
Contributions can be done by editing [this file](https://github.com/go-task/task/blob/master/docs/static/schema.json).
### Visual Studio Code extension

View File

@@ -48,6 +48,16 @@ guide to check the full schema documentation and Task features.
if a given set of files haven't changed since last run (based either on its
timestamp or content).
## Gold Sponsors
<div class="gold-sponsors">
| [Appwrite][appwrite] |
| - |
| [![Appwrite](/img/appwrite.svg)][appwrite] |
</div>
[make]: https://www.gnu.org/software/make/
[go]: https://go.dev/
[yaml]: http://yaml.org/
@@ -55,3 +65,4 @@ guide to check the full schema documentation and Task features.
[snapcraft]: https://snapcraft.io/
[scoop]: https://scoop.sh/
[sh]: https://github.com/mvdan/sh
[appwrite]: https://appwrite.io/?utm_source=taskfile.dev&utm_medium=website&utm_campaign=task_oss_fund

View File

@@ -52,6 +52,35 @@ committed version (`.dist`) while still allowing individual users to override
the Taskfile by adding an additional `Taskfile.yml` (which would be on
`.gitignore`).
### Running a Taskfile from a subdirectory
If a Taskfile cannot be found in the current working directory, it will walk up
the file tree until it finds one (similar to how `git` works). When running Task
from a subdirectory like this, it will behave as if you ran it from the
directory containing the Taskfile.
You can use this functionality along with the special `{{.USER_WORKING_DIR}}`
variable to create some very useful reusable tasks. For example, if you have a
monorepo with directories for each microservice, you can `cd` into a
microservice directory and run a task command to bring it up without having to
create multiple tasks or Taskfiles with identical content. For example:
```yaml
version: '3'
tasks:
up:
dir: '{{.USER_WORKING_DIR}}'
preconditions:
- test -f docker-compose.yml
cmds:
- docker-compose up -d
```
In this example, we can run `cd <service>` and `task up` and as long as the
`<service>` directory contains a `docker-compose.yml`, the Docker composition will be
brought up.
## Environment variables
### Task
@@ -118,6 +147,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

View File

@@ -1,13 +1,15 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const {
GITHUB_URL,
TWITTER_URL,
DISCORD_URL,
CHINESE_URL
} = require('./constants');
const lightCodeTheme = require('./src/themes/prismLight');
const darkCodeTheme = require('./src/themes/prismDark');
const GITHUB_URL = 'https://github.com/go-task/task';
const TWITTER_URL = 'https://twitter.com/taskfiledev';
const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Task',
@@ -153,6 +155,15 @@ const config = {
href: 'https://opencollective.com/task'
}
]
},
{
title: 'Translations',
items: [
{
label: 'Chinese | 中国人',
href: CHINESE_URL
}
]
}
]
},

View File

@@ -1,9 +1,19 @@
// @ts-check
const { CHINESE_URL } = require('./constants');
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
tutorialSidebar: [
{ type: 'autogenerated', dirName: '.' },
{
type: 'autogenerated',
dirName: '.'
},
{
type: 'link',
label: 'Chinese | 中国人',
href: CHINESE_URL
},
{
type: 'html',
value: '<div id="sidebar-ads"></div>'

View File

@@ -27,3 +27,12 @@
margin-top: 30px;
margin-right: 10px;
}
.gold-sponsors {
display: flex;
justify-content: center;
}
.gold-sponsors table img {
width: 200px;
}

7
docs/static/img/appwrite.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -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",
@@ -260,7 +267,7 @@
}
}
},
"oneOf": [
"allOf": [
{
"type": "object",
"properties": {

View File

@@ -4858,9 +4858,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.3.tgz#d4b15b8504c63d1fc3f2ade52d41bc8459d6ede1"
integrity sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"

View File

@@ -18,7 +18,8 @@ import (
var _ compiler.Compiler = &CompilerV3{}
type CompilerV3 struct {
Dir string
Dir string
UserWorkingDir string
TaskfileEnv *taskfile.Vars
TaskfileVars *taskfile.Vars
@@ -179,9 +180,10 @@ func (c *CompilerV3) getSpecialVars(t *taskfile.Task) (map[string]string, error)
}
return map[string]string{
"TASK": t.Task,
"ROOT_DIR": c.Dir,
"TASKFILE_DIR": taskfileDir,
"TASK": t.Task,
"ROOT_DIR": c.Dir,
"TASKFILE_DIR": taskfileDir,
"USER_WORKING_DIR": c.UserWorkingDir,
}, nil
}

22
internal/sysinfo/uid.go Normal file
View File

@@ -0,0 +1,22 @@
//go:build !windows
package sysinfo
import (
"os"
"syscall"
)
func Owner(path string) (int, error) {
info, err := os.Stat(path)
if err != nil {
return 0, err
}
var uid int
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
uid = int(stat.Uid)
} else {
uid = os.Getuid()
}
return uid, nil
}

View File

@@ -0,0 +1,9 @@
//go:build windows
package sysinfo
// NOTE: This always returns -1 since there is currently no easy way to get
// file owner information on Windows.
func Owner(path string) (int, error) {
return -1, nil
}

18
package-lock.json generated
View File

@@ -1,22 +1,22 @@
{
"name": "@go-task/cli",
"version": "3.17.0",
"version": "3.19.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@go-task/cli",
"version": "3.17.0",
"version": "3.19.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@go-task/go-npm": "^0.1.15"
"@go-task/go-npm": "^0.1.17"
}
},
"node_modules/@go-task/go-npm": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.15.tgz",
"integrity": "sha512-xG+6SsSQsa6MzWML1CABWHTwHrCrBqXc/D1POoMDGIgjsRE/PB1noSBGLFhvU5DWHdPksqbAt/w9VOjbqlXpYw==",
"version": "0.1.17",
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.17.tgz",
"integrity": "sha512-j+xydQWrAxsqLYjweok1fWzDmBAA1g/gmFbPyG8kRI/d/+rzXGGLlro8zdS6mJ3Is+8BrIy2ZBmQkoONhowh7A==",
"bin": {
"go-npm": "bin/index.js"
}
@@ -24,9 +24,9 @@
},
"dependencies": {
"@go-task/go-npm": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.15.tgz",
"integrity": "sha512-xG+6SsSQsa6MzWML1CABWHTwHrCrBqXc/D1POoMDGIgjsRE/PB1noSBGLFhvU5DWHdPksqbAt/w9VOjbqlXpYw=="
"version": "0.1.17",
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.17.tgz",
"integrity": "sha512-j+xydQWrAxsqLYjweok1fWzDmBAA1g/gmFbPyG8kRI/d/+rzXGGLlro8zdS6mJ3Is+8BrIy2ZBmQkoONhowh7A=="
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@go-task/cli",
"version": "3.18.0",
"version": "3.19.0",
"description": "A task runner / simpler Make alternative written in Go",
"scripts": {
"postinstall": "go-npm install",
@@ -29,6 +29,6 @@
},
"homepage": "https://taskfile.dev",
"dependencies": {
"@go-task/go-npm": "^0.1.15"
"@go-task/go-npm": "^0.1.17"
}
}

View File

@@ -80,7 +80,7 @@ func (e *Executor) setCurrentDir() error {
func (e *Executor) readTaskfile() error {
var err error
e.Taskfile, err = read.Taskfile(&read.ReaderNode{
e.Taskfile, e.Dir, err = read.Taskfile(&read.ReaderNode{
Dir: e.Dir,
Entrypoint: e.Entrypoint,
Parent: nil,
@@ -179,11 +179,16 @@ func (e *Executor) setupCompiler(v float64) error {
Logger: e.Logger,
}
} else {
userWorkingDir, err := os.Getwd()
if err != nil {
return err
}
e.Compiler = &compilerv3.CompilerV3{
Dir: e.Dir,
TaskfileEnv: e.Taskfile.Env,
TaskfileVars: e.Taskfile.Vars,
Logger: e.Logger,
Dir: e.Dir,
UserWorkingDir: userWorkingDir,
TaskfileEnv: e.Taskfile.Env,
TaskfileVars: e.Taskfile.Vars,
Logger: e.Logger,
}
}

View File

@@ -1059,6 +1059,40 @@ func TestIncludesInternal(t *testing.T) {
}
}
func TestIncludesInterpolation(t *testing.T) {
const dir = "testdata/includes_interpolation"
tests := []struct {
name string
task string
expectedErr bool
expectedOutput string
}{
{"include", "include", false, "includes_interpolation\n"},
{"include with dir", "include-with-dir", false, "included\n"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Stdout: &buff,
Stderr: &buff,
Silent: true,
}
assert.NoError(t, e.Setup())
err := e.Run(context.Background(), taskfile.Call{Task: test.task})
if test.expectedErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, test.expectedOutput, buff.String())
})
}
}
func TestInternalTask(t *testing.T) {
const dir = "testdata/internal_task"
tests := []struct {
@@ -1376,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"
@@ -1621,3 +1703,52 @@ Hello, World!
err = os.RemoveAll(filepathext.SmartJoin(dir, "src"))
assert.NoError(t, err)
}
func TestTaskfileWalk(t *testing.T) {
tests := []struct {
name string
dir string
expected string
}{
{
name: "walk from root directory",
dir: "testdata/taskfile_walk",
expected: "foo\n",
}, {
name: "walk from sub directory",
dir: "testdata/taskfile_walk/foo",
expected: "foo\n",
}, {
name: "walk from sub sub directory",
dir: "testdata/taskfile_walk/foo/bar",
expected: "foo\n",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var buff bytes.Buffer
e := task.Executor{
Dir: test.dir,
Stdout: &buff,
Stderr: &buff,
}
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
assert.Equal(t, test.expected, buff.String())
})
}
}
func TestUserWorkingDirectory(t *testing.T) {
var buff bytes.Buffer
e := task.Executor{
Dir: "testdata/user_working_dir",
Stdout: &buff,
Stderr: &buff,
}
wd, err := os.Getwd()
assert.NoError(t, err)
assert.NoError(t, e.Setup())
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "default"}))
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
}

View File

@@ -10,6 +10,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/sysinfo"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile"
)
@@ -36,29 +37,30 @@ type ReaderNode struct {
// Taskfile reads a Taskfile for a given directory
// Uses current dir when dir is left empty. Uses Taskfile.yml
// or Taskfile.yaml when entrypoint is left empty
func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, string, error) {
if readerNode.Dir == "" {
d, err := os.Getwd()
if err != nil {
return nil, err
return nil, "", err
}
readerNode.Dir = d
}
path, err := exists(filepathext.SmartJoin(readerNode.Dir, readerNode.Entrypoint))
path, err := existsWalk(filepathext.SmartJoin(readerNode.Dir, readerNode.Entrypoint))
if err != nil {
return nil, err
return nil, "", err
}
readerNode.Dir = filepath.Dir(path)
readerNode.Entrypoint = filepath.Base(path)
t, err := readTaskfile(path)
if err != nil {
return nil, err
return nil, "", err
}
v, err := t.ParsedVersion()
if err != nil {
return nil, err
return nil, "", err
}
// Annotate any included Taskfile reference with a base directory for resolving relative paths
@@ -73,7 +75,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
if v >= 3.0 {
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true}
tr := templater.Templater{Vars: t.Vars, RemoveNoValue: true}
includedTask = taskfile.IncludedTaskfile{
Taskfile: tr.Replace(includedTask.Taskfile),
Dir: tr.Replace(includedTask.Dir),
@@ -113,7 +115,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
return err
}
includedTaskfile, err := Taskfile(includeReaderNode)
includedTaskfile, _, err := Taskfile(includeReaderNode)
if err != nil {
if includedTask.Optional {
return nil
@@ -163,7 +165,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
return nil
})
if err != nil {
return nil, err
return nil, "", err
}
if v < 3.0 {
@@ -171,10 +173,10 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
if _, err = os.Stat(path); err == nil {
osTaskfile, err := readTaskfile(path)
if err != nil {
return nil, err
return nil, "", err
}
if err = taskfile.Merge(t, osTaskfile, nil); err != nil {
return nil, err
return nil, "", err
}
}
}
@@ -187,7 +189,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
task.Task = name
}
return t, nil
return t, readerNode.Dir, nil
}
func readTaskfile(file string) (*taskfile.Taskfile, error) {
@@ -221,6 +223,36 @@ func exists(path string) (string, error) {
return "", fmt.Errorf(`task: No Taskfile found in "%s". Use "task --init" to create a new one`, path)
}
func existsWalk(path string) (string, error) {
origPath := path
owner, err := sysinfo.Owner(path)
if err != nil {
return "", err
}
for {
fpath, err := exists(path)
if err == nil {
return fpath, nil
}
// Get the parent path/user id
parentPath := filepath.Dir(path)
parentOwner, err := sysinfo.Owner(parentPath)
if err != nil {
return "", err
}
// Error if we reached the root directory and still haven't found a file
// OR if the user id of the directory changes
if path == parentPath || (parentOwner != owner) {
return "", fmt.Errorf(`task: No Taskfile found in "%s" (or any of the parent directories). Use "task --init" to create a new one`, origPath)
}
owner = parentOwner
path = parentPath
}
}
func checkCircularIncludes(node *ReaderNode) error {
if node == nil {
return errors.New("task: failed to check for include cycle: node was nil")

View File

@@ -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
View File

@@ -0,0 +1 @@
FOO=foo

View File

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

View 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

View File

@@ -0,0 +1,10 @@
version: "3"
vars:
MODULE_NAME: included
includes:
include: './{{.MODULE_NAME}}/Taskfile.yml'
include-with-dir:
taskfile: './{{.MODULE_NAME}}/Taskfile.yml'
dir: '{{.MODULE_NAME}}'

View File

@@ -0,0 +1,6 @@
version: "3"
tasks:
default:
cmds:
- basename $(pwd)

7
testdata/taskfile_walk/Taskfile.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
version: '3'
tasks:
default:
cmds:
- echo 'foo'
silent: true

View File

View File

@@ -0,0 +1,7 @@
version: '3'
tasks:
default:
cmds:
- echo '{{.USER_WORKING_DIR}}'
silent: true

View File

@@ -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 {