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 # 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 - Show aliases on `task --list --silent` (`task --ls`). This means that aliases
will be completed by the completion scripts will be completed by the completion scripts
@@ -12,7 +26,7 @@
aliased to `docs` aliased to `docs`
([#661](https://github.com/go-task/task/issues/661), [#815](https://github.com/go-task/task/pull/815)). ([#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 - Add a "Did you mean ...?" suggestion when a task does not exits another one
with a similar name is found 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> <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> </p>
</div> </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. | | `TASK` | The name of the current task. |
| `ROOT_DIR` | The absolute path of the root Taskfile. | | `ROOT_DIR` | The absolute path of the root Taskfile. |
| `TASKFILE_DIR` | The absolute path of the included 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`. | | `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`. | | `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. | | `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`. |

View File

@@ -5,7 +5,21 @@ sidebar_position: 6
# Changelog # 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 - Show aliases on `task --list --silent` (`task --ls`). This means that aliases
will be completed by the completion scripts will be completed by the completion scripts
@@ -17,7 +31,7 @@ sidebar_position: 6
aliased to `docs` aliased to `docs`
([#661](https://github.com/go-task/task/issues/661), [#815](https://github.com/go-task/task/pull/815)). ([#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 - Add a "Did you mean ...?" suggestion when a task does not exits another one
with a similar name is found 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 it installation methods or integrations with code editor. I (the author) am
thankful for everyone that helps me to improve the overall experience. 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 ## Editor Integrations
### JSON Schema ### JSON Schema
[@KROSF](https://github.com/KROSF) worked on a JSON Schema [into this Gist](https://gist.github.com/KROSF/c5435acf590acd632f71bb720f685895), Initial work on the schema was made by [@KROSF](https://github.com/KROSF)
which later was made officially available by [@Crandel](https://github.com/Crandel) on [this Gist](https://gist.github.com/KROSF/c5435acf590acd632f71bb720f685895).
at [https://json.schemastore.org/taskfile.json](https://json.schemastore.org/taskfile.json). The schema is currently available at
Further improvements are possible by opening pull requests changing https://taskfile.dev/schema.json and linked at https://json.schemastore.org/taskfile.json
[this file](https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/taskfile.json). so it is be used automatically many code editors, like VSCode.
Some code editors, like Visual Studio Code, make use of Schema Store Contributions can be done by editing [this file](https://github.com/go-task/task/blob/master/docs/static/schema.json).
automatically.
### Visual Studio Code extension ### 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 if a given set of files haven't changed since last run (based either on its
timestamp or content). timestamp or content).
## Gold Sponsors
<div class="gold-sponsors">
| [Appwrite][appwrite] |
| - |
| [![Appwrite](/img/appwrite.svg)][appwrite] |
</div>
[make]: https://www.gnu.org/software/make/ [make]: https://www.gnu.org/software/make/
[go]: https://go.dev/ [go]: https://go.dev/
[yaml]: http://yaml.org/ [yaml]: http://yaml.org/
@@ -55,3 +65,4 @@ guide to check the full schema documentation and Task features.
[snapcraft]: https://snapcraft.io/ [snapcraft]: https://snapcraft.io/
[scoop]: https://scoop.sh/ [scoop]: https://scoop.sh/
[sh]: https://github.com/mvdan/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 the Taskfile by adding an additional `Taskfile.yml` (which would be on
`.gitignore`). `.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 ## Environment variables
### Task ### Task
@@ -118,6 +147,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

View File

@@ -1,13 +1,15 @@
// @ts-check // @ts-check
// Note: type annotations allow type checking and IDEs autocompletion // 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 lightCodeTheme = require('./src/themes/prismLight');
const darkCodeTheme = require('./src/themes/prismDark'); 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} */ /** @type {import('@docusaurus/types').Config} */
const config = { const config = {
title: 'Task', title: 'Task',
@@ -153,6 +155,15 @@ const config = {
href: 'https://opencollective.com/task' href: 'https://opencollective.com/task'
} }
] ]
},
{
title: 'Translations',
items: [
{
label: 'Chinese | 中国人',
href: CHINESE_URL
}
]
} }
] ]
}, },

View File

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

View File

@@ -27,3 +27,12 @@
margin-top: 30px; margin-top: 30px;
margin-right: 10px; 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.", "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",
@@ -260,7 +267,7 @@
} }
} }
}, },
"oneOf": [ "allOf": [
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View File

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

View File

@@ -18,7 +18,8 @@ import (
var _ compiler.Compiler = &CompilerV3{} var _ compiler.Compiler = &CompilerV3{}
type CompilerV3 struct { type CompilerV3 struct {
Dir string Dir string
UserWorkingDir string
TaskfileEnv *taskfile.Vars TaskfileEnv *taskfile.Vars
TaskfileVars *taskfile.Vars TaskfileVars *taskfile.Vars
@@ -179,9 +180,10 @@ func (c *CompilerV3) getSpecialVars(t *taskfile.Task) (map[string]string, error)
} }
return map[string]string{ return map[string]string{
"TASK": t.Task, "TASK": t.Task,
"ROOT_DIR": c.Dir, "ROOT_DIR": c.Dir,
"TASKFILE_DIR": taskfileDir, "TASKFILE_DIR": taskfileDir,
"USER_WORKING_DIR": c.UserWorkingDir,
}, nil }, 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", "name": "@go-task/cli",
"version": "3.17.0", "version": "3.19.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@go-task/cli", "name": "@go-task/cli",
"version": "3.17.0", "version": "3.19.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@go-task/go-npm": "^0.1.15" "@go-task/go-npm": "^0.1.17"
} }
}, },
"node_modules/@go-task/go-npm": { "node_modules/@go-task/go-npm": {
"version": "0.1.15", "version": "0.1.17",
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.15.tgz", "resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.17.tgz",
"integrity": "sha512-xG+6SsSQsa6MzWML1CABWHTwHrCrBqXc/D1POoMDGIgjsRE/PB1noSBGLFhvU5DWHdPksqbAt/w9VOjbqlXpYw==", "integrity": "sha512-j+xydQWrAxsqLYjweok1fWzDmBAA1g/gmFbPyG8kRI/d/+rzXGGLlro8zdS6mJ3Is+8BrIy2ZBmQkoONhowh7A==",
"bin": { "bin": {
"go-npm": "bin/index.js" "go-npm": "bin/index.js"
} }
@@ -24,9 +24,9 @@
}, },
"dependencies": { "dependencies": {
"@go-task/go-npm": { "@go-task/go-npm": {
"version": "0.1.15", "version": "0.1.17",
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.15.tgz", "resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.1.17.tgz",
"integrity": "sha512-xG+6SsSQsa6MzWML1CABWHTwHrCrBqXc/D1POoMDGIgjsRE/PB1noSBGLFhvU5DWHdPksqbAt/w9VOjbqlXpYw==" "integrity": "sha512-j+xydQWrAxsqLYjweok1fWzDmBAA1g/gmFbPyG8kRI/d/+rzXGGLlro8zdS6mJ3Is+8BrIy2ZBmQkoONhowh7A=="
} }
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@go-task/cli", "name": "@go-task/cli",
"version": "3.18.0", "version": "3.19.0",
"description": "A task runner / simpler Make alternative written in Go", "description": "A task runner / simpler Make alternative written in Go",
"scripts": { "scripts": {
"postinstall": "go-npm install", "postinstall": "go-npm install",
@@ -29,6 +29,6 @@
}, },
"homepage": "https://taskfile.dev", "homepage": "https://taskfile.dev",
"dependencies": { "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 { func (e *Executor) readTaskfile() error {
var err error var err error
e.Taskfile, err = read.Taskfile(&read.ReaderNode{ e.Taskfile, e.Dir, err = read.Taskfile(&read.ReaderNode{
Dir: e.Dir, Dir: e.Dir,
Entrypoint: e.Entrypoint, Entrypoint: e.Entrypoint,
Parent: nil, Parent: nil,
@@ -179,11 +179,16 @@ func (e *Executor) setupCompiler(v float64) error {
Logger: e.Logger, Logger: e.Logger,
} }
} else { } else {
userWorkingDir, err := os.Getwd()
if err != nil {
return err
}
e.Compiler = &compilerv3.CompilerV3{ e.Compiler = &compilerv3.CompilerV3{
Dir: e.Dir, Dir: e.Dir,
TaskfileEnv: e.Taskfile.Env, UserWorkingDir: userWorkingDir,
TaskfileVars: e.Taskfile.Vars, TaskfileEnv: e.Taskfile.Env,
Logger: e.Logger, 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) { func TestInternalTask(t *testing.T) {
const dir = "testdata/internal_task" const dir = "testdata/internal_task"
tests := []struct { tests := []struct {
@@ -1376,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"
@@ -1621,3 +1703,52 @@ Hello, World!
err = os.RemoveAll(filepathext.SmartJoin(dir, "src")) err = os.RemoveAll(filepathext.SmartJoin(dir, "src"))
assert.NoError(t, err) 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" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/internal/filepathext" "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/internal/templater"
"github.com/go-task/task/v3/taskfile" "github.com/go-task/task/v3/taskfile"
) )
@@ -36,29 +37,30 @@ type ReaderNode struct {
// Taskfile reads a Taskfile for a given directory // Taskfile reads a Taskfile for a given directory
// Uses current dir when dir is left empty. Uses Taskfile.yml // Uses current dir when dir is left empty. Uses Taskfile.yml
// or Taskfile.yaml when entrypoint is left empty // 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 == "" { if readerNode.Dir == "" {
d, err := os.Getwd() d, err := os.Getwd()
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
readerNode.Dir = d readerNode.Dir = d
} }
path, err := exists(filepathext.SmartJoin(readerNode.Dir, readerNode.Entrypoint)) path, err := existsWalk(filepathext.SmartJoin(readerNode.Dir, readerNode.Entrypoint))
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
readerNode.Dir = filepath.Dir(path)
readerNode.Entrypoint = filepath.Base(path) readerNode.Entrypoint = filepath.Base(path)
t, err := readTaskfile(path) t, err := readTaskfile(path)
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
v, err := t.ParsedVersion() v, err := t.ParsedVersion()
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
// Annotate any included Taskfile reference with a base directory for resolving relative paths // 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 { err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
if v >= 3.0 { if v >= 3.0 {
tr := templater.Templater{Vars: &taskfile.Vars{}, RemoveNoValue: true} tr := templater.Templater{Vars: t.Vars, RemoveNoValue: true}
includedTask = taskfile.IncludedTaskfile{ includedTask = taskfile.IncludedTaskfile{
Taskfile: tr.Replace(includedTask.Taskfile), Taskfile: tr.Replace(includedTask.Taskfile),
Dir: tr.Replace(includedTask.Dir), Dir: tr.Replace(includedTask.Dir),
@@ -113,7 +115,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
return err return err
} }
includedTaskfile, err := Taskfile(includeReaderNode) includedTaskfile, _, err := Taskfile(includeReaderNode)
if err != nil { if err != nil {
if includedTask.Optional { if includedTask.Optional {
return nil return nil
@@ -163,7 +165,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
return nil return nil
}) })
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
if v < 3.0 { if v < 3.0 {
@@ -171,10 +173,10 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
if _, err = os.Stat(path); err == nil { if _, err = os.Stat(path); err == nil {
osTaskfile, err := readTaskfile(path) osTaskfile, err := readTaskfile(path)
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
if err = taskfile.Merge(t, osTaskfile, nil); err != nil { 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 task.Task = name
} }
return t, nil return t, readerNode.Dir, nil
} }
func readTaskfile(file string) (*taskfile.Taskfile, error) { 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) 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 { func checkCircularIncludes(node *ReaderNode) error {
if node == nil { if node == nil {
return errors.New("task: failed to check for include cycle: node was 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 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
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 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 {