mirror of
https://github.com/go-task/task.git
synced 2026-06-19 22:01:38 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1072ff5950 | ||
|
|
983f6fff5d | ||
|
|
b3627fcb18 | ||
|
|
99d7338c29 | ||
|
|
9cf930454d | ||
|
|
4b4962e8c6 | ||
|
|
f2afa77114 | ||
|
|
3aa647c89b | ||
|
|
45ab4dc718 | ||
|
|
d1850e8fd2 | ||
|
|
f1d516cf2a | ||
|
|
d55282b53c | ||
|
|
ef9f7af0c5 | ||
|
|
8823887bb4 | ||
|
|
593980e45a | ||
|
|
081dc16312 |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -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
|
||||
|
||||
12
README.md
12
README.md
@@ -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] |
|
||||
|
||||
</div>
|
||||
|
||||
[appwrite]: https://appwrite.io/?utm_source=task_github&utm_medium=social&utm_campaign=task_oss_fund
|
||||
|
||||
11
docs/constants.js
Normal file
11
docs/constants.js
Normal 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
|
||||
};
|
||||
@@ -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`. |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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] |
|
||||
|
||||
</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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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>'
|
||||
|
||||
@@ -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
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 |
9
docs/static/schema.json
vendored
9
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",
|
||||
@@ -260,7 +267,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
22
internal/sysinfo/uid.go
Normal 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
|
||||
}
|
||||
9
internal/sysinfo/uid_win.go
Normal file
9
internal/sysinfo/uid_win.go
Normal 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
18
package-lock.json
generated
@@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
15
setup.go
15
setup.go
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
131
task_test.go
131
task_test.go
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
10
testdata/includes_interpolation/Taskfile.yml
vendored
Normal file
10
testdata/includes_interpolation/Taskfile.yml
vendored
Normal 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}}'
|
||||
6
testdata/includes_interpolation/included/Taskfile.yml
vendored
Normal file
6
testdata/includes_interpolation/included/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- basename $(pwd)
|
||||
7
testdata/taskfile_walk/Taskfile.yml
vendored
Normal file
7
testdata/taskfile_walk/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo 'foo'
|
||||
silent: true
|
||||
0
testdata/taskfile_walk/foo/bar/.gitkeep
vendored
Normal file
0
testdata/taskfile_walk/foo/bar/.gitkeep
vendored
Normal file
7
testdata/user_working_dir/Taskfile.yml
vendored
Normal file
7
testdata/user_working_dir/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo '{{.USER_WORKING_DIR}}'
|
||||
silent: true
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user