mirror of
https://github.com/go-task/task.git
synced 2026-06-12 10:21:50 +00:00
Compare commits
53 Commits
split-exec
...
v3.45.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb4c254211 | ||
|
|
57bf348829 | ||
|
|
092b9b6391 | ||
|
|
cd8c831204 | ||
|
|
0d03f4f266 | ||
|
|
b8bf298c84 | ||
|
|
9a91c4cb21 | ||
|
|
2921450bf7 | ||
|
|
dffa355cad | ||
|
|
48039be12c | ||
|
|
43cb64e6cc | ||
|
|
25a7b5936f | ||
|
|
4ae3071845 | ||
|
|
242523c797 | ||
|
|
0fdb5e8665 | ||
|
|
534dfa089c | ||
|
|
51a3bcaacd | ||
|
|
6289fcf34c | ||
|
|
2959737d7d | ||
|
|
a3047d3cd8 | ||
|
|
725600f220 | ||
|
|
fd83414074 | ||
|
|
6c645a33f7 | ||
|
|
9d969e5971 | ||
|
|
8b382a3bae | ||
|
|
a34892ad94 | ||
|
|
e55bb29554 | ||
|
|
1168ef32df | ||
|
|
245d7f747f | ||
|
|
b216ae885c | ||
|
|
61cb15ad01 | ||
|
|
04579c0c44 | ||
|
|
39462cbfde | ||
|
|
72dfec68b0 | ||
|
|
f89c12ddf0 | ||
|
|
c903d07332 | ||
|
|
138b9a5a4f | ||
|
|
1e2121a99f | ||
|
|
9495fb2b1c | ||
|
|
1fda55910e | ||
|
|
e6c808c02b | ||
|
|
0fc26a43a9 | ||
|
|
c0b4c19443 | ||
|
|
1a8df44e9e | ||
|
|
82ad1de8d0 | ||
|
|
d59c795502 | ||
|
|
504cb94e8b | ||
|
|
e7606635fe | ||
|
|
9a05ceaa80 | ||
|
|
083654d8c9 | ||
|
|
79c93fb42b | ||
|
|
64fc538a16 | ||
|
|
4da081e5c3 |
@@ -8,6 +8,6 @@ charset = utf-8
|
|||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
|
|
||||||
[*.{md,mdx,yml,yaml,json,toml,htm,html,js,ts,css,svg,sh,bash,fish}]
|
[*.{md,mdx,yml,yaml,json,toml,htm,html,js,ts,vue,css,svg,sh,bash,fish}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|||||||
41
.github/workflows/lint.yml
vendored
41
.github/workflows/lint.yml
vendored
@@ -13,14 +13,14 @@ jobs:
|
|||||||
name: Lint
|
name: Lint
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.23.x, 1.24.x]
|
go-version: [1.24.x, 1.25.x]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{matrix.go-version}}
|
go-version: ${{matrix.go-version}}
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v8
|
uses: golangci/golangci-lint-action@v8
|
||||||
@@ -32,43 +32,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.12
|
python-version: 3.13
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: install check-jsonschema
|
- name: install check-jsonschema
|
||||||
run: python -m pip install 'check-jsonschema==0.27.3'
|
run: python -m pip install 'check-jsonschema==0.27.3'
|
||||||
|
|
||||||
- name: check-jsonschema (metaschema)
|
- name: check-jsonschema (metaschema)
|
||||||
run: check-jsonschema --check-metaschema website/static/schema.json
|
run: check-jsonschema --check-metaschema website/src/public/schema.json
|
||||||
check_doc:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Get changed files in the docs folder
|
|
||||||
id: changed-files-specific
|
|
||||||
uses: tj-actions/changed-files@v46
|
|
||||||
with:
|
|
||||||
files: website/versioned_docs/**
|
|
||||||
|
|
||||||
- uses: actions/github-script@v7
|
|
||||||
if: steps.changed-files-specific.outputs.any_changed == 'true'
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
core.setFailed('website/versioned_docs has changed. Instead you need to update the docs in the website/docs folder.')
|
|
||||||
check_schema:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Get changed files in the docs folder
|
|
||||||
id: changed-files-specific
|
|
||||||
uses: tj-actions/changed-files@v46
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
website/static/schema.json
|
|
||||||
website/static/schema-taskrc.json
|
|
||||||
- uses: actions/github-script@v7
|
|
||||||
if: steps.changed-files-specific.outputs.any_changed == 'true'
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
core.setFailed('schema.json or schema-taskrc.json has changed. Instead you need to update next-schema.json or next-schema-taskrc.json.')
|
|
||||||
|
|||||||
7
.github/workflows/release-nightly.yml
vendored
7
.github/workflows/release-nightly.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Realease nightly
|
name: Release nightly
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -9,14 +9,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.24.x
|
go-version: 1.25.x
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
@@ -27,3 +27,4 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||||
|
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
|
||||||
|
|||||||
25
.github/workflows/release.yml
vendored
25
.github/workflows/release.yml
vendored
@@ -10,14 +10,29 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.24.x
|
go-version: 1.25.x
|
||||||
|
|
||||||
|
- name: npm-login
|
||||||
|
run: |
|
||||||
|
npm config set '//registry.npmjs.org/:_authToken'=${{ secrets.NPM_TOKEN }}
|
||||||
|
- name: Install Task
|
||||||
|
uses: arduino/setup-task@v2
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22.x'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
@@ -28,3 +43,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||||
|
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
|
||||||
|
|
||||||
|
- name: Deploy Website
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
task website:deploy:prod
|
||||||
|
|||||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
name: Test
|
name: Test
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.23.x, 1.24.x]
|
go-version: [1.24.x, 1.25.x]
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
runs-on: ${{matrix.platform}}
|
runs-on: ${{matrix.platform}}
|
||||||
steps:
|
steps:
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Download Go modules
|
- name: Download Go modules
|
||||||
run: go mod download
|
run: go mod download
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ nfpms:
|
|||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
|
- apk
|
||||||
file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}'
|
file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}'
|
||||||
contents:
|
contents:
|
||||||
- src: completion/bash/task.bash
|
- src: completion/bash/task.bash
|
||||||
@@ -135,3 +136,37 @@ winget:
|
|||||||
owner: microsoft
|
owner: microsoft
|
||||||
name: winget-pkgs
|
name: winget-pkgs
|
||||||
branch: master
|
branch: master
|
||||||
|
|
||||||
|
|
||||||
|
npms:
|
||||||
|
- name: "@go-task/cli"
|
||||||
|
repository: "git+https://github.com/go-task/task.git"
|
||||||
|
bugs: https://github.com/go-task/task/issues
|
||||||
|
description: A task runner / simpler Make alternative written in Go
|
||||||
|
homepage: https://taskfile.dev
|
||||||
|
license: MIT
|
||||||
|
author: "The Task authors"
|
||||||
|
access: public
|
||||||
|
keywords:
|
||||||
|
- "task"
|
||||||
|
- "taskfile"
|
||||||
|
- "build-tool"
|
||||||
|
- "task-runner"
|
||||||
|
|
||||||
|
|
||||||
|
cloudsmiths:
|
||||||
|
- organization: "task"
|
||||||
|
repository: "{{if not .IsNightly}}task{{end}}"
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
- apk
|
||||||
|
distributions:
|
||||||
|
deb:
|
||||||
|
- "any-distro/any-version"
|
||||||
|
rpm:
|
||||||
|
- "any-distro/any-version"
|
||||||
|
alpine:
|
||||||
|
- "alpine/any-version"
|
||||||
|
component: main
|
||||||
|
republish: true
|
||||||
|
|||||||
49
CHANGELOG.md
49
CHANGELOG.md
@@ -1,5 +1,54 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v3.45.0 - 2025-09-15
|
||||||
|
|
||||||
|
- Task now includes built-in core utilities to greatly improve compatibility on
|
||||||
|
Windows. This means that your commands that uses `cp`, `mv`, `mkdir` or any
|
||||||
|
other common core utility will now work by default on Windows, without extra
|
||||||
|
setup. This is something we wanted to address for many many years, and it's
|
||||||
|
finally being shipped!
|
||||||
|
[Read our blog post this the topic](https://taskfile.dev/blog/windows-core-utils).
|
||||||
|
(#197, #2360 by @andreynering).
|
||||||
|
- :sparkles: Built and deployed a [brand new website](https://taskfile.dev)
|
||||||
|
using [VitePress](https://vitepress.dev) (#2359, #2369, #2371, #2375, #2378 by
|
||||||
|
@vmaerten, @andreynering, @pd93).
|
||||||
|
- Began releasing
|
||||||
|
[nightly builds](https://github.com/go-task/task/releases/tag/nightly). This
|
||||||
|
will allow people to test our changes before they are fully released and
|
||||||
|
without having to install Go to build them (#2358 by @vmaerten).
|
||||||
|
- Added support for global config files in `$XDG_CONFIG_HOME/task/taskrc.yml` or
|
||||||
|
`$HOME/.taskrc.yml`. Check out our new
|
||||||
|
[configuration guide](https://taskfile.dev/docs/reference/config) for more
|
||||||
|
details (#2247, #2380, #2390, #2391 by @vmaerten, @pd93).
|
||||||
|
- Added experiments to the taskrc schema to clarify the expected keys and values
|
||||||
|
(#2235 by @vmaerten).
|
||||||
|
- Added support for new properties in `.taskrc.yml`: insecure, verbose,
|
||||||
|
concurrency, remote offline, remote timeout, and remote expiry. :warning:
|
||||||
|
Note: setting offline via environment variable is no longer supported. (#2389
|
||||||
|
by @vmaerten)
|
||||||
|
- Added a `--nested` flag when outputting tasks using `--list --json`. This will
|
||||||
|
output tasks in a nested structure when tasks are namespaced (#2415 by @pd93).
|
||||||
|
- Enhanced support for tasks with wildcards: they are now logged correctly, and
|
||||||
|
wildcard parameters are fully considered during fingerprinting (#1808, #1795
|
||||||
|
by @vmaerten).
|
||||||
|
- Fixed panic when a variable was declared as an empty hash (`{}`) (#2416,
|
||||||
|
#2417 by @trulede).
|
||||||
|
|
||||||
|
#### Package API
|
||||||
|
|
||||||
|
- Bumped the minimum version of Go to 1.24 (#2358 by @vmaerten).
|
||||||
|
|
||||||
|
#### Other news
|
||||||
|
|
||||||
|
We recently released our
|
||||||
|
[official GitHub Action](https://github.com/go-task/setup-task). This is based
|
||||||
|
on the fantastic work by the Arduino team who created and maintained the
|
||||||
|
community version. Now that this is officially adopted, fixes/updates should be
|
||||||
|
more timely. We have already merged a couple of longstanding PRs in our
|
||||||
|
[first release](https://github.com/go-task/setup-task/releases/tag/v1.0.0) (by
|
||||||
|
@pd93, @shrink, @trim21 and all the previous contributors to
|
||||||
|
[arduino/setup-task](https://github.com/arduino/setup-task/)).
|
||||||
|
|
||||||
## v3.44.1 - 2025-07-23
|
## v3.44.1 - 2025-07-23
|
||||||
|
|
||||||
- Internal tasks will no longer be shown as suggestions since they cannot be
|
- Internal tasks will no longer be shown as suggestions since they cannot be
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://taskfile.dev">
|
<a href="https://taskfile.dev">
|
||||||
<img src="website/static/img/logo.svg" width="200px" height="200px" />
|
<img src="website/src/public/img/logo.svg" width="200px" height="200px" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<h1>Task</h1>
|
<h1>Task</h1>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="middle">
|
<td align="center" valign="middle">
|
||||||
<a target="_blank" href="https://devowl.io">
|
<a target="_blank" href="https://devowl.io">
|
||||||
<img src="/website/static/img/devowl.io.svg" height="100px" title="devowl.io" />
|
<img src="https://devowl.io/wp-content/uploads/meta/favicon.webp" height="100px" title="devowl.io" />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
25
Taskfile.yml
25
Taskfile.yml
@@ -8,6 +8,7 @@ includes:
|
|||||||
|
|
||||||
vars:
|
vars:
|
||||||
BIN: "{{.ROOT_DIR}}/bin"
|
BIN: "{{.ROOT_DIR}}/bin"
|
||||||
|
GOTESTSUM_FORMAT: '{{if .CI}}github-actions{{else}}pkgname{{end}}'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: '0'
|
CGO_ENABLED: '0'
|
||||||
@@ -131,29 +132,37 @@ tasks:
|
|||||||
test:
|
test:
|
||||||
desc: Runs test suite
|
desc: Runs test suite
|
||||||
aliases: [t]
|
aliases: [t]
|
||||||
|
deps: [gotestsum:install]
|
||||||
sources:
|
sources:
|
||||||
- "**/*.go"
|
- "**/*.go"
|
||||||
- "testdata/**/*"
|
- "testdata/**/*"
|
||||||
cmds:
|
cmds:
|
||||||
- go test ./...
|
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./...
|
||||||
|
|
||||||
test:watch:
|
test:watch:
|
||||||
desc: Runs test suite with watch tests included
|
desc: Runs test suite with watch tests included
|
||||||
deps: [sleepit:build]
|
deps: [sleepit:build, gotestsum:install]
|
||||||
cmds:
|
cmds:
|
||||||
- go test ./... -tags 'watch'
|
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./... -tags 'watch'
|
||||||
|
|
||||||
test:all:
|
test:all:
|
||||||
desc: Runs test suite with signals and watch tests included
|
desc: Runs test suite with signals and watch tests included
|
||||||
deps: [sleepit:build]
|
deps: [sleepit:build, gotestsum:install]
|
||||||
cmds:
|
cmds:
|
||||||
- go test -tags 'signals watch' ./...
|
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' -tags 'signals watch' ./...
|
||||||
|
|
||||||
goreleaser:test:
|
goreleaser:test:
|
||||||
desc: Tests release process without publishing
|
desc: Tests release process without publishing
|
||||||
cmds:
|
cmds:
|
||||||
- goreleaser --snapshot --clean
|
- goreleaser --snapshot --clean
|
||||||
|
|
||||||
|
gotestsum:install:
|
||||||
|
desc: Installs gotestsum
|
||||||
|
status:
|
||||||
|
- command -v gotestsum
|
||||||
|
cmds:
|
||||||
|
- go install gotest.tools/gotestsum@latest
|
||||||
|
|
||||||
goreleaser:install:
|
goreleaser:install:
|
||||||
desc: Installs goreleaser
|
desc: Installs goreleaser
|
||||||
cmds:
|
cmds:
|
||||||
@@ -203,7 +212,6 @@ tasks:
|
|||||||
Please wait for the CI to finish and then do the following:
|
Please wait for the CI to finish and then do the following:
|
||||||
|
|
||||||
- Copy the changelog for v{{.VERSION}} to the GitHub release
|
- Copy the changelog for v{{.VERSION}} to the GitHub release
|
||||||
- Publish the package to NPM with `task npm:publish`
|
|
||||||
- Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml
|
- Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml
|
||||||
preconditions:
|
preconditions:
|
||||||
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
|
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
|
||||||
@@ -222,8 +230,3 @@ tasks:
|
|||||||
- "git push origin tag v{{.VERSION}}"
|
- "git push origin tag v{{.VERSION}}"
|
||||||
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
|
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
|
||||||
silent: true
|
silent: true
|
||||||
|
|
||||||
npm:publish:
|
|
||||||
desc: Publish release to npm
|
|
||||||
cmds:
|
|
||||||
- npm publish --access=public
|
|
||||||
|
|||||||
@@ -3,33 +3,23 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/otiai10/copy"
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
changelogSource = "CHANGELOG.md"
|
changelogSource = "CHANGELOG.md"
|
||||||
changelogTarget = "website/docs/changelog.mdx"
|
changelogTarget = "website/src/docs/changelog.md"
|
||||||
docsSource = "website/docs"
|
versionFile = "internal/version/version.txt"
|
||||||
docsTarget = "website/versioned_docs/version-latest"
|
|
||||||
schemaSource = "website/static/next-schema.json"
|
|
||||||
schemaTarget = "website/static/schema.json"
|
|
||||||
schemaTaskrcSource = "website/static/next-schema-taskrc.json"
|
|
||||||
schemaTaskrcTarget = "website/static/schema-taskrc.json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var changelogReleaseRegex = regexp.MustCompile(`## Unreleased`)
|
||||||
changelogReleaseRegex = regexp.MustCompile(`## Unreleased`)
|
|
||||||
versionRegex = regexp.MustCompile(`(?m)^ "version": "\d+\.\d+\.\d+",$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Flags
|
// Flags
|
||||||
var (
|
var (
|
||||||
@@ -53,7 +43,7 @@ func release() error {
|
|||||||
return errors.New("error: expected version number")
|
return errors.New("error: expected version number")
|
||||||
}
|
}
|
||||||
|
|
||||||
version, err := getVersion()
|
version, err := getVersion(versionFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -71,36 +61,18 @@ func release() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setVersionFile("internal/version/version.txt", version); err != nil {
|
if err := setVersionFile(versionFile, version); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := setJSONVersion("package.json", version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := setJSONVersion("package-lock.json", version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := docs(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := schema(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getVersion() (*semver.Version, error) {
|
func getVersion(filename string) (*semver.Version, error) {
|
||||||
cmd := exec.Command("git", "describe", "--tags", "--abbrev=0")
|
b, err := os.ReadFile(filename)
|
||||||
b, err := cmd.Output()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return semver.NewVersion(strings.TrimSpace(string(b)))
|
return semver.NewVersion(strings.TrimSpace(string(b)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,37 +131,3 @@ func changelog(version *semver.Version) error {
|
|||||||
func setVersionFile(fileName string, version *semver.Version) error {
|
func setVersionFile(fileName string, version *semver.Version) error {
|
||||||
return os.WriteFile(fileName, []byte(version.String()+"\n"), 0o644)
|
return os.WriteFile(fileName, []byte(version.String()+"\n"), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setJSONVersion(fileName string, version *semver.Version) error {
|
|
||||||
// Read the JSON file
|
|
||||||
b, err := os.ReadFile(fileName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the version
|
|
||||||
new := versionRegex.ReplaceAllString(string(b), fmt.Sprintf(` "version": "%s",`, version.String()))
|
|
||||||
|
|
||||||
// Write the JSON file
|
|
||||||
return os.WriteFile(fileName, []byte(new), 0o644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func docs() error {
|
|
||||||
if err := os.RemoveAll(docsTarget); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := copy.Copy(docsSource, docsTarget); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func schema() error {
|
|
||||||
if err := copy.Copy(schemaSource, schemaTarget); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := copy.Copy(schemaTaskrcSource, schemaTaskrcTarget); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/go-task/task/v3/internal/flags"
|
"github.com/go-task/task/v3/internal/flags"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
"github.com/go-task/task/v3/internal/version"
|
"github.com/go-task/task/v3/internal/version"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,63 +110,16 @@ func run() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := experiments.Validate(); err != nil {
|
e := task.NewExecutor(
|
||||||
log.Warnf("%s\n", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new root node for the given entrypoint
|
|
||||||
node, err := taskfile.NewRootNode(
|
|
||||||
flags.Entrypoint,
|
|
||||||
flags.Dir,
|
|
||||||
flags.Insecure,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDir, err := task.NewTempDir(node.Dir())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := taskfile.NewReader(
|
|
||||||
flags.WithFlags(),
|
flags.WithFlags(),
|
||||||
taskfile.WithTempDir(tempDir.Remote),
|
task.WithVersionCheck(true),
|
||||||
taskfile.WithDebugFunc(func(s string) {
|
|
||||||
log.VerboseOutf(logger.Magenta, s)
|
|
||||||
}),
|
|
||||||
taskfile.WithPromptFunc(func(s string) error {
|
|
||||||
return log.Prompt(logger.Yellow, s, "n", "y", "yes")
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
if err := e.Setup(); err != nil {
|
||||||
ctx, cf := context.WithTimeout(context.Background(), flags.Timeout)
|
|
||||||
defer cf()
|
|
||||||
graph, err := reader.Read(ctx, node)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, context.DeadlineExceeded) {
|
|
||||||
return &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: flags.Timeout}
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
executor, err := task.NewExecutor(graph,
|
|
||||||
flags.WithFlags(),
|
|
||||||
task.WithDir(node.Dir()),
|
|
||||||
task.WithTempDir(tempDir),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the download flag is specified, we should stop execution as soon as
|
|
||||||
// taskfile is downloaded
|
|
||||||
if flags.Download {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if flags.ClearCache {
|
if flags.ClearCache {
|
||||||
cachePath := filepath.Join(executor.TempDir.Remote, "remote")
|
cachePath := filepath.Join(e.TempDir.Remote, "remote")
|
||||||
return os.RemoveAll(cachePath)
|
return os.RemoveAll(cachePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,12 +128,13 @@ func run() error {
|
|||||||
flags.ListAll,
|
flags.ListAll,
|
||||||
flags.ListJson,
|
flags.ListJson,
|
||||||
flags.NoStatus,
|
flags.NoStatus,
|
||||||
|
flags.Nested,
|
||||||
)
|
)
|
||||||
if listOptions.ShouldListTasks() {
|
if listOptions.ShouldListTasks() {
|
||||||
if flags.Silent {
|
if flags.Silent {
|
||||||
return executor.ListTaskNames(flags.ListAll)
|
return e.ListTaskNames(flags.ListAll)
|
||||||
}
|
}
|
||||||
foundTasks, err := executor.ListTasks(listOptions)
|
foundTasks, err := e.ListTasks(listOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -213,17 +166,17 @@ func run() error {
|
|||||||
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||||
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||||
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
||||||
executor.Taskfile.Vars.Merge(globals, nil)
|
e.Taskfile.Vars.Merge(globals, nil)
|
||||||
|
|
||||||
if !flags.Watch {
|
if !flags.Watch {
|
||||||
executor.InterceptInterruptSignals()
|
e.InterceptInterruptSignals()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
if flags.Status {
|
if flags.Status {
|
||||||
return executor.Status(ctx, calls...)
|
return e.Status(ctx, calls...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return executor.Run(ctx, calls...)
|
return e.Run(ctx, calls...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func (v MissingVar) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (err *TaskMissingRequiredVarsError) Error() string {
|
func (err *TaskMissingRequiredVarsError) Error() string {
|
||||||
var vars []string
|
vars := make([]string, 0, len(err.MissingVars))
|
||||||
for _, v := range err.MissingVars {
|
for _, v := range err.MissingVars {
|
||||||
vars = append(vars, v.String())
|
vars = append(vars, v.String())
|
||||||
}
|
}
|
||||||
|
|||||||
150
executor.go
150
executor.go
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -27,21 +26,27 @@ type (
|
|||||||
// within them.
|
// within them.
|
||||||
Executor struct {
|
Executor struct {
|
||||||
// Flags
|
// Flags
|
||||||
Dir string
|
Dir string
|
||||||
TempDir *TempDir
|
Entrypoint string
|
||||||
Force bool
|
TempDir TempDir
|
||||||
ForceAll bool
|
Force bool
|
||||||
Watch bool
|
ForceAll bool
|
||||||
Verbose bool
|
Insecure bool
|
||||||
Silent bool
|
Download bool
|
||||||
AssumeYes bool
|
Offline bool
|
||||||
AssumeTerm bool // Used for testing
|
Timeout time.Duration
|
||||||
Dry bool
|
CacheExpiryDuration time.Duration
|
||||||
Summary bool
|
Watch bool
|
||||||
Parallel bool
|
Verbose bool
|
||||||
Color bool
|
Silent bool
|
||||||
Concurrency int
|
AssumeYes bool
|
||||||
Interval time.Duration
|
AssumeTerm bool // Used for testing
|
||||||
|
Dry bool
|
||||||
|
Summary bool
|
||||||
|
Parallel bool
|
||||||
|
Color bool
|
||||||
|
Concurrency int
|
||||||
|
Interval time.Duration
|
||||||
|
|
||||||
// I/O
|
// I/O
|
||||||
Stdin io.Reader
|
Stdin io.Reader
|
||||||
@@ -67,17 +72,17 @@ type (
|
|||||||
executionHashesMutex sync.Mutex
|
executionHashesMutex sync.Mutex
|
||||||
watchedDirs *xsync.MapOf[string, bool]
|
watchedDirs *xsync.MapOf[string, bool]
|
||||||
}
|
}
|
||||||
|
TempDir struct {
|
||||||
|
Remote string
|
||||||
|
Fingerprint string
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewExecutor creates a new [Executor] and applies the given functional options
|
// NewExecutor creates a new [Executor] and applies the given functional options
|
||||||
// to it.
|
// to it.
|
||||||
func NewExecutor(graph *ast.TaskfileGraph, opts ...ExecutorOption) (*Executor, error) {
|
func NewExecutor(opts ...ExecutorOption) *Executor {
|
||||||
tf, err := graph.Merge()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
e := &Executor{
|
e := &Executor{
|
||||||
Taskfile: tf,
|
Timeout: time.Second * 10,
|
||||||
Stdin: os.Stdin,
|
Stdin: os.Stdin,
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
@@ -95,10 +100,7 @@ func NewExecutor(graph *ast.TaskfileGraph, opts ...ExecutorOption) (*Executor, e
|
|||||||
executionHashesMutex: sync.Mutex{},
|
executionHashesMutex: sync.Mutex{},
|
||||||
}
|
}
|
||||||
e.Options(opts...)
|
e.Options(opts...)
|
||||||
if err := e.setup(); err != nil {
|
return e
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return e, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options loops through the given [ExecutorOption] functions and applies them
|
// Options loops through the given [ExecutorOption] functions and applies them
|
||||||
@@ -120,23 +122,33 @@ type dirOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *dirOption) ApplyToExecutor(e *Executor) {
|
func (o *dirOption) ApplyToExecutor(e *Executor) {
|
||||||
absDir, err := filepath.Abs(o.dir)
|
e.Dir = o.dir
|
||||||
if err != nil {
|
}
|
||||||
e.Dir = o.dir
|
|
||||||
return
|
// WithEntrypoint sets the entrypoint (main Taskfile) of the [Executor]. By
|
||||||
}
|
// default, Task will search for one of the default Taskfiles in the given
|
||||||
e.Dir = absDir
|
// directory.
|
||||||
|
func WithEntrypoint(entrypoint string) ExecutorOption {
|
||||||
|
return &entrypointOption{entrypoint}
|
||||||
|
}
|
||||||
|
|
||||||
|
type entrypointOption struct {
|
||||||
|
entrypoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *entrypointOption) ApplyToExecutor(e *Executor) {
|
||||||
|
e.Entrypoint = o.entrypoint
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTempDir sets the temporary directory that will be used by [Executor] for
|
// WithTempDir sets the temporary directory that will be used by [Executor] for
|
||||||
// storing temporary files like checksums and cached remote files. By default,
|
// storing temporary files like checksums and cached remote files. By default,
|
||||||
// the temporary directory is set to the user's temporary directory.
|
// the temporary directory is set to the user's temporary directory.
|
||||||
func WithTempDir(tempDir *TempDir) ExecutorOption {
|
func WithTempDir(tempDir TempDir) ExecutorOption {
|
||||||
return &tempDirOption{tempDir}
|
return &tempDirOption{tempDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
type tempDirOption struct {
|
type tempDirOption struct {
|
||||||
tempDir *TempDir
|
tempDir TempDir
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *tempDirOption) ApplyToExecutor(e *Executor) {
|
func (o *tempDirOption) ApplyToExecutor(e *Executor) {
|
||||||
@@ -171,6 +183,76 @@ func (o *forceAllOption) ApplyToExecutor(e *Executor) {
|
|||||||
e.ForceAll = o.forceAll
|
e.ForceAll = o.forceAll
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithInsecure allows the [Executor] to make insecure connections when reading
|
||||||
|
// remote taskfiles. By default, insecure connections are rejected.
|
||||||
|
func WithInsecure(insecure bool) ExecutorOption {
|
||||||
|
return &insecureOption{insecure}
|
||||||
|
}
|
||||||
|
|
||||||
|
type insecureOption struct {
|
||||||
|
insecure bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *insecureOption) ApplyToExecutor(e *Executor) {
|
||||||
|
e.Insecure = o.insecure
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDownload forces the [Executor] to download a fresh copy of the taskfile
|
||||||
|
// from the remote source.
|
||||||
|
func WithDownload(download bool) ExecutorOption {
|
||||||
|
return &downloadOption{download}
|
||||||
|
}
|
||||||
|
|
||||||
|
type downloadOption struct {
|
||||||
|
download bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *downloadOption) ApplyToExecutor(e *Executor) {
|
||||||
|
e.Download = o.download
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOffline stops the [Executor] from being able to make network connections.
|
||||||
|
// It will still be able to read local files and cached copies of remote files.
|
||||||
|
func WithOffline(offline bool) ExecutorOption {
|
||||||
|
return &offlineOption{offline}
|
||||||
|
}
|
||||||
|
|
||||||
|
type offlineOption struct {
|
||||||
|
offline bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *offlineOption) ApplyToExecutor(e *Executor) {
|
||||||
|
e.Offline = o.offline
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout sets the [Executor]'s timeout for fetching remote taskfiles. By
|
||||||
|
// default, the timeout is set to 10 seconds.
|
||||||
|
func WithTimeout(timeout time.Duration) ExecutorOption {
|
||||||
|
return &timeoutOption{timeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
type timeoutOption struct {
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *timeoutOption) ApplyToExecutor(e *Executor) {
|
||||||
|
e.Timeout = o.timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCacheExpiryDuration sets the duration after which the cache is considered
|
||||||
|
// expired. By default, the cache is considered expired after 24 hours.
|
||||||
|
func WithCacheExpiryDuration(duration time.Duration) ExecutorOption {
|
||||||
|
return &cacheExpiryDurationOption{duration: duration}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cacheExpiryDurationOption struct {
|
||||||
|
duration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *cacheExpiryDurationOption) ApplyToExecutor(r *Executor) {
|
||||||
|
r.CacheExpiryDuration = o.duration
|
||||||
|
}
|
||||||
|
|
||||||
// WithWatch tells the [Executor] to keep running in the background and watch
|
// WithWatch tells the [Executor] to keep running in the background and watch
|
||||||
// for changes to the fingerprint of the tasks that are run. When changes are
|
// for changes to the fingerprint of the tasks that are run. When changes are
|
||||||
// detected, a new task run is triggered.
|
// detected, a new task run is triggered.
|
||||||
|
|||||||
314
executor_test.go
314
executor_test.go
@@ -3,11 +3,9 @@ package task_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sebdah/goldie/v2"
|
"github.com/sebdah/goldie/v2"
|
||||||
@@ -16,7 +14,6 @@ import (
|
|||||||
"github.com/go-task/task/v3"
|
"github.com/go-task/task/v3"
|
||||||
"github.com/go-task/task/v3/experiments"
|
"github.com/go-task/task/v3/experiments"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,12 +33,7 @@ type (
|
|||||||
task string
|
task string
|
||||||
vars map[string]any
|
vars map[string]any
|
||||||
input string
|
input string
|
||||||
nodeDir string
|
|
||||||
nodeEntrypoint string
|
|
||||||
nodeInsecure bool
|
|
||||||
readerOpts []taskfile.ReaderOption
|
|
||||||
executorOpts []task.ExecutorOption
|
executorOpts []task.ExecutorOption
|
||||||
wantReaderError bool
|
|
||||||
wantSetupError bool
|
wantSetupError bool
|
||||||
wantRunError bool
|
wantRunError bool
|
||||||
wantStatusError bool
|
wantStatusError bool
|
||||||
@@ -54,9 +46,8 @@ type (
|
|||||||
func NewExecutorTest(t *testing.T, opts ...ExecutorTestOption) {
|
func NewExecutorTest(t *testing.T, opts ...ExecutorTestOption) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
tt := &ExecutorTest{
|
tt := &ExecutorTest{
|
||||||
task: "default",
|
task: "default",
|
||||||
vars: map[string]any{},
|
vars: map[string]any{},
|
||||||
nodeDir: ".",
|
|
||||||
TaskTest: TaskTest{
|
TaskTest: TaskTest{
|
||||||
experiments: map[*experiments.Experiment]int{},
|
experiments: map[*experiments.Experiment]int{},
|
||||||
fixtureTemplateData: map[string]any{},
|
fixtureTemplateData: map[string]any{},
|
||||||
@@ -153,52 +144,11 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
|||||||
f := func(t *testing.T) {
|
f := func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Create a new root node for the given entrypoint
|
opts := append(
|
||||||
node, err := taskfile.NewRootNode(
|
|
||||||
tt.nodeEntrypoint,
|
|
||||||
tt.nodeDir,
|
|
||||||
tt.nodeInsecure,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a golden fixture file for the output
|
|
||||||
g := goldie.New(t,
|
|
||||||
goldie.WithFixtureDir(filepath.Join(node.Dir(), "testdata")),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set up a temporary directory for the taskfile reader and task executor
|
|
||||||
tempDir, err := task.NewTempDir(node.Dir())
|
|
||||||
require.NoError(t, err)
|
|
||||||
tt.readerOpts = append(tt.readerOpts, taskfile.WithTempDir(tempDir.Remote))
|
|
||||||
|
|
||||||
// Set up the taskfile reader
|
|
||||||
reader := taskfile.NewReader(tt.readerOpts...)
|
|
||||||
graph, err := reader.Read(ctx, node)
|
|
||||||
if tt.wantReaderError {
|
|
||||||
require.Error(t, err)
|
|
||||||
tt.writeFixtureErrReader(t, g, err)
|
|
||||||
tt.writeFixtureBuffer(t, g, buf)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
executorOpts := slices.Concat(
|
|
||||||
// Apply the node directory and temp directory to the executor options
|
|
||||||
// by default, but allow them to by overridden by the test options
|
|
||||||
[]task.ExecutorOption{
|
|
||||||
task.WithDir(node.Dir()),
|
|
||||||
task.WithTempDir(tempDir),
|
|
||||||
},
|
|
||||||
// Apply the executor options from the test
|
|
||||||
tt.executorOpts,
|
tt.executorOpts,
|
||||||
// Force the input/output streams to be set to the test buffer
|
task.WithStdout(&buf),
|
||||||
[]task.ExecutorOption{
|
task.WithStderr(&buf),
|
||||||
task.WithStdout(&buf),
|
|
||||||
task.WithStderr(&buf),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// If the test has input, create a reader for it and add it to the
|
// If the test has input, create a reader for it and add it to the
|
||||||
@@ -206,12 +156,19 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
|||||||
if tt.input != "" {
|
if tt.input != "" {
|
||||||
var reader bytes.Buffer
|
var reader bytes.Buffer
|
||||||
reader.WriteString(tt.input)
|
reader.WriteString(tt.input)
|
||||||
executorOpts = append(executorOpts, task.WithStdin(&reader))
|
opts = append(opts, task.WithStdin(&reader))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the task executor
|
// Set up the task executor
|
||||||
executor, err := task.NewExecutor(graph, executorOpts...)
|
e := task.NewExecutor(opts...)
|
||||||
if tt.wantSetupError {
|
|
||||||
|
// Create a golden fixture file for the output
|
||||||
|
g := goldie.New(t,
|
||||||
|
goldie.WithFixtureDir(filepath.Join(e.Dir, "testdata")),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Call setup and check for errors
|
||||||
|
if err := e.Setup(); tt.wantSetupError {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
tt.writeFixtureErrSetup(t, g, err)
|
tt.writeFixtureErrSetup(t, g, err)
|
||||||
tt.writeFixtureBuffer(t, g, buf)
|
tt.writeFixtureBuffer(t, g, buf)
|
||||||
@@ -231,7 +188,8 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run the task and check for errors
|
// Run the task and check for errors
|
||||||
if err := executor.Run(ctx, call); tt.wantRunError {
|
ctx := t.Context()
|
||||||
|
if err := e.Run(ctx, call); tt.wantRunError {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
tt.writeFixtureErrRun(t, g, err)
|
tt.writeFixtureErrRun(t, g, err)
|
||||||
tt.writeFixtureBuffer(t, g, buf)
|
tt.writeFixtureBuffer(t, g, buf)
|
||||||
@@ -242,7 +200,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
|||||||
|
|
||||||
// If the status flag is set, run the status check
|
// If the status flag is set, run the status check
|
||||||
if tt.wantStatusError {
|
if tt.wantStatusError {
|
||||||
if err := executor.Status(ctx, call); err != nil {
|
if err := e.Status(ctx, call); err != nil {
|
||||||
tt.writeFixtureStatus(t, g, err.Error())
|
tt.writeFixtureStatus(t, g, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,16 +219,19 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
|||||||
func TestEmptyTask(t *testing.T) {
|
func TestEmptyTask(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithNodeDir("testdata/empty_task"),
|
WithExecutorOptions(
|
||||||
WithExecutorOptions(),
|
task.WithDir("testdata/empty_task"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyTaskfile(t *testing.T) {
|
func TestEmptyTaskfile(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithNodeDir("testdata/empty_taskfile"),
|
WithExecutorOptions(
|
||||||
WithReaderError(),
|
task.WithDir("testdata/empty_taskfile"),
|
||||||
|
),
|
||||||
|
WithSetupError(),
|
||||||
WithFixtureTemplating(),
|
WithFixtureTemplating(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -279,15 +240,15 @@ func TestEnv(t *testing.T) {
|
|||||||
t.Setenv("QUX", "from_os")
|
t.Setenv("QUX", "from_os")
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("env precedence disabled"),
|
WithName("env precedence disabled"),
|
||||||
WithNodeDir("testdata/env"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/env"),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("env precedence enabled"),
|
WithName("env precedence enabled"),
|
||||||
WithNodeDir("testdata/env"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/env"),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
WithExperiment(&experiments.EnvPrecedence, 1),
|
WithExperiment(&experiments.EnvPrecedence, 1),
|
||||||
@@ -297,8 +258,8 @@ func TestEnv(t *testing.T) {
|
|||||||
func TestVars(t *testing.T) {
|
func TestVars(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithNodeDir("testdata/vars"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/vars"),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -308,19 +269,25 @@ func TestRequires(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("required var missing"),
|
WithName("required var missing"),
|
||||||
WithNodeDir("testdata/requires"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/requires"),
|
||||||
|
),
|
||||||
WithTask("missing-var"),
|
WithTask("missing-var"),
|
||||||
WithRunError(),
|
WithRunError(),
|
||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("required var ok"),
|
WithName("required var ok"),
|
||||||
WithNodeDir("testdata/requires"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/requires"),
|
||||||
|
),
|
||||||
WithTask("missing-var"),
|
WithTask("missing-var"),
|
||||||
WithVar("FOO", "bar"),
|
WithVar("FOO", "bar"),
|
||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("fails validation"),
|
WithName("fails validation"),
|
||||||
WithNodeDir("testdata/requires"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/requires"),
|
||||||
|
),
|
||||||
WithTask("validation-var"),
|
WithTask("validation-var"),
|
||||||
WithVar("ENV", "dev"),
|
WithVar("ENV", "dev"),
|
||||||
WithVar("FOO", "bar"),
|
WithVar("FOO", "bar"),
|
||||||
@@ -328,37 +295,48 @@ func TestRequires(t *testing.T) {
|
|||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("passes validation"),
|
WithName("passes validation"),
|
||||||
WithNodeDir("testdata/requires"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/requires"),
|
||||||
|
),
|
||||||
WithTask("validation-var"),
|
WithTask("validation-var"),
|
||||||
WithVar("FOO", "one"),
|
WithVar("FOO", "one"),
|
||||||
WithVar("ENV", "dev"),
|
WithVar("ENV", "dev"),
|
||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("required var missing + fails validation"),
|
WithName("required var missing + fails validation"),
|
||||||
WithNodeDir("testdata/requires"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/requires"),
|
||||||
|
),
|
||||||
WithTask("validation-var"),
|
WithTask("validation-var"),
|
||||||
WithRunError(),
|
WithRunError(),
|
||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("required var missing + fails validation"),
|
WithName("required var missing + fails validation"),
|
||||||
WithNodeDir("testdata/requires"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/requires"),
|
||||||
|
),
|
||||||
WithTask("validation-var-dynamic"),
|
WithTask("validation-var-dynamic"),
|
||||||
WithVar("FOO", "one"),
|
WithVar("FOO", "one"),
|
||||||
WithVar("ENV", "dev"),
|
WithVar("ENV", "dev"),
|
||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("require before compile"),
|
WithName("require before compile"),
|
||||||
WithNodeDir("testdata/requires"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/requires"),
|
||||||
|
),
|
||||||
WithTask("require-before-compile"),
|
WithTask("require-before-compile"),
|
||||||
WithRunError(),
|
WithRunError(),
|
||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("var defined in task"),
|
WithName("var defined in task"),
|
||||||
WithNodeDir("testdata/requires"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/requires"),
|
||||||
|
),
|
||||||
WithTask("var-defined-in-task"),
|
WithTask("var-defined-in-task"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: mock fs
|
||||||
func TestSpecialVars(t *testing.T) {
|
func TestSpecialVars(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -379,13 +357,12 @@ func TestSpecialVars(t *testing.T) {
|
|||||||
"included:print-taskfile-dir",
|
"included:print-taskfile-dir",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, executorDir := range []string{dir, subdir} {
|
for _, dir := range []string{dir, subdir} {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
name := fmt.Sprintf("%s-%s", executorDir, test)
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName(name),
|
WithName(fmt.Sprintf("%s-%s", dir, test)),
|
||||||
WithNodeDir(executorDir),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
task.WithVersionCheck(true),
|
task.WithVersionCheck(true),
|
||||||
),
|
),
|
||||||
@@ -399,8 +376,8 @@ func TestSpecialVars(t *testing.T) {
|
|||||||
func TestConcurrency(t *testing.T) {
|
func TestConcurrency(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithNodeDir("testdata/concurrency"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/concurrency"),
|
||||||
task.WithConcurrency(1),
|
task.WithConcurrency(1),
|
||||||
),
|
),
|
||||||
WithPostProcessFn(PPSortedLines),
|
WithPostProcessFn(PPSortedLines),
|
||||||
@@ -410,8 +387,8 @@ func TestConcurrency(t *testing.T) {
|
|||||||
func TestParams(t *testing.T) {
|
func TestParams(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithNodeDir("testdata/params"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/params"),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
WithPostProcessFn(PPSortedLines),
|
WithPostProcessFn(PPSortedLines),
|
||||||
@@ -421,14 +398,15 @@ func TestParams(t *testing.T) {
|
|||||||
func TestDeps(t *testing.T) {
|
func TestDeps(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithNodeDir("testdata/deps"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/deps"),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
WithPostProcessFn(PPSortedLines),
|
WithPostProcessFn(PPSortedLines),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: mock fs
|
||||||
func TestStatus(t *testing.T) {
|
func TestStatus(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -451,8 +429,8 @@ func TestStatus(t *testing.T) {
|
|||||||
// gen-foo creates foo.txt, and will always fail it's status check.
|
// gen-foo creates foo.txt, and will always fail it's status check.
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-foo 1 silent"),
|
WithName("run gen-foo 1 silent"),
|
||||||
WithNodeDir(dir),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
WithTask("gen-foo"),
|
WithTask("gen-foo"),
|
||||||
@@ -463,8 +441,8 @@ func TestStatus(t *testing.T) {
|
|||||||
// only exists after the first run.
|
// only exists after the first run.
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-bar 1 silent"),
|
WithName("run gen-bar 1 silent"),
|
||||||
WithNodeDir(dir),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
WithTask("gen-bar"),
|
WithTask("gen-bar"),
|
||||||
@@ -473,8 +451,8 @@ func TestStatus(t *testing.T) {
|
|||||||
// if e.Verbose is set to true.
|
// if e.Verbose is set to true.
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-baz silent"),
|
WithName("run gen-baz silent"),
|
||||||
WithNodeDir(dir),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
WithTask("gen-silent-baz"),
|
WithTask("gen-silent-baz"),
|
||||||
@@ -489,8 +467,8 @@ func TestStatus(t *testing.T) {
|
|||||||
// Run gen-bar a second time to produce a checksum file that matches bar.txt
|
// Run gen-bar a second time to produce a checksum file that matches bar.txt
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-bar 2 silent"),
|
WithName("run gen-bar 2 silent"),
|
||||||
WithNodeDir(dir),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
WithTask("gen-bar"),
|
WithTask("gen-bar"),
|
||||||
@@ -498,8 +476,8 @@ func TestStatus(t *testing.T) {
|
|||||||
// Run gen-bar a third time, to make sure we've triggered the status check.
|
// Run gen-bar a third time, to make sure we've triggered the status check.
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-bar 3 silent"),
|
WithName("run gen-bar 3 silent"),
|
||||||
WithNodeDir(dir),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
WithTask("gen-bar"),
|
WithTask("gen-bar"),
|
||||||
@@ -511,8 +489,8 @@ func TestStatus(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-bar 4 silent"),
|
WithName("run gen-bar 4 silent"),
|
||||||
WithNodeDir(dir),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
),
|
),
|
||||||
WithTask("gen-bar"),
|
WithTask("gen-bar"),
|
||||||
@@ -520,44 +498,56 @@ func TestStatus(t *testing.T) {
|
|||||||
// all: not up-to-date
|
// all: not up-to-date
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-foo 2"),
|
WithName("run gen-foo 2"),
|
||||||
WithNodeDir(dir),
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
|
),
|
||||||
WithTask("gen-foo"),
|
WithTask("gen-foo"),
|
||||||
)
|
)
|
||||||
// status: not up-to-date
|
// status: not up-to-date
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-foo 3"),
|
WithName("run gen-foo 3"),
|
||||||
WithNodeDir(dir),
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
|
),
|
||||||
WithTask("gen-foo"),
|
WithTask("gen-foo"),
|
||||||
)
|
)
|
||||||
// sources: not up-to-date
|
// sources: not up-to-date
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-bar 5"),
|
WithName("run gen-bar 5"),
|
||||||
WithNodeDir(dir),
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
|
),
|
||||||
WithTask("gen-bar"),
|
WithTask("gen-bar"),
|
||||||
)
|
)
|
||||||
// all: up-to-date
|
// all: up-to-date
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-bar 6"),
|
WithName("run gen-bar 6"),
|
||||||
WithNodeDir(dir),
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
|
),
|
||||||
WithTask("gen-bar"),
|
WithTask("gen-bar"),
|
||||||
)
|
)
|
||||||
// sources: not up-to-date, no output produced.
|
// sources: not up-to-date, no output produced.
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-baz 2"),
|
WithName("run gen-baz 2"),
|
||||||
WithNodeDir(dir),
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
|
),
|
||||||
WithTask("gen-silent-baz"),
|
WithTask("gen-silent-baz"),
|
||||||
)
|
)
|
||||||
// up-to-date, no output produced
|
// up-to-date, no output produced
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-baz 3"),
|
WithName("run gen-baz 3"),
|
||||||
WithNodeDir(dir),
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
|
),
|
||||||
WithTask("gen-silent-baz"),
|
WithTask("gen-silent-baz"),
|
||||||
)
|
)
|
||||||
// up-to-date, output produced due to Verbose mode.
|
// up-to-date, output produced due to Verbose mode.
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("run gen-baz 4 verbose"),
|
WithName("run gen-baz 4 verbose"),
|
||||||
WithNodeDir(dir),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
task.WithVerbose(true),
|
task.WithVerbose(true),
|
||||||
),
|
),
|
||||||
WithTask("gen-silent-baz"),
|
WithTask("gen-silent-baz"),
|
||||||
@@ -570,24 +560,32 @@ func TestPrecondition(t *testing.T) {
|
|||||||
const dir = "testdata/precondition"
|
const dir = "testdata/precondition"
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("a precondition has been met"),
|
WithName("a precondition has been met"),
|
||||||
WithNodeDir(dir),
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
|
),
|
||||||
WithTask("foo"),
|
WithTask("foo"),
|
||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("a precondition was not met"),
|
WithName("a precondition was not met"),
|
||||||
WithNodeDir(dir),
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
|
),
|
||||||
WithTask("impossible"),
|
WithTask("impossible"),
|
||||||
WithRunError(),
|
WithRunError(),
|
||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("precondition in dependency fails the task"),
|
WithName("precondition in dependency fails the task"),
|
||||||
WithNodeDir(dir),
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
|
),
|
||||||
WithTask("depends_on_impossible"),
|
WithTask("depends_on_impossible"),
|
||||||
WithRunError(),
|
WithRunError(),
|
||||||
)
|
)
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("precondition in cmd fails the task"),
|
WithName("precondition in cmd fails the task"),
|
||||||
WithNodeDir(dir),
|
WithExecutorOptions(
|
||||||
|
task.WithDir(dir),
|
||||||
|
),
|
||||||
WithTask("executes_failing_task_as_cmd"),
|
WithTask("executes_failing_task_as_cmd"),
|
||||||
WithRunError(),
|
WithRunError(),
|
||||||
)
|
)
|
||||||
@@ -598,21 +596,25 @@ func TestAlias(t *testing.T) {
|
|||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("alias"),
|
WithName("alias"),
|
||||||
WithNodeDir("testdata/alias"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/alias"),
|
||||||
|
),
|
||||||
WithTask("f"),
|
WithTask("f"),
|
||||||
)
|
)
|
||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("duplicate alias"),
|
WithName("duplicate alias"),
|
||||||
WithNodeDir("testdata/alias"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/alias"),
|
||||||
|
),
|
||||||
WithTask("x"),
|
WithTask("x"),
|
||||||
WithRunError(),
|
WithRunError(),
|
||||||
)
|
)
|
||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("alias summary"),
|
WithName("alias summary"),
|
||||||
WithNodeDir("testdata/alias"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/alias"),
|
||||||
task.WithSummary(true),
|
task.WithSummary(true),
|
||||||
),
|
),
|
||||||
WithTask("f"),
|
WithTask("f"),
|
||||||
@@ -624,14 +626,16 @@ func TestLabel(t *testing.T) {
|
|||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("up to date"),
|
WithName("up to date"),
|
||||||
WithNodeDir("testdata/label_uptodate"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/label_uptodate"),
|
||||||
|
),
|
||||||
WithTask("foo"),
|
WithTask("foo"),
|
||||||
)
|
)
|
||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("summary"),
|
WithName("summary"),
|
||||||
WithNodeDir("testdata/label_summary"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/label_summary"),
|
||||||
task.WithSummary(true),
|
task.WithSummary(true),
|
||||||
),
|
),
|
||||||
WithTask("foo"),
|
WithTask("foo"),
|
||||||
@@ -639,20 +643,26 @@ func TestLabel(t *testing.T) {
|
|||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("status"),
|
WithName("status"),
|
||||||
WithNodeDir("testdata/label_status"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/label_status"),
|
||||||
|
),
|
||||||
WithTask("foo"),
|
WithTask("foo"),
|
||||||
WithStatusError(),
|
WithStatusError(),
|
||||||
)
|
)
|
||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("var"),
|
WithName("var"),
|
||||||
WithNodeDir("testdata/label_var"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/label_var"),
|
||||||
|
),
|
||||||
WithTask("foo"),
|
WithTask("foo"),
|
||||||
)
|
)
|
||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("label in summary"),
|
WithName("label in summary"),
|
||||||
WithNodeDir("testdata/label_summary"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/label_summary"),
|
||||||
|
),
|
||||||
WithTask("foo"),
|
WithTask("foo"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -679,8 +689,8 @@ func TestPromptInSummary(t *testing.T) {
|
|||||||
|
|
||||||
opts := []ExecutorTestOption{
|
opts := []ExecutorTestOption{
|
||||||
WithName(test.name),
|
WithName(test.name),
|
||||||
WithNodeDir("testdata/prompt"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/prompt"),
|
||||||
task.WithAssumeTerm(true),
|
task.WithAssumeTerm(true),
|
||||||
),
|
),
|
||||||
WithTask("foo"),
|
WithTask("foo"),
|
||||||
@@ -698,8 +708,8 @@ func TestPromptWithIndirectTask(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithNodeDir("testdata/prompt"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/prompt"),
|
||||||
task.WithAssumeTerm(true),
|
task.WithAssumeTerm(true),
|
||||||
),
|
),
|
||||||
WithTask("bar"),
|
WithTask("bar"),
|
||||||
@@ -712,8 +722,8 @@ func TestPromptAssumeYes(t *testing.T) {
|
|||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("--yes flag should skip prompt"),
|
WithName("--yes flag should skip prompt"),
|
||||||
WithNodeDir("testdata/prompt"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/prompt"),
|
||||||
task.WithAssumeTerm(true),
|
task.WithAssumeTerm(true),
|
||||||
task.WithAssumeYes(true),
|
task.WithAssumeYes(true),
|
||||||
),
|
),
|
||||||
@@ -723,8 +733,8 @@ func TestPromptAssumeYes(t *testing.T) {
|
|||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("task should raise errors.TaskCancelledError"),
|
WithName("task should raise errors.TaskCancelledError"),
|
||||||
WithNodeDir("testdata/prompt"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/prompt"),
|
||||||
task.WithAssumeTerm(true),
|
task.WithAssumeTerm(true),
|
||||||
),
|
),
|
||||||
WithTask("foo"),
|
WithTask("foo"),
|
||||||
@@ -761,8 +771,8 @@ func TestForCmds(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
opts := []ExecutorTestOption{
|
opts := []ExecutorTestOption{
|
||||||
WithName(test.name),
|
WithName(test.name),
|
||||||
WithNodeDir("testdata/for/cmds"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/for/cmds"),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
task.WithForce(true),
|
task.WithForce(true),
|
||||||
),
|
),
|
||||||
@@ -804,8 +814,8 @@ func TestForDeps(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
opts := []ExecutorTestOption{
|
opts := []ExecutorTestOption{
|
||||||
WithName(test.name),
|
WithName(test.name),
|
||||||
WithNodeDir("testdata/for/deps"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/for/deps"),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
task.WithForce(true),
|
task.WithForce(true),
|
||||||
// Force output of each dep to be grouped together to prevent interleaving
|
// Force output of each dep to be grouped together to prevent interleaving
|
||||||
@@ -850,8 +860,8 @@ func TestReference(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName(test.name),
|
WithName(test.name),
|
||||||
WithNodeDir("testdata/var_references"),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/var_references"),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
task.WithForce(true),
|
task.WithForce(true),
|
||||||
),
|
),
|
||||||
@@ -918,8 +928,8 @@ func TestVarInheritance(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName(test.name),
|
WithName(test.name),
|
||||||
WithNodeDir(fmt.Sprintf("testdata/var_inheritance/v3/%s", test.name)),
|
|
||||||
WithExecutorOptions(
|
WithExecutorOptions(
|
||||||
|
task.WithDir(fmt.Sprintf("testdata/var_inheritance/v3/%s", test.name)),
|
||||||
task.WithSilent(true),
|
task.WithSilent(true),
|
||||||
task.WithForce(true),
|
task.WithForce(true),
|
||||||
),
|
),
|
||||||
@@ -933,20 +943,26 @@ func TestFuzzyModel(t *testing.T) {
|
|||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("fuzzy"),
|
WithName("fuzzy"),
|
||||||
WithNodeDir("testdata/fuzzy"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/fuzzy"),
|
||||||
|
),
|
||||||
WithTask("instal"),
|
WithTask("instal"),
|
||||||
WithRunError(),
|
WithRunError(),
|
||||||
)
|
)
|
||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("not-fuzzy"),
|
WithName("not-fuzzy"),
|
||||||
WithNodeDir("testdata/fuzzy"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/fuzzy"),
|
||||||
|
),
|
||||||
WithTask("install"),
|
WithTask("install"),
|
||||||
)
|
)
|
||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("intern"),
|
WithName("intern"),
|
||||||
WithNodeDir("testdata/fuzzy"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/fuzzy"),
|
||||||
|
),
|
||||||
WithTask("intern"),
|
WithTask("intern"),
|
||||||
WithRunError(),
|
WithRunError(),
|
||||||
)
|
)
|
||||||
@@ -957,65 +973,17 @@ func TestIncludeChecksum(t *testing.T) {
|
|||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("correct"),
|
WithName("correct"),
|
||||||
WithNodeDir("testdata/includes_checksum/correct"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/includes_checksum/correct"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
NewExecutorTest(t,
|
NewExecutorTest(t,
|
||||||
WithName("incorrect"),
|
WithName("incorrect"),
|
||||||
WithNodeDir("testdata/includes_checksum/incorrect"),
|
WithExecutorOptions(
|
||||||
WithReaderError(),
|
task.WithDir("testdata/includes_checksum/incorrect"),
|
||||||
|
),
|
||||||
|
WithSetupError(),
|
||||||
WithFixtureTemplating(),
|
WithFixtureTemplating(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWildcard(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
call string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "basic wildcard",
|
|
||||||
call: "wildcard-foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "double wildcard",
|
|
||||||
call: "foo-wildcard-bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "store wildcard",
|
|
||||||
call: "start-foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "matches exactly",
|
|
||||||
call: "matches-exactly-*",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no matches",
|
|
||||||
call: "no-match",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple matches",
|
|
||||||
call: "wildcard-foo-bar",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
opts := []ExecutorTestOption{
|
|
||||||
WithName(test.name),
|
|
||||||
WithNodeDir("testdata/wildcards"),
|
|
||||||
WithExecutorOptions(
|
|
||||||
task.WithSilent(true),
|
|
||||||
task.WithForce(true),
|
|
||||||
),
|
|
||||||
WithTask(test.call),
|
|
||||||
}
|
|
||||||
if test.wantErr {
|
|
||||||
opts = append(opts, WithRunError())
|
|
||||||
}
|
|
||||||
NewExecutorTest(t, opts...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/taskrc"
|
"github.com/go-task/task/v3/taskrc"
|
||||||
|
"github.com/go-task/task/v3/taskrc/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
const envPrefix = "TASK_X_"
|
const envPrefix = "TASK_X_"
|
||||||
@@ -31,16 +32,15 @@ var (
|
|||||||
var xList []Experiment
|
var xList []Experiment
|
||||||
|
|
||||||
func Parse(dir string) {
|
func Parse(dir string) {
|
||||||
|
config, _ := taskrc.GetConfig(dir)
|
||||||
|
|
||||||
|
ParseWithConfig(dir, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseWithConfig(dir string, config *ast.TaskRC) {
|
||||||
// Read any .env files
|
// Read any .env files
|
||||||
readDotEnv(dir)
|
readDotEnv(dir)
|
||||||
|
|
||||||
// Create a node for the Task config reader
|
|
||||||
node, _ := taskrc.NewNode("", dir)
|
|
||||||
|
|
||||||
// Read the Task config file
|
|
||||||
reader := taskrc.NewReader()
|
|
||||||
config, _ := reader.Read(node)
|
|
||||||
|
|
||||||
// Initialize the experiments
|
// Initialize the experiments
|
||||||
GentleForce = New("GENTLE_FORCE", config, 1)
|
GentleForce = New("GENTLE_FORCE", config, 1)
|
||||||
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)
|
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package task_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sebdah/goldie/v2"
|
"github.com/sebdah/goldie/v2"
|
||||||
@@ -12,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/go-task/task/v3"
|
"github.com/go-task/task/v3"
|
||||||
"github.com/go-task/task/v3/experiments"
|
"github.com/go-task/task/v3/experiments"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,17 +26,12 @@ type (
|
|||||||
// running `task gen:fixtures`.
|
// running `task gen:fixtures`.
|
||||||
FormatterTest struct {
|
FormatterTest struct {
|
||||||
TaskTest
|
TaskTest
|
||||||
task string
|
task string
|
||||||
vars map[string]any
|
vars map[string]any
|
||||||
nodeDir string
|
executorOpts []task.ExecutorOption
|
||||||
nodeEntrypoint string
|
listOptions task.ListOptions
|
||||||
nodeInsecure bool
|
wantSetupError bool
|
||||||
readerOpts []taskfile.ReaderOption
|
wantListError bool
|
||||||
executorOpts []task.ExecutorOption
|
|
||||||
listOptions task.ListOptions
|
|
||||||
wantReaderError bool
|
|
||||||
wantSetupError bool
|
|
||||||
wantListError bool
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,9 +41,8 @@ type (
|
|||||||
func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) {
|
func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
tt := &FormatterTest{
|
tt := &FormatterTest{
|
||||||
task: "default",
|
task: "default",
|
||||||
vars: map[string]any{},
|
vars: map[string]any{},
|
||||||
nodeDir: ".",
|
|
||||||
TaskTest: TaskTest{
|
TaskTest: TaskTest{
|
||||||
experiments: map[*experiments.Experiment]int{},
|
experiments: map[*experiments.Experiment]int{},
|
||||||
fixtureTemplateData: map[string]any{},
|
fixtureTemplateData: map[string]any{},
|
||||||
@@ -123,57 +114,23 @@ func (tt *FormatterTest) run(t *testing.T) {
|
|||||||
f := func(t *testing.T) {
|
f := func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Create a new root node for the given entrypoint
|
opts := append(
|
||||||
node, err := taskfile.NewRootNode(
|
|
||||||
tt.nodeEntrypoint,
|
|
||||||
tt.nodeDir,
|
|
||||||
tt.nodeInsecure,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a golden fixture file for the output
|
|
||||||
g := goldie.New(t,
|
|
||||||
goldie.WithFixtureDir(filepath.Join(node.Dir(), "testdata")),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set up a temporary directory for the taskfile reader and task executor
|
|
||||||
tempDir, err := task.NewTempDir(node.Dir())
|
|
||||||
require.NoError(t, err)
|
|
||||||
tt.readerOpts = append(tt.readerOpts, taskfile.WithTempDir(tempDir.Remote))
|
|
||||||
|
|
||||||
// Set up the taskfile reader
|
|
||||||
reader := taskfile.NewReader(tt.readerOpts...)
|
|
||||||
graph, err := reader.Read(ctx, node)
|
|
||||||
if tt.wantReaderError {
|
|
||||||
require.Error(t, err)
|
|
||||||
tt.writeFixtureErrReader(t, g, err)
|
|
||||||
tt.writeFixtureBuffer(t, g, buf)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
executorOpts := slices.Concat(
|
|
||||||
// Apply the node directory and temp directory to the executor options
|
|
||||||
// by default, but allow them to by overridden by the test options
|
|
||||||
[]task.ExecutorOption{
|
|
||||||
task.WithDir(node.Dir()),
|
|
||||||
task.WithTempDir(tempDir),
|
|
||||||
},
|
|
||||||
// Apply the executor options from the test
|
|
||||||
tt.executorOpts,
|
tt.executorOpts,
|
||||||
// Force the input/output streams to be set to the test buffer
|
task.WithStdout(&buf),
|
||||||
[]task.ExecutorOption{
|
task.WithStderr(&buf),
|
||||||
task.WithStdout(&buf),
|
|
||||||
task.WithStderr(&buf),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set up the task executor
|
// Set up the task executor
|
||||||
executor, err := task.NewExecutor(graph, executorOpts...)
|
e := task.NewExecutor(opts...)
|
||||||
if tt.wantSetupError {
|
|
||||||
|
// Create a golden fixture file for the output
|
||||||
|
g := goldie.New(t,
|
||||||
|
goldie.WithFixtureDir(filepath.Join(e.Dir, "testdata")),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Call setup and check for errors
|
||||||
|
if err := e.Setup(); tt.wantSetupError {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
tt.writeFixtureErrSetup(t, g, err)
|
tt.writeFixtureErrSetup(t, g, err)
|
||||||
tt.writeFixtureBuffer(t, g, buf)
|
tt.writeFixtureBuffer(t, g, buf)
|
||||||
@@ -189,7 +146,7 @@ func (tt *FormatterTest) run(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run the formatter and check for errors
|
// Run the formatter and check for errors
|
||||||
if _, err := executor.ListTasks(tt.listOptions); tt.wantListError {
|
if _, err := e.ListTasks(tt.listOptions); tt.wantListError {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
tt.writeFixtureErrList(t, g, err)
|
tt.writeFixtureErrList(t, g, err)
|
||||||
tt.writeFixtureBuffer(t, g, buf)
|
tt.writeFixtureBuffer(t, g, buf)
|
||||||
@@ -213,7 +170,9 @@ func TestNoLabelInList(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
NewFormatterTest(t,
|
NewFormatterTest(t,
|
||||||
WithNodeDir("testdata/label_list"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/label_list"),
|
||||||
|
),
|
||||||
WithListOptions(task.ListOptions{
|
WithListOptions(task.ListOptions{
|
||||||
ListOnlyTasksWithDescriptions: true,
|
ListOnlyTasksWithDescriptions: true,
|
||||||
}),
|
}),
|
||||||
@@ -225,7 +184,9 @@ func TestListAllShowsNoDesc(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
NewFormatterTest(t,
|
NewFormatterTest(t,
|
||||||
WithNodeDir("testdata/list_mixed_desc"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/list_mixed_desc"),
|
||||||
|
),
|
||||||
WithListOptions(task.ListOptions{
|
WithListOptions(task.ListOptions{
|
||||||
ListAllTasks: true,
|
ListAllTasks: true,
|
||||||
}),
|
}),
|
||||||
@@ -237,7 +198,9 @@ func TestListCanListDescOnly(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
NewFormatterTest(t,
|
NewFormatterTest(t,
|
||||||
WithNodeDir("testdata/list_mixed_desc"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/list_mixed_desc"),
|
||||||
|
),
|
||||||
WithListOptions(task.ListOptions{
|
WithListOptions(task.ListOptions{
|
||||||
ListOnlyTasksWithDescriptions: true,
|
ListOnlyTasksWithDescriptions: true,
|
||||||
}),
|
}),
|
||||||
@@ -248,7 +211,9 @@ func TestListDescInterpolation(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
NewFormatterTest(t,
|
NewFormatterTest(t,
|
||||||
WithNodeDir("testdata/list_desc_interpolation"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/list_desc_interpolation"),
|
||||||
|
),
|
||||||
WithListOptions(task.ListOptions{
|
WithListOptions(task.ListOptions{
|
||||||
ListOnlyTasksWithDescriptions: true,
|
ListOnlyTasksWithDescriptions: true,
|
||||||
}),
|
}),
|
||||||
@@ -259,7 +224,9 @@ func TestJsonListFormat(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
NewFormatterTest(t,
|
NewFormatterTest(t,
|
||||||
WithNodeDir("testdata/json_list_format"),
|
WithExecutorOptions(
|
||||||
|
task.WithDir("testdata/json_list_format"),
|
||||||
|
),
|
||||||
WithListOptions(task.ListOptions{
|
WithListOptions(task.ListOptions{
|
||||||
FormatTaskListAsJSON: true,
|
FormatTaskListAsJSON: true,
|
||||||
}),
|
}),
|
||||||
|
|||||||
21
go.mod
21
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/go-task/task/v3
|
module github.com/go-task/task/v3
|
||||||
|
|
||||||
go 1.23.0
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Ladicle/tabwriter v1.0.0
|
github.com/Ladicle/tabwriter v1.0.0
|
||||||
@@ -19,16 +19,16 @@ require (
|
|||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
github.com/otiai10/copy v1.14.1
|
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||||
github.com/sajari/fuzzy v1.0.0
|
github.com/sajari/fuzzy v1.0.0
|
||||||
github.com/sebdah/goldie/v2 v2.7.1
|
github.com/sebdah/goldie/v2 v2.7.1
|
||||||
github.com/spf13/pflag v1.0.7
|
github.com/spf13/pflag v1.0.10
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/zeebo/xxh3 v1.0.2
|
github.com/zeebo/xxh3 v1.0.2
|
||||||
golang.org/x/sync v0.16.0
|
golang.org/x/sync v0.17.0
|
||||||
golang.org/x/term v0.33.0
|
golang.org/x/term v0.35.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
mvdan.cc/sh/moreinterp v0.0.0-20250807215248-5a1a658912aa
|
||||||
mvdan.cc/sh/v3 v3.12.0
|
mvdan.cc/sh/v3 v3.12.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,23 +39,28 @@ require (
|
|||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/otiai10/mint v1.6.3 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/u-root/u-root v0.14.1-0.20250807200646-5e7721023dc7 // indirect
|
||||||
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
golang.org/x/crypto v0.37.0 // indirect
|
golang.org/x/crypto v0.37.0 // indirect
|
||||||
golang.org/x/net v0.39.0 // indirect
|
golang.org/x/net v0.39.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
50
go.sum
50
go.sum
@@ -11,12 +11,10 @@ github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNx
|
|||||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||||
github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4=
|
|
||||||
github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
|
||||||
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
|
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
|
||||||
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
||||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
@@ -36,6 +34,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
|
|||||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||||
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
|
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
|
||||||
@@ -76,8 +76,12 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -94,10 +98,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4
|
|||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
|
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||||
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
|
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
|
||||||
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
|
||||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@@ -121,14 +123,22 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB
|
|||||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/u-root/u-root v0.14.1-0.20250807200646-5e7721023dc7 h1:ax+jBy7xFhh+Ka0IGLmH5mft+YDuqvzEjSgWuAP0nsM=
|
||||||
|
github.com/u-root/u-root v0.14.1-0.20250807200646-5e7721023dc7/go.mod h1:/0Qr7qJeDwWxoKku2xKQ4Szc+SwBE3g9VE8jNiamsmc=
|
||||||
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||||
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||||
@@ -138,13 +148,15 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD
|
|||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -154,11 +166,15 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||||
|
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||||
|
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
@@ -173,5 +189,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
mvdan.cc/sh/moreinterp v0.0.0-20250807215248-5a1a658912aa h1:sRmA9AmA5+9CbK6a7N52q9W9jAeoBy1EJ7cncm+SLxw=
|
||||||
|
mvdan.cc/sh/moreinterp v0.0.0-20250807215248-5a1a658912aa/go.mod h1:Of9PCedbLDYT8b3EyiYG64rNnx5nOp27OLCVdDrjJyo=
|
||||||
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
|
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
|
||||||
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
|
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
|
||||||
|
|||||||
65
help.go
65
help.go
@@ -24,15 +24,17 @@ type ListOptions struct {
|
|||||||
ListAllTasks bool
|
ListAllTasks bool
|
||||||
FormatTaskListAsJSON bool
|
FormatTaskListAsJSON bool
|
||||||
NoStatus bool
|
NoStatus bool
|
||||||
|
Nested bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewListOptions creates a new ListOptions instance
|
// NewListOptions creates a new ListOptions instance
|
||||||
func NewListOptions(list, listAll, listAsJson, noStatus bool) ListOptions {
|
func NewListOptions(list, listAll, listAsJson, noStatus, nested bool) ListOptions {
|
||||||
return ListOptions{
|
return ListOptions{
|
||||||
ListOnlyTasksWithDescriptions: list,
|
ListOnlyTasksWithDescriptions: list,
|
||||||
ListAllTasks: listAll,
|
ListAllTasks: listAll,
|
||||||
FormatTaskListAsJSON: listAsJson,
|
FormatTaskListAsJSON: listAsJson,
|
||||||
NoStatus: noStatus,
|
NoStatus: noStatus,
|
||||||
|
Nested: nested,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if o.FormatTaskListAsJSON {
|
if o.FormatTaskListAsJSON {
|
||||||
output, err := e.ToEditorOutput(tasks, o.NoStatus)
|
output, err := e.ToEditorOutput(tasks, o.NoStatus, o.Nested)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -135,33 +137,17 @@ func (e *Executor) ListTaskNames(allTasks bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Taskfile, error) {
|
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool) (*editors.Namespace, error) {
|
||||||
o := &editors.Taskfile{
|
|
||||||
Tasks: make([]editors.Task, len(tasks)),
|
|
||||||
Location: e.Taskfile.Location,
|
|
||||||
}
|
|
||||||
var g errgroup.Group
|
var g errgroup.Group
|
||||||
|
editorTasks := make([]editors.Task, len(tasks))
|
||||||
|
|
||||||
|
// Look over each task in parallel and turn it into an editor task
|
||||||
for i := range tasks {
|
for i := range tasks {
|
||||||
aliases := []string{}
|
|
||||||
if len(tasks[i].Aliases) > 0 {
|
|
||||||
aliases = tasks[i].Aliases
|
|
||||||
}
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
o.Tasks[i] = editors.Task{
|
editorTask := editors.NewTask(tasks[i])
|
||||||
Name: tasks[i].Name(),
|
|
||||||
Task: tasks[i].Task,
|
|
||||||
Desc: tasks[i].Desc,
|
|
||||||
Summary: tasks[i].Summary,
|
|
||||||
Aliases: aliases,
|
|
||||||
UpToDate: false,
|
|
||||||
Location: &editors.Location{
|
|
||||||
Line: tasks[i].Location.Line,
|
|
||||||
Column: tasks[i].Location.Column,
|
|
||||||
Taskfile: tasks[i].Location.Taskfile,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if noStatus {
|
if noStatus {
|
||||||
|
editorTasks[i] = editorTask
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,10 +166,35 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Tasks[i].UpToDate = upToDate
|
editorTask.UpToDate = &upToDate
|
||||||
|
editorTasks[i] = editorTask
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return o, g.Wait()
|
if err := g.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the root namespace
|
||||||
|
var tasksLen int
|
||||||
|
if !nested {
|
||||||
|
tasksLen = len(editorTasks)
|
||||||
|
}
|
||||||
|
rootNamespace := &editors.Namespace{
|
||||||
|
Tasks: make([]editors.Task, tasksLen),
|
||||||
|
Location: e.Taskfile.Location,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively add namespaces to the root namespace or if nesting is
|
||||||
|
// disabled add them all to the root namespace
|
||||||
|
for i, task := range editorTasks {
|
||||||
|
taskNamespacePath := strings.Split(task.Task, ast.NamespaceSeparator)
|
||||||
|
if nested {
|
||||||
|
rootNamespace.AddNamespace(taskNamespacePath, task)
|
||||||
|
} else {
|
||||||
|
rootNamespace.Tasks[i] = task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootNamespace, g.Wait()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package editors
|
package editors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Taskfile wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
// Namespace wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
||||||
Taskfile struct {
|
Namespace struct {
|
||||||
Tasks []Task `json:"tasks"`
|
Tasks []Task `json:"tasks"`
|
||||||
Location string `json:"location"`
|
Namespaces map[string]*Namespace `json:"namespaces,omitempty"`
|
||||||
|
Location string `json:"location,omitempty"`
|
||||||
}
|
}
|
||||||
// Task describes a single task
|
// Task describes a single task
|
||||||
Task struct {
|
Task struct {
|
||||||
@@ -13,7 +18,7 @@ type (
|
|||||||
Desc string `json:"desc"`
|
Desc string `json:"desc"`
|
||||||
Summary string `json:"summary"`
|
Summary string `json:"summary"`
|
||||||
Aliases []string `json:"aliases"`
|
Aliases []string `json:"aliases"`
|
||||||
UpToDate bool `json:"up_to_date"`
|
UpToDate *bool `json:"up_to_date,omitempty"`
|
||||||
Location *Location `json:"location"`
|
Location *Location `json:"location"`
|
||||||
}
|
}
|
||||||
// Location describes a task's location in a taskfile
|
// Location describes a task's location in a taskfile
|
||||||
@@ -23,3 +28,59 @@ type (
|
|||||||
Taskfile string `json:"taskfile"`
|
Taskfile string `json:"taskfile"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func NewTask(task *ast.Task) Task {
|
||||||
|
aliases := []string{}
|
||||||
|
if len(task.Aliases) > 0 {
|
||||||
|
aliases = task.Aliases
|
||||||
|
}
|
||||||
|
return Task{
|
||||||
|
Name: task.Name(),
|
||||||
|
Task: task.Task,
|
||||||
|
Desc: task.Desc,
|
||||||
|
Summary: task.Summary,
|
||||||
|
Aliases: aliases,
|
||||||
|
Location: &Location{
|
||||||
|
Line: task.Location.Line,
|
||||||
|
Column: task.Location.Column,
|
||||||
|
Taskfile: task.Location.Taskfile,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parent *Namespace) AddNamespace(namespacePath []string, task Task) {
|
||||||
|
if len(namespacePath) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no child namespaces, then we have found a task and we can
|
||||||
|
// simply add it to the current namespace
|
||||||
|
if len(namespacePath) == 1 {
|
||||||
|
parent.Tasks = append(parent.Tasks, task)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the key of the current namespace in the path
|
||||||
|
namespaceKey := namespacePath[0]
|
||||||
|
|
||||||
|
// Add the namespace to the parent namespaces map using the namespace key
|
||||||
|
if parent.Namespaces == nil {
|
||||||
|
parent.Namespaces = make(map[string]*Namespace, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for the current namespace in the parent namespaces map
|
||||||
|
// If it doesn't exist, create it
|
||||||
|
namespace, ok := parent.Namespaces[namespaceKey]
|
||||||
|
if !ok {
|
||||||
|
namespace = &Namespace{}
|
||||||
|
parent.Namespaces[namespaceKey] = namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the current namespace key from the namespace path.
|
||||||
|
childNamespacePath := namespacePath[1:]
|
||||||
|
|
||||||
|
// If there are no child namespaces in the task name, then we have found the
|
||||||
|
// namespace of the task and we can add it to the current namespace.
|
||||||
|
// Otherwise, we need to go deeper
|
||||||
|
namespace.AddNamespace(childNamespacePath, task)
|
||||||
|
}
|
||||||
|
|||||||
20
internal/execext/coreutils.go
Normal file
20
internal/execext/coreutils.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package execext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
var useGoCoreUtils bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// If TASK_CORE_UTILS is set to either true or false, respect that.
|
||||||
|
// By default, enable on Windows only.
|
||||||
|
if v, err := strconv.ParseBool(env.GetTaskEnv("CORE_UTILS")); err == nil {
|
||||||
|
useGoCoreUtils = v
|
||||||
|
} else {
|
||||||
|
useGoCoreUtils = runtime.GOOS == "windows"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"mvdan.cc/sh/moreinterp/coreutils"
|
||||||
"mvdan.cc/sh/v3/expand"
|
"mvdan.cc/sh/v3/expand"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
@@ -59,7 +59,7 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
|||||||
r, err := interp.New(
|
r, err := interp.New(
|
||||||
interp.Params(params...),
|
interp.Params(params...),
|
||||||
interp.Env(expand.ListEnviron(environ...)),
|
interp.Env(expand.ListEnviron(environ...)),
|
||||||
interp.ExecHandlers(execHandler),
|
interp.ExecHandlers(execHandlers()...),
|
||||||
interp.OpenHandler(openHandler),
|
interp.OpenHandler(openHandler),
|
||||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
||||||
dirOption(opts.Dir),
|
dirOption(opts.Dir),
|
||||||
@@ -143,8 +143,11 @@ func ExpandFields(s string) ([]string, error) {
|
|||||||
return expand.Fields(cfg, words...)
|
return expand.Fields(cfg, words...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func execHandler(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
|
func execHandlers() (handlers []func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc) {
|
||||||
return interp.DefaultExecHandler(15 * time.Second)
|
if useGoCoreUtils {
|
||||||
|
handlers = append(handlers, coreutils.ExecHandler)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package fingerprint
|
package fingerprint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -164,7 +163,7 @@ func TestIsTaskUpToDate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result, err := IsTaskUpToDate(
|
result, err := IsTaskUpToDate(
|
||||||
context.Background(),
|
t.Context(),
|
||||||
tt.task,
|
tt.task,
|
||||||
WithStatusChecker(mockStatusChecker),
|
WithStatusChecker(mockStatusChecker),
|
||||||
WithSourcesChecker(mockSourcesChecker),
|
WithSourcesChecker(mockSourcesChecker),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -13,10 +12,10 @@ import (
|
|||||||
"github.com/go-task/task/v3"
|
"github.com/go-task/task/v3"
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/experiments"
|
"github.com/go-task/task/v3/experiments"
|
||||||
"github.com/go-task/task/v3/internal/env"
|
|
||||||
"github.com/go-task/task/v3/internal/sort"
|
"github.com/go-task/task/v3/internal/sort"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
|
"github.com/go-task/task/v3/taskrc"
|
||||||
|
taskrcast "github.com/go-task/task/v3/taskrc/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usage = `Usage: task [flags...] [task...]
|
const usage = `Usage: task [flags...] [task...]
|
||||||
@@ -52,6 +51,7 @@ var (
|
|||||||
TaskSort string
|
TaskSort string
|
||||||
Status bool
|
Status bool
|
||||||
NoStatus bool
|
NoStatus bool
|
||||||
|
Nested bool
|
||||||
Insecure bool
|
Insecure bool
|
||||||
Force bool
|
Force bool
|
||||||
ForceAll bool
|
ForceAll bool
|
||||||
@@ -96,7 +96,9 @@ func init() {
|
|||||||
|
|
||||||
// Parse the experiments
|
// Parse the experiments
|
||||||
dir = cmp.Or(dir, filepath.Dir(entrypoint))
|
dir = cmp.Or(dir, filepath.Dir(entrypoint))
|
||||||
experiments.Parse(dir)
|
|
||||||
|
config, _ := taskrc.GetConfig(dir)
|
||||||
|
experiments.ParseWithConfig(dir, config)
|
||||||
|
|
||||||
// Parse the rest of the flags
|
// Parse the rest of the flags
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
@@ -105,10 +107,7 @@ func init() {
|
|||||||
log.Print(usage)
|
log.Print(usage)
|
||||||
pflag.PrintDefaults()
|
pflag.PrintDefaults()
|
||||||
}
|
}
|
||||||
offline, err := strconv.ParseBool(cmp.Or(env.GetTaskEnv("OFFLINE"), "false"))
|
|
||||||
if err != nil {
|
|
||||||
offline = false
|
|
||||||
}
|
|
||||||
pflag.BoolVar(&Version, "version", false, "Show Task version.")
|
pflag.BoolVar(&Version, "version", false, "Show Task version.")
|
||||||
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
|
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
|
||||||
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
|
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
|
||||||
@@ -119,9 +118,10 @@ func init() {
|
|||||||
pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].")
|
pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].")
|
||||||
pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.")
|
pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.")
|
||||||
pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON")
|
pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON")
|
||||||
pflag.BoolVar(&Insecure, "insecure", false, "Forces Task to download Taskfiles over insecure connections.")
|
pflag.BoolVar(&Nested, "nested", false, "Nest namespaces when listing tasks as JSON")
|
||||||
|
pflag.BoolVar(&Insecure, "insecure", getConfig(config, func() *bool { return config.Remote.Insecure }, false), "Forces Task to download Taskfiles over insecure connections.")
|
||||||
pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.")
|
pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.")
|
||||||
pflag.BoolVarP(&Verbose, "verbose", "v", false, "Enables verbose mode.")
|
pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, func() *bool { return config.Verbose }, false), "Enables verbose mode.")
|
||||||
pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.")
|
pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.")
|
||||||
pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.")
|
pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.")
|
||||||
pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.")
|
pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.")
|
||||||
@@ -135,7 +135,7 @@ func init() {
|
|||||||
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.")
|
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.")
|
||||||
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.")
|
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.")
|
||||||
pflag.BoolVarP(&Color, "color", "c", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
|
pflag.BoolVarP(&Color, "color", "c", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
|
||||||
pflag.IntVarP(&Concurrency, "concurrency", "C", 0, "Limit number of tasks to run concurrently.")
|
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
|
||||||
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
|
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
|
||||||
pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
|
pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
|
||||||
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
|
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
|
||||||
@@ -151,12 +151,11 @@ func init() {
|
|||||||
// Remote Taskfiles experiment will adds the "download" and "offline" flags
|
// Remote Taskfiles experiment will adds the "download" and "offline" flags
|
||||||
if experiments.RemoteTaskfiles.Enabled() {
|
if experiments.RemoteTaskfiles.Enabled() {
|
||||||
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
|
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
|
||||||
pflag.BoolVar(&Offline, "offline", offline, "Forces Task to only use local or cached Taskfiles.")
|
pflag.BoolVar(&Offline, "offline", getConfig(config, func() *bool { return config.Remote.Offline }, false), "Forces Task to only use local or cached Taskfiles.")
|
||||||
pflag.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.")
|
pflag.DurationVar(&Timeout, "timeout", getConfig(config, func() *time.Duration { return config.Remote.Timeout }, time.Second*10), "Timeout for downloading remote Taskfiles.")
|
||||||
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
|
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
|
||||||
pflag.DurationVar(&CacheExpiryDuration, "expiry", 0, "Expiry duration for cached remote Taskfiles.")
|
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, func() *time.Duration { return config.Remote.Timeout }, 0), "Expiry duration for cached remote Taskfiles.")
|
||||||
}
|
}
|
||||||
|
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,12 +196,16 @@ func Validate() error {
|
|||||||
return errors.New("task: --no-status only applies to --json with --list or --list-all")
|
return errors.New("task: --no-status only applies to --json with --list or --list-all")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Nested && !ListJson {
|
||||||
|
return errors.New("task: --nested only applies to --json with --list or --list-all")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFlags is a special internal functional option that is used to pass flags
|
// WithFlags is a special internal functional option that is used to pass flags
|
||||||
// from the CLI into any constructor that accepts functional options.
|
// from the CLI into any constructor that accepts functional options.
|
||||||
func WithFlags() *flagsOption {
|
func WithFlags() task.ExecutorOption {
|
||||||
return &flagsOption{}
|
return &flagsOption{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,8 +232,14 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
|||||||
|
|
||||||
e.Options(
|
e.Options(
|
||||||
task.WithDir(dir),
|
task.WithDir(dir),
|
||||||
|
task.WithEntrypoint(Entrypoint),
|
||||||
task.WithForce(Force),
|
task.WithForce(Force),
|
||||||
task.WithForceAll(ForceAll),
|
task.WithForceAll(ForceAll),
|
||||||
|
task.WithInsecure(Insecure),
|
||||||
|
task.WithDownload(Download),
|
||||||
|
task.WithOffline(Offline),
|
||||||
|
task.WithTimeout(Timeout),
|
||||||
|
task.WithCacheExpiryDuration(CacheExpiryDuration),
|
||||||
task.WithWatch(Watch),
|
task.WithWatch(Watch),
|
||||||
task.WithVerbose(Verbose),
|
task.WithVerbose(Verbose),
|
||||||
task.WithSilent(Silent),
|
task.WithSilent(Silent),
|
||||||
@@ -247,11 +256,15 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *flagsOption) ApplyToReader(r *taskfile.Reader) {
|
// getConfig extracts a config value directly from a pointer field with a fallback default
|
||||||
r.Options(
|
func getConfig[T any](config *taskrcast.TaskRC, fieldFunc func() *T, fallback T) T {
|
||||||
taskfile.WithInsecure(Insecure),
|
if config == nil {
|
||||||
taskfile.WithDownload(Download),
|
return fallback
|
||||||
taskfile.WithOffline(Offline),
|
}
|
||||||
taskfile.WithCacheExpiryDuration(CacheExpiryDuration),
|
|
||||||
)
|
field := fieldFunc()
|
||||||
|
if field != nil {
|
||||||
|
return *field
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,51 +37,87 @@ func DefaultDir(entrypoint, dir string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search will look for files with the given possible filenames using the given
|
// ResolveDir returns an absolute path to the directory that the task should be
|
||||||
// entrypoint and directory. If the entrypoint is set, it will check if the
|
// run in. If the entrypoint and dir are BOTH set, then the Taskfile will not
|
||||||
|
// sit inside the directory specified by dir and we should ensure that the dir
|
||||||
|
// is absolute. Otherwise, the dir will always be the parent directory of the
|
||||||
|
// resolved entrypoint, so we should return that parent directory.
|
||||||
|
func ResolveDir(entrypoint, resolvedEntrypoint, dir string) (string, error) {
|
||||||
|
if entrypoint != "" && dir != "" {
|
||||||
|
return filepath.Abs(dir)
|
||||||
|
}
|
||||||
|
return filepath.Dir(resolvedEntrypoint), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search looks for files with the given possible filenames using the given
|
||||||
|
// entrypoint and directory. If the entrypoint is set, it checks if the
|
||||||
// entrypoint matches a file or if it matches a directory containing one of the
|
// entrypoint matches a file or if it matches a directory containing one of the
|
||||||
// possible filenames. Otherwise, it will walk up the file tree starting at the
|
// possible filenames. Otherwise, it walks up the file tree starting at the
|
||||||
// given directory and perform a search in each directory for the possible
|
// given directory and performs a search in each directory for the possible
|
||||||
// filenames until it finds a match or reaches the root directory. If the
|
// filenames until it finds a match or reaches the root directory. If the
|
||||||
// entrypoint and directory are both empty, it will default the directory to the
|
// entrypoint and directory are both empty, it defaults the directory to the
|
||||||
// current working directory and perform a recursive search starting there. If a
|
// current working directory and performs a recursive search starting there. If
|
||||||
// match is found, the absolute path to the file will be returned with its
|
// a match is found, the absolute path to the file is returned with its
|
||||||
// directory. If no match is found, an error will be returned.
|
// directory. If no match is found, an error is returned.
|
||||||
func Search(entrypoint, dir string, possibleFilenames []string) (string, string, error) {
|
func Search(entrypoint, dir string, possibleFilenames []string) (string, error) {
|
||||||
var err error
|
var err error
|
||||||
if entrypoint != "" {
|
if entrypoint != "" {
|
||||||
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if dir == "" {
|
return entrypoint, nil
|
||||||
dir = filepath.Dir(entrypoint)
|
|
||||||
} else {
|
|
||||||
dir, err = filepath.Abs(dir)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entrypoint, dir, nil
|
|
||||||
}
|
}
|
||||||
if dir == "" {
|
if dir == "" {
|
||||||
dir, err = os.Getwd()
|
dir, err = os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entrypoint, err = SearchPathRecursively(dir, possibleFilenames)
|
entrypoint, err = SearchPathRecursively(dir, possibleFilenames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", err
|
||||||
}
|
}
|
||||||
dir = filepath.Dir(entrypoint)
|
return entrypoint, nil
|
||||||
return entrypoint, dir, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search will check if a file at the given path exists or not. If it does, it
|
// SearchAll looks for files with the given possible filenames using the given
|
||||||
// will return the path to it. If it does not, it will search for any files at
|
// entrypoint and directory. If the entrypoint is set, it checks if the
|
||||||
// the given path with any of the given possible names. If any of these match a
|
// entrypoint matches a file or if it matches a directory containing one of the
|
||||||
// file, the first matching path will be returned. If no files are found, an
|
// possible filenames and add it to a list of matches. It then walks up the file
|
||||||
|
// tree starting at the given directory and performs a search in each directory
|
||||||
|
// for the possible filenames until it finds a match or reaches the root
|
||||||
|
// directory. If the entrypoint and directory are both empty, it defaults the
|
||||||
|
// directory to the current working directory and performs a recursive search
|
||||||
|
// starting there. If matches are found, the absolute path to each file is added
|
||||||
|
// to the list and returned.
|
||||||
|
func SearchAll(entrypoint, dir string, possibleFilenames []string) ([]string, error) {
|
||||||
|
var err error
|
||||||
|
var entrypoints []string
|
||||||
|
if entrypoint != "" {
|
||||||
|
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entrypoints = append(entrypoints, entrypoint)
|
||||||
|
}
|
||||||
|
if dir == "" {
|
||||||
|
dir, err = os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paths, err := SearchNPathRecursively(dir, possibleFilenames, -1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append(entrypoints, paths...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchPath will check if a file at the given path exists or not. If it does,
|
||||||
|
// it will return the path to it. If it does not, it will search for any files
|
||||||
|
// at the given path with any of the given possible names. If any of these match
|
||||||
|
// a file, the first matching path will be returned. If no files are found, an
|
||||||
// error will be returned.
|
// error will be returned.
|
||||||
func SearchPath(path string, possibleFilenames []string) (string, error) {
|
func SearchPath(path string, possibleFilenames []string) (string, error) {
|
||||||
// Get file info about the path
|
// Get file info about the path
|
||||||
@@ -111,36 +147,56 @@ func SearchPath(path string, possibleFilenames []string) (string, error) {
|
|||||||
return "", os.ErrNotExist
|
return "", os.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchRecursively will check if a file at the given path exists by calling
|
// SearchPathRecursively walks up the directory tree starting at the given
|
||||||
// the exists function. If a file is not found, it will walk up the directory
|
// path, calling the Search function in each directory until it finds a matching
|
||||||
// tree calling the Search function until it finds a file or reaches the root
|
// file or reaches the root directory. On supported operating systems, it will
|
||||||
// directory. On supported operating systems, it will also check if the user ID
|
// also check if the user ID of the directory changes and abort if it does.
|
||||||
// of the directory changes and abort if it does.
|
|
||||||
func SearchPathRecursively(path string, possibleFilenames []string) (string, error) {
|
func SearchPathRecursively(path string, possibleFilenames []string) (string, error) {
|
||||||
owner, err := sysinfo.Owner(path)
|
paths, err := SearchNPathRecursively(path, possibleFilenames, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
for {
|
if len(paths) == 0 {
|
||||||
|
return "", os.ErrNotExist
|
||||||
|
}
|
||||||
|
return paths[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchNPathRecursively walks up the directory tree starting at the given
|
||||||
|
// path, calling the Search function in each directory and adding each matching
|
||||||
|
// file that it finds to a list until it reaches the root directory or the
|
||||||
|
// length of the list exceeds n. On supported operating systems, it will also
|
||||||
|
// check if the user ID of the directory changes and abort if it does.
|
||||||
|
func SearchNPathRecursively(path string, possibleFilenames []string, n int) ([]string, error) {
|
||||||
|
var paths []string
|
||||||
|
|
||||||
|
owner, err := sysinfo.Owner(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for n == -1 || len(paths) < n {
|
||||||
fpath, err := SearchPath(path, possibleFilenames)
|
fpath, err := SearchPath(path, possibleFilenames)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fpath, nil
|
paths = append(paths, fpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the parent path/user id
|
// Get the parent path/user id
|
||||||
parentPath := filepath.Dir(path)
|
parentPath := filepath.Dir(path)
|
||||||
parentOwner, err := sysinfo.Owner(parentPath)
|
parentOwner, err := sysinfo.Owner(parentPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error if we reached the root directory and still haven't found a file
|
// Error if we reached the root directory and still haven't found a file
|
||||||
// OR if the user id of the directory changes
|
// OR if the user id of the directory changes
|
||||||
if path == parentPath || (parentOwner != owner) {
|
if path == parentPath || (parentOwner != owner) {
|
||||||
return "", os.ErrNotExist
|
return paths, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
owner = parentOwner
|
owner = parentOwner
|
||||||
path = parentPath
|
path = parentPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return paths, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,35 +71,30 @@ func TestSearch(t *testing.T) {
|
|||||||
dir string
|
dir string
|
||||||
possibleFilenames []string
|
possibleFilenames []string
|
||||||
expectedEntrypoint string
|
expectedEntrypoint string
|
||||||
expectedDir string
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "find foo.txt using relative entrypoint",
|
name: "find foo.txt using relative entrypoint",
|
||||||
entrypoint: "./testdata/foo.txt",
|
entrypoint: "./testdata/foo.txt",
|
||||||
possibleFilenames: []string{"foo.txt"},
|
possibleFilenames: []string{"foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find foo.txt using absolute entrypoint",
|
name: "find foo.txt using absolute entrypoint",
|
||||||
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
possibleFilenames: []string{"foo.txt"},
|
possibleFilenames: []string{"foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find foo.txt using relative dir",
|
name: "find foo.txt using relative dir",
|
||||||
dir: "./testdata",
|
dir: "./testdata",
|
||||||
possibleFilenames: []string{"foo.txt"},
|
possibleFilenames: []string{"foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find foo.txt using absolute dir",
|
name: "find foo.txt using absolute dir",
|
||||||
dir: filepath.Join(wd, "testdata"),
|
dir: filepath.Join(wd, "testdata"),
|
||||||
possibleFilenames: []string{"foo.txt"},
|
possibleFilenames: []string{"foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find foo.txt using relative dir and relative entrypoint",
|
name: "find foo.txt using relative dir and relative entrypoint",
|
||||||
@@ -107,7 +102,6 @@ func TestSearch(t *testing.T) {
|
|||||||
dir: "./testdata/some/other/dir",
|
dir: "./testdata/some/other/dir",
|
||||||
possibleFilenames: []string{"foo.txt"},
|
possibleFilenames: []string{"foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find fs.go using no entrypoint or dir",
|
name: "find fs.go using no entrypoint or dir",
|
||||||
@@ -115,7 +109,6 @@ func TestSearch(t *testing.T) {
|
|||||||
dir: "",
|
dir: "",
|
||||||
possibleFilenames: []string{"fs.go"},
|
possibleFilenames: []string{"fs.go"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "fs.go"),
|
expectedEntrypoint: filepath.Join(wd, "fs.go"),
|
||||||
expectedDir: wd,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
||||||
@@ -123,30 +116,109 @@ func TestSearch(t *testing.T) {
|
|||||||
dir: "",
|
dir: "",
|
||||||
possibleFilenames: []string{"Taskfile.yml"},
|
possibleFilenames: []string{"Taskfile.yml"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
||||||
expectedDir: filepath.Join(wd, "..", ".."),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find foo.txt first if listed first in possible filenames",
|
name: "find foo.txt first if listed first in possible filenames",
|
||||||
entrypoint: "./testdata",
|
entrypoint: "./testdata",
|
||||||
possibleFilenames: []string{"foo.txt", "bar.txt"},
|
possibleFilenames: []string{"foo.txt", "bar.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "find bar.txt first if listed first in possible filenames",
|
name: "find bar.txt first if listed first in possible filenames",
|
||||||
entrypoint: "./testdata",
|
entrypoint: "./testdata",
|
||||||
possibleFilenames: []string{"bar.txt", "foo.txt"},
|
possibleFilenames: []string{"bar.txt", "foo.txt"},
|
||||||
expectedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
|
expectedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
entrypoint, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.expectedEntrypoint, entrypoint)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveDir(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
entrypoint string
|
||||||
|
resolvedEntrypoint string
|
||||||
|
dir string
|
||||||
|
expectedDir string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "find foo.txt using relative entrypoint",
|
||||||
|
entrypoint: "./testdata/foo.txt",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find foo.txt using absolute entrypoint",
|
||||||
|
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find foo.txt using relative dir",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
dir: "./testdata",
|
||||||
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find foo.txt using absolute dir",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
dir: filepath.Join(wd, "testdata"),
|
||||||
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find foo.txt using relative dir and relative entrypoint",
|
||||||
|
entrypoint: "./testdata/foo.txt",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
dir: "./testdata/some/other/dir",
|
||||||
|
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find fs.go using no entrypoint or dir",
|
||||||
|
entrypoint: "",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "fs.go"),
|
||||||
|
dir: "",
|
||||||
|
expectedDir: wd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
||||||
|
entrypoint: "",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
||||||
|
dir: "",
|
||||||
|
expectedDir: filepath.Join(wd, "..", ".."),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find foo.txt first if listed first in possible filenames",
|
||||||
|
entrypoint: "./testdata",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||||
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find bar.txt first if listed first in possible filenames",
|
||||||
|
entrypoint: "./testdata",
|
||||||
|
resolvedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
|
||||||
expectedDir: filepath.Join(wd, "testdata"),
|
expectedDir: filepath.Join(wd, "testdata"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
entrypoint, dir, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames)
|
dir, err := ResolveDir(tt.entrypoint, tt.resolvedEntrypoint, tt.dir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.expectedEntrypoint, entrypoint)
|
|
||||||
require.Equal(t, tt.expectedDir, dir)
|
require.Equal(t, tt.expectedDir, dir)
|
||||||
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3.44.1
|
3.45.0
|
||||||
|
|||||||
32
package-lock.json
generated
32
package-lock.json
generated
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@go-task/cli",
|
|
||||||
"version": "3.44.1",
|
|
||||||
"lockfileVersion": 2,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "@go-task/cli",
|
|
||||||
"version": "3.26.0",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@go-task/go-npm": "^0.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@go-task/go-npm": {
|
|
||||||
"version": "0.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.2.0.tgz",
|
|
||||||
"integrity": "sha512-vQbdtBvesHm8EUFHX8QKg4rbBodmu9VsAXH1ozpbiN5jdTMOYHTCMM31EurAYmY+rNNtxJQ4JGy6t383RPlqbw==",
|
|
||||||
"bin": {
|
|
||||||
"go-npm": "bin/index.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@go-task/go-npm": {
|
|
||||||
"version": "0.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.2.0.tgz",
|
|
||||||
"integrity": "sha512-vQbdtBvesHm8EUFHX8QKg4rbBodmu9VsAXH1ozpbiN5jdTMOYHTCMM31EurAYmY+rNNtxJQ4JGy6t383RPlqbw=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
package.json
34
package.json
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@go-task/cli",
|
|
||||||
"version": "3.44.1",
|
|
||||||
"description": "A task runner / simpler Make alternative written in Go",
|
|
||||||
"scripts": {
|
|
||||||
"postinstall": "go-npm install",
|
|
||||||
"preuninstall": "go-npm uninstall"
|
|
||||||
},
|
|
||||||
"goBinary": {
|
|
||||||
"name": "task",
|
|
||||||
"path": "./bin",
|
|
||||||
"url": "https://github.com/go-task/task/releases/download/v{{version}}/task_{{platform}}_{{arch}}{{archive_ext}}"
|
|
||||||
},
|
|
||||||
"files": [],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/go-task/task.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"task",
|
|
||||||
"taskfile",
|
|
||||||
"build-tool",
|
|
||||||
"task-runner"
|
|
||||||
],
|
|
||||||
"author": "The Task authors",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/go-task/task/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://taskfile.dev",
|
|
||||||
"dependencies": {
|
|
||||||
"@go-task/go-npm": "^0.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,18 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/sajari/fuzzy"
|
"github.com/sajari/fuzzy"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
|
"github.com/go-task/task/v3/internal/env"
|
||||||
|
"github.com/go-task/task/v3/internal/execext"
|
||||||
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
"github.com/go-task/task/v3/internal/output"
|
"github.com/go-task/task/v3/internal/output"
|
||||||
"github.com/go-task/task/v3/internal/version"
|
"github.com/go-task/task/v3/internal/version"
|
||||||
@@ -18,8 +23,18 @@ import (
|
|||||||
"github.com/go-task/task/v3/taskfile/ast"
|
"github.com/go-task/task/v3/taskfile/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e *Executor) setup() error {
|
func (e *Executor) Setup() error {
|
||||||
e.setupLogger()
|
e.setupLogger()
|
||||||
|
node, err := e.getRootNode()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := e.setupTempDir(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := e.readTaskfile(node); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
e.setupFuzzyModel()
|
e.setupFuzzyModel()
|
||||||
e.setupStdFiles()
|
e.setupStdFiles()
|
||||||
if err := e.setupOutput(); err != nil {
|
if err := e.setupOutput(); err != nil {
|
||||||
@@ -39,6 +54,46 @@ func (e *Executor) setup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Executor) getRootNode() (taskfile.Node, error) {
|
||||||
|
node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e.Dir = node.Dir()
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Executor) readTaskfile(node taskfile.Node) error {
|
||||||
|
ctx, cf := context.WithTimeout(context.Background(), e.Timeout)
|
||||||
|
defer cf()
|
||||||
|
debugFunc := func(s string) {
|
||||||
|
e.Logger.VerboseOutf(logger.Magenta, s)
|
||||||
|
}
|
||||||
|
promptFunc := func(s string) error {
|
||||||
|
return e.Logger.Prompt(logger.Yellow, s, "n", "y", "yes")
|
||||||
|
}
|
||||||
|
reader := taskfile.NewReader(
|
||||||
|
taskfile.WithInsecure(e.Insecure),
|
||||||
|
taskfile.WithDownload(e.Download),
|
||||||
|
taskfile.WithOffline(e.Offline),
|
||||||
|
taskfile.WithTempDir(e.TempDir.Remote),
|
||||||
|
taskfile.WithCacheExpiryDuration(e.CacheExpiryDuration),
|
||||||
|
taskfile.WithDebugFunc(debugFunc),
|
||||||
|
taskfile.WithPromptFunc(promptFunc),
|
||||||
|
)
|
||||||
|
graph, err := reader.Read(ctx, node)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
return &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: e.Timeout}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Taskfile, err = graph.Merge(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Executor) setupFuzzyModel() {
|
func (e *Executor) setupFuzzyModel() {
|
||||||
if e.Taskfile == nil {
|
if e.Taskfile == nil {
|
||||||
return
|
return
|
||||||
@@ -60,6 +115,52 @@ func (e *Executor) setupFuzzyModel() {
|
|||||||
e.fuzzyModel = model
|
e.fuzzyModel = model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Executor) setupTempDir() error {
|
||||||
|
if e.TempDir != (TempDir{}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tempDir := env.GetTaskEnv("TEMP_DIR")
|
||||||
|
if tempDir == "" {
|
||||||
|
e.TempDir = TempDir{
|
||||||
|
Remote: filepathext.SmartJoin(e.Dir, ".task"),
|
||||||
|
Fingerprint: filepathext.SmartJoin(e.Dir, ".task"),
|
||||||
|
}
|
||||||
|
} else if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") {
|
||||||
|
tempDir, err := execext.ExpandLiteral(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
projectDir, _ := filepath.Abs(e.Dir)
|
||||||
|
projectName := filepath.Base(projectDir)
|
||||||
|
e.TempDir = TempDir{
|
||||||
|
Remote: tempDir,
|
||||||
|
Fingerprint: filepathext.SmartJoin(tempDir, projectName),
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
e.TempDir = TempDir{
|
||||||
|
Remote: filepathext.SmartJoin(e.Dir, tempDir),
|
||||||
|
Fingerprint: filepathext.SmartJoin(e.Dir, tempDir),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteDir := env.GetTaskEnv("REMOTE_DIR")
|
||||||
|
if remoteDir != "" {
|
||||||
|
if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") {
|
||||||
|
remoteTempDir, err := execext.ExpandLiteral(remoteDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.TempDir.Remote = remoteTempDir
|
||||||
|
} else {
|
||||||
|
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Executor) setupStdFiles() {
|
func (e *Executor) setupStdFiles() {
|
||||||
if e.Stdin == nil {
|
if e.Stdin == nil {
|
||||||
e.Stdin = os.Stdin
|
e.Stdin = os.Stdin
|
||||||
@@ -105,7 +206,7 @@ func (e *Executor) setupCompiler() error {
|
|||||||
|
|
||||||
e.Compiler = &Compiler{
|
e.Compiler = &Compiler{
|
||||||
Dir: e.Dir,
|
Dir: e.Dir,
|
||||||
Entrypoint: e.Taskfile.Location,
|
Entrypoint: e.Entrypoint,
|
||||||
UserWorkingDir: e.UserWorkingDir,
|
UserWorkingDir: e.UserWorkingDir,
|
||||||
TaskfileEnv: e.Taskfile.Env,
|
TaskfileEnv: e.Taskfile.Env,
|
||||||
TaskfileVars: e.Taskfile.Vars,
|
TaskfileVars: e.Taskfile.Vars,
|
||||||
2
task.go
2
task.go
@@ -172,7 +172,6 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
|||||||
if t.Method != "" {
|
if t.Method != "" {
|
||||||
method = t.Method
|
method = t.Method
|
||||||
}
|
}
|
||||||
|
|
||||||
upToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
|
upToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
|
||||||
fingerprint.WithMethod(method),
|
fingerprint.WithMethod(method),
|
||||||
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
||||||
@@ -467,7 +466,6 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
|
|||||||
DidYouMean: didYouMean,
|
DidYouMean: didYouMean,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchingTask, nil
|
return matchingTask, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4521
task_test.go
4521
task_test.go
File diff suppressed because it is too large
Load Diff
@@ -46,17 +46,22 @@ type Task struct {
|
|||||||
Namespace string
|
Namespace string
|
||||||
IncludeVars *Vars
|
IncludeVars *Vars
|
||||||
IncludedTaskfileVars *Vars
|
IncludedTaskfileVars *Vars
|
||||||
|
|
||||||
|
FullName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) Name() string {
|
func (t *Task) Name() string {
|
||||||
if t.Label != "" {
|
if t.Label != "" {
|
||||||
return t.Label
|
return t.Label
|
||||||
}
|
}
|
||||||
|
if t.FullName != "" {
|
||||||
|
return t.FullName
|
||||||
|
}
|
||||||
return t.Task
|
return t.Task
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) LocalName() string {
|
func (t *Task) LocalName() string {
|
||||||
name := t.Task
|
name := t.FullName
|
||||||
name = strings.TrimPrefix(name, t.Namespace)
|
name = strings.TrimPrefix(name, t.Namespace)
|
||||||
name = strings.TrimPrefix(name, ":")
|
name = strings.TrimPrefix(name, ":")
|
||||||
return name
|
return name
|
||||||
@@ -220,6 +225,7 @@ func (t *Task) DeepCopy() *Task {
|
|||||||
Location: t.Location.DeepCopy(),
|
Location: t.Location.DeepCopy(),
|
||||||
Requires: t.Requires.DeepCopy(),
|
Requires: t.Requires.DeepCopy(),
|
||||||
Namespace: t.Namespace,
|
Namespace: t.Namespace,
|
||||||
|
FullName: t.FullName,
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ type Var struct {
|
|||||||
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||||
switch node.Kind {
|
switch node.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
key := node.Content[0].Value
|
key := "<none>"
|
||||||
|
if len(node.Content) > 0 {
|
||||||
|
key = node.Content[0].Value
|
||||||
|
}
|
||||||
switch key {
|
switch key {
|
||||||
case "sh", "ref", "map":
|
case "sh", "ref", "map":
|
||||||
var m struct {
|
var m struct {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package taskfile
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
giturls "github.com/chainguard-dev/git-urls"
|
giturls "github.com/chainguard-dev/git-urls"
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ func NewRootNode(
|
|||||||
entrypoint string,
|
entrypoint string,
|
||||||
dir string,
|
dir string,
|
||||||
insecure bool,
|
insecure bool,
|
||||||
|
timeout time.Duration,
|
||||||
) (Node, error) {
|
) (Node, error) {
|
||||||
dir = fsext.DefaultDir(entrypoint, dir)
|
dir = fsext.DefaultDir(entrypoint, dir)
|
||||||
// If the entrypoint is "-", we read from stdin
|
// If the entrypoint is "-", we read from stdin
|
||||||
|
|||||||
@@ -18,15 +18,21 @@ type FileNode struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
|
func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
|
||||||
var err error
|
// Find the entrypoint file
|
||||||
base := NewBaseNode(dir, opts...)
|
resolvedEntrypoint, err := fsext.Search(entrypoint, dir, defaultTaskfiles)
|
||||||
entrypoint, base.dir, err = fsext.Search(entrypoint, base.dir, defaultTaskfiles)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve the directory
|
||||||
|
resolvedDir, err := fsext.ResolveDir(entrypoint, resolvedEntrypoint, dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &FileNode{
|
return &FileNode{
|
||||||
baseNode: base,
|
baseNode: NewBaseNode(resolvedDir, opts...),
|
||||||
entrypoint: entrypoint,
|
entrypoint: resolvedEntrypoint,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,48 @@
|
|||||||
package ast
|
package ast
|
||||||
|
|
||||||
import "github.com/Masterminds/semver/v3"
|
import (
|
||||||
|
"cmp"
|
||||||
|
"maps"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
)
|
||||||
|
|
||||||
type TaskRC struct {
|
type TaskRC struct {
|
||||||
Version *semver.Version `yaml:"version"`
|
Version *semver.Version `yaml:"version"`
|
||||||
|
Verbose *bool `yaml:"verbose"`
|
||||||
|
Concurrency *int `yaml:"concurrency"`
|
||||||
|
Remote Remote `yaml:"remote"`
|
||||||
Experiments map[string]int `yaml:"experiments"`
|
Experiments map[string]int `yaml:"experiments"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Remote struct {
|
||||||
|
Insecure *bool `yaml:"insecure"`
|
||||||
|
Offline *bool `yaml:"offline"`
|
||||||
|
Timeout *time.Duration `yaml:"timeout"`
|
||||||
|
CacheExpiry *time.Duration `yaml:"cache-expiry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge combines the current TaskRC with another TaskRC, prioritizing non-nil fields from the other TaskRC.
|
||||||
|
func (t *TaskRC) Merge(other *TaskRC) {
|
||||||
|
if other == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Version = cmp.Or(other.Version, t.Version)
|
||||||
|
|
||||||
|
if t.Experiments == nil && other.Experiments != nil {
|
||||||
|
t.Experiments = other.Experiments
|
||||||
|
} else if t.Experiments != nil && other.Experiments != nil {
|
||||||
|
maps.Copy(t.Experiments, other.Experiments)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge Remote fields
|
||||||
|
t.Remote.Insecure = cmp.Or(other.Remote.Insecure, t.Remote.Insecure)
|
||||||
|
t.Remote.Offline = cmp.Or(other.Remote.Offline, t.Remote.Offline)
|
||||||
|
t.Remote.Timeout = cmp.Or(other.Remote.Timeout, t.Remote.Timeout)
|
||||||
|
t.Remote.CacheExpiry = cmp.Or(other.Remote.CacheExpiry, t.Remote.CacheExpiry)
|
||||||
|
|
||||||
|
t.Verbose = cmp.Or(other.Verbose, t.Verbose)
|
||||||
|
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
package taskrc
|
package taskrc
|
||||||
|
|
||||||
import "github.com/go-task/task/v3/internal/fsext"
|
import (
|
||||||
|
"github.com/go-task/task/v3/internal/fsext"
|
||||||
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
entrypoint string
|
entrypoint string
|
||||||
dir string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNode(
|
func NewNode(
|
||||||
entrypoint string,
|
entrypoint string,
|
||||||
dir string,
|
dir string,
|
||||||
|
possibleFileNames []string,
|
||||||
) (*Node, error) {
|
) (*Node, error) {
|
||||||
dir = fsext.DefaultDir(entrypoint, dir)
|
dir = fsext.DefaultDir(entrypoint, dir)
|
||||||
var err error
|
resolvedEntrypoint, err := fsext.SearchPath(dir, possibleFileNames)
|
||||||
entrypoint, dir, err = fsext.Search(entrypoint, dir, defaultTaskRCs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Node{
|
return &Node{
|
||||||
entrypoint: entrypoint,
|
entrypoint: resolvedEntrypoint,
|
||||||
dir: dir,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,89 @@
|
|||||||
package taskrc
|
package taskrc
|
||||||
|
|
||||||
var defaultTaskRCs = []string{
|
import (
|
||||||
".taskrc.yml",
|
"os"
|
||||||
".taskrc.yaml",
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/fsext"
|
||||||
|
"github.com/go-task/task/v3/taskrc/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultXDGTaskRCs = []string{
|
||||||
|
"taskrc.yml",
|
||||||
|
"taskrc.yaml",
|
||||||
|
}
|
||||||
|
defaultTaskRCs = []string{
|
||||||
|
".taskrc.yml",
|
||||||
|
".taskrc.yaml",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetConfig loads and merges local and global Task configuration files
|
||||||
|
func GetConfig(dir string) (*ast.TaskRC, error) {
|
||||||
|
var config *ast.TaskRC
|
||||||
|
reader := NewReader()
|
||||||
|
|
||||||
|
// Read the XDG config file
|
||||||
|
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
|
||||||
|
xdgConfigNode, err := NewNode("", filepath.Join(xdgConfigHome, "task"), defaultXDGTaskRCs)
|
||||||
|
if err == nil && xdgConfigNode != nil {
|
||||||
|
xdgConfig, err := reader.Read(xdgConfigNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config = xdgConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the current path does not contain $HOME
|
||||||
|
// If it does contain $HOME, then we will find this config later anyway
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err == nil && !strings.Contains(home, dir) {
|
||||||
|
homeNode, err := NewNode("", home, defaultTaskRCs)
|
||||||
|
if err == nil && homeNode != nil {
|
||||||
|
homeConfig, err := reader.Read(homeNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
config = homeConfig
|
||||||
|
} else {
|
||||||
|
config.Merge(homeConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all the nodes from the given directory up to the users home directory
|
||||||
|
entrypoints, err := fsext.SearchAll("", dir, defaultTaskRCs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the entrypoints since we want the child files to override parent ones
|
||||||
|
slices.Reverse(entrypoints)
|
||||||
|
|
||||||
|
// Loop over the nodes, and merge them into the main config
|
||||||
|
for _, entrypoint := range entrypoints {
|
||||||
|
node, err := NewNode("", entrypoint, defaultTaskRCs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
localConfig, err := reader.Read(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if localConfig == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
config = localConfig
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
config.Merge(localConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|||||||
137
taskrc/taskrc_test.go
Normal file
137
taskrc/taskrc_test.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package taskrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/taskrc/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
xdgConfigYAML = `
|
||||||
|
experiments:
|
||||||
|
FOO: 1
|
||||||
|
BAR: 1
|
||||||
|
BAZ: 1
|
||||||
|
`
|
||||||
|
|
||||||
|
homeConfigYAML = `
|
||||||
|
experiments:
|
||||||
|
FOO: 2
|
||||||
|
BAR: 2
|
||||||
|
`
|
||||||
|
|
||||||
|
localConfigYAML = `
|
||||||
|
experiments:
|
||||||
|
FOO: 3
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupDirs(t *testing.T) (string, string, string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
xdgConfigDir := t.TempDir()
|
||||||
|
xdgTaskConfigDir := filepath.Join(xdgConfigDir, "task")
|
||||||
|
require.NoError(t, os.Mkdir(xdgTaskConfigDir, 0o755))
|
||||||
|
|
||||||
|
homeDir := t.TempDir()
|
||||||
|
|
||||||
|
localDir := filepath.Join(homeDir, "local")
|
||||||
|
require.NoError(t, os.Mkdir(localDir, 0o755))
|
||||||
|
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", xdgConfigDir)
|
||||||
|
t.Setenv("HOME", homeDir)
|
||||||
|
|
||||||
|
return xdgTaskConfigDir, homeDir, localDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFile(t *testing.T, dir, filename, content string) {
|
||||||
|
t.Helper()
|
||||||
|
err := os.WriteFile(filepath.Join(dir, filename), []byte(content), 0o644)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfig_NoConfigFiles(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||||
|
_, _, localDir := setupDirs(t)
|
||||||
|
|
||||||
|
cfg, err := GetConfig(localDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfig_OnlyXDG(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||||
|
xdgDir, _, localDir := setupDirs(t)
|
||||||
|
|
||||||
|
writeFile(t, xdgDir, "taskrc.yml", xdgConfigYAML)
|
||||||
|
|
||||||
|
cfg, err := GetConfig(localDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &ast.TaskRC{
|
||||||
|
Version: nil,
|
||||||
|
Experiments: map[string]int{
|
||||||
|
"FOO": 1,
|
||||||
|
"BAR": 1,
|
||||||
|
"BAZ": 1,
|
||||||
|
},
|
||||||
|
}, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfig_OnlyHome(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||||
|
_, homeDir, localDir := setupDirs(t)
|
||||||
|
|
||||||
|
writeFile(t, homeDir, ".taskrc.yml", homeConfigYAML)
|
||||||
|
|
||||||
|
cfg, err := GetConfig(localDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &ast.TaskRC{
|
||||||
|
Version: nil,
|
||||||
|
Experiments: map[string]int{
|
||||||
|
"FOO": 2,
|
||||||
|
"BAR": 2,
|
||||||
|
},
|
||||||
|
}, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfig_OnlyLocal(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||||
|
_, _, localDir := setupDirs(t)
|
||||||
|
|
||||||
|
writeFile(t, localDir, ".taskrc.yml", localConfigYAML)
|
||||||
|
|
||||||
|
cfg, err := GetConfig(localDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &ast.TaskRC{
|
||||||
|
Version: nil,
|
||||||
|
Experiments: map[string]int{
|
||||||
|
"FOO": 3,
|
||||||
|
},
|
||||||
|
}, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfig_All(t *testing.T) { //nolint:paralleltest // cannot run in parallel
|
||||||
|
xdgConfigDir, homeDir, localDir := setupDirs(t)
|
||||||
|
|
||||||
|
// Write local config
|
||||||
|
writeFile(t, localDir, ".taskrc.yml", localConfigYAML)
|
||||||
|
|
||||||
|
// Write home config
|
||||||
|
writeFile(t, homeDir, ".taskrc.yml", homeConfigYAML)
|
||||||
|
|
||||||
|
// Write XDG config
|
||||||
|
writeFile(t, xdgConfigDir, "taskrc.yml", xdgConfigYAML)
|
||||||
|
|
||||||
|
cfg, err := GetConfig(localDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, cfg)
|
||||||
|
assert.Equal(t, &ast.TaskRC{
|
||||||
|
Version: nil,
|
||||||
|
Experiments: map[string]int{
|
||||||
|
"FOO": 3,
|
||||||
|
"BAR": 2,
|
||||||
|
"BAZ": 1,
|
||||||
|
},
|
||||||
|
}, cfg)
|
||||||
|
}
|
||||||
78
temp_dir.go
78
temp_dir.go
@@ -1,78 +0,0 @@
|
|||||||
package task
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/env"
|
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TempDir struct {
|
|
||||||
Remote string
|
|
||||||
Fingerprint string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTempDir(dir string) (*TempDir, error) {
|
|
||||||
tempDir, err := setupTempDirFingerprint(dir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = setupTempDirRemote(dir, tempDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tempDir, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupTempDirFingerprint(dir string) (*TempDir, error) {
|
|
||||||
tempDir := env.GetTaskEnv("TEMP_DIR")
|
|
||||||
|
|
||||||
if tempDir == "" {
|
|
||||||
return &TempDir{
|
|
||||||
Remote: filepathext.SmartJoin(dir, ".task"),
|
|
||||||
Fingerprint: filepathext.SmartJoin(dir, ".task"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") {
|
|
||||||
tempDir, err := execext.ExpandLiteral(tempDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
projectDir, _ := filepath.Abs(dir)
|
|
||||||
projectName := filepath.Base(projectDir)
|
|
||||||
return &TempDir{
|
|
||||||
Remote: tempDir,
|
|
||||||
Fingerprint: filepathext.SmartJoin(tempDir, projectName),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TempDir{
|
|
||||||
Remote: filepathext.SmartJoin(dir, tempDir),
|
|
||||||
Fingerprint: filepathext.SmartJoin(dir, tempDir),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupTempDirRemote(dir string, tempDir *TempDir) error {
|
|
||||||
remoteDir := env.GetTaskEnv("REMOTE_DIR")
|
|
||||||
|
|
||||||
if remoteDir == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") {
|
|
||||||
remoteTempDir, err := execext.ExpandLiteral(remoteDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tempDir.Remote = remoteTempDir
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDir.Remote = filepathext.SmartJoin(dir, ".task")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
8
testdata/checksum/Taskfile.yml
vendored
8
testdata/checksum/Taskfile.yml
vendored
@@ -12,6 +12,14 @@ tasks:
|
|||||||
generates:
|
generates:
|
||||||
- ./generated.txt
|
- ./generated.txt
|
||||||
method: checksum
|
method: checksum
|
||||||
|
build-*:
|
||||||
|
cmds:
|
||||||
|
- cp ./source.txt ./generated-{{index .MATCH 0}}.txt
|
||||||
|
sources:
|
||||||
|
- ./source.txt
|
||||||
|
generates:
|
||||||
|
- ./generated-{{index .MATCH 0}}.txt
|
||||||
|
method: checksum
|
||||||
|
|
||||||
build-with-status:
|
build-with-status:
|
||||||
cmds:
|
cmds:
|
||||||
|
|||||||
1
testdata/checksum/generated-wildcard.txt
vendored
Normal file
1
testdata/checksum/generated-wildcard.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello, World!
|
||||||
11
testdata/run/Taskfile.yml
vendored
11
testdata/run/Taskfile.yml
vendored
@@ -22,3 +22,14 @@ tasks:
|
|||||||
run: once
|
run: once
|
||||||
cmds:
|
cmds:
|
||||||
- echo starting {{.CONTENT}} >> hash.txt
|
- echo starting {{.CONTENT}} >> hash.txt
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
cmds:
|
||||||
|
- rm -rf wildcard.txt
|
||||||
|
- task: deploy:infra
|
||||||
|
- task: deploy:js
|
||||||
|
- task: deploy:go
|
||||||
|
|
||||||
|
deploy:*:
|
||||||
|
run: once
|
||||||
|
cmd: echo "Deploy {{index .MATCH 0}}" >> wildcard.txt
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
Hello foo
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Hello foo bar
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
I don't consume matches: []
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Hello foo-bar
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
task: Task "no-match" does not exist
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
task: No tasks with description available. Try --list-all to list all tasks
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Starting foo
|
|
||||||
@@ -44,9 +44,14 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
fullName := origTask.Task
|
||||||
|
if matches, exists := vars.Get("MATCH"); exists {
|
||||||
|
for _, match := range matches.Value.([]string) {
|
||||||
|
fullName = strings.Replace(fullName, "*", match, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cache := &templater.Cache{Vars: vars}
|
cache := &templater.Cache{Vars: vars}
|
||||||
|
|
||||||
new := ast.Task{
|
new := ast.Task{
|
||||||
Task: origTask.Task,
|
Task: origTask.Task,
|
||||||
Label: templater.Replace(origTask.Label, cache),
|
Label: templater.Replace(origTask.Label, cache),
|
||||||
@@ -76,6 +81,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
|||||||
Requires: origTask.Requires,
|
Requires: origTask.Requires,
|
||||||
Watch: origTask.Watch,
|
Watch: origTask.Watch,
|
||||||
Namespace: origTask.Namespace,
|
Namespace: origTask.Namespace,
|
||||||
|
FullName: fullName,
|
||||||
}
|
}
|
||||||
new.Dir, err = execext.ExpandLiteral(new.Dir)
|
new.Dir, err = execext.ExpandLiteral(new.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
18
website/.gitignore
vendored
18
website/.gitignore
vendored
@@ -1,21 +1,9 @@
|
|||||||
# Dependencies
|
# Dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
|
|
||||||
# Production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.docusaurus
|
|
||||||
.cache-loader
|
|
||||||
i18n
|
|
||||||
|
|
||||||
# Misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
.vitepress/cache
|
||||||
|
.vitepress/dist
|
||||||
|
.task/
|
||||||
|
|||||||
1
website/.prettierignore
Normal file
1
website/.prettierignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pnpm-lock.yaml
|
||||||
5
website/.vitepress/components.d.ts
vendored
Normal file
5
website/.vitepress/components.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue';
|
||||||
|
const component: DefineComponent<{}, {}, any>;
|
||||||
|
export default component;
|
||||||
|
}
|
||||||
97
website/.vitepress/components/AuthorCard.vue
Normal file
97
website/.vitepress/components/AuthorCard.vue
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<div class="author-compact" v-if="author">
|
||||||
|
<img :src="author.avatar" :alt="author.name" class="author-avatar" />
|
||||||
|
<div class="author-info">
|
||||||
|
<div class="author-name-line">
|
||||||
|
<span class="author-name">{{ author.name }}</span>
|
||||||
|
|
||||||
|
<div class="author-socials">
|
||||||
|
<a
|
||||||
|
v-for="{ link, icon } in author.links"
|
||||||
|
:key="link"
|
||||||
|
:href="link"
|
||||||
|
target="_blank"
|
||||||
|
class="social-link"
|
||||||
|
>
|
||||||
|
<span :class="`vpi-social-${icon}`"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="author-bio">{{ author.title }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { team } from '../team.ts';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
const props = defineProps({
|
||||||
|
author: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const author = computed(() => {
|
||||||
|
return team.find((m) => m.slug === props.author) || null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.author-compact {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.1rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-bio {
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author-socials {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-link {
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
transition: color 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-link:hover {
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.author-compact {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
182
website/.vitepress/components/BlogPost.vue
Normal file
182
website/.vitepress/components/BlogPost.vue
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<template>
|
||||||
|
<article class="blog-post">
|
||||||
|
<div class="post-header">
|
||||||
|
<h3 class="post-title">
|
||||||
|
<a :href="url">{{ title }}</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="post-meta">
|
||||||
|
<time :datetime="date" class="post-date">
|
||||||
|
{{ formatDate(date) }}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="post-content">
|
||||||
|
<div class="post-image" v-if="image">
|
||||||
|
<img :src="image" :alt="title" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="post-text">
|
||||||
|
<AuthorCard :author="author" />
|
||||||
|
|
||||||
|
<p class="post-description">{{ description }}</p>
|
||||||
|
|
||||||
|
<div class="post-footer">
|
||||||
|
<div class="post-tags" v-if="tags?.length">
|
||||||
|
<strong>Tags:</strong>
|
||||||
|
<code v-for="tag in tags" :key="tag" class="post-tag">{{
|
||||||
|
tag
|
||||||
|
}}</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a :href="url" class="read-more"> Read more → </a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import AuthorCard from './AuthorCard.vue';
|
||||||
|
const props = defineProps({
|
||||||
|
title: String,
|
||||||
|
url: String,
|
||||||
|
date: String,
|
||||||
|
author: String,
|
||||||
|
description: String,
|
||||||
|
tags: Array,
|
||||||
|
image: String
|
||||||
|
});
|
||||||
|
|
||||||
|
function formatDate(date) {
|
||||||
|
return new Date(date).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.blog-post {
|
||||||
|
border-bottom: 1px solid var(--vp-c-divider);
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title a {
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title a:hover {
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-date {
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
object-fit: cover;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-description {
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-tags {
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-tag {
|
||||||
|
background: var(--vp-c-default-soft);
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
font-family: var(--vp-font-family-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-more {
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid var(--vp-c-brand-1);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-more:hover {
|
||||||
|
background: var(--vp-c-brand-1);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.post-content {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
website/.vitepress/components/HomePage.vue
Normal file
20
website/.vitepress/components/HomePage.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { VPHomeSponsors } from 'vitepress/theme';
|
||||||
|
import { sponsors } from '../sponsors';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="content">
|
||||||
|
<div class="content-container">
|
||||||
|
<main class="main">
|
||||||
|
<VPHomeSponsors
|
||||||
|
v-if="sponsors"
|
||||||
|
message="Task is free and open source, made possible by wonderful sponsors."
|
||||||
|
:data="sponsors"
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
245
website/.vitepress/components/VPTeamMembersItem.vue
Normal file
245
website/.vitepress/components/VPTeamMembersItem.vue
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DefaultTheme } from 'vitepress/theme';
|
||||||
|
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
|
||||||
|
import VPSocialLinks from 'vitepress/dist/client/theme-default/components/VPSocialLinks.vue';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: 'small' | 'medium';
|
||||||
|
member: TeamMember;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TeamMember extends DefaultTheme.TeamMember {
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
size: 'medium'
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<article class="VPTeamMembersItem" :class="[size]">
|
||||||
|
<div class="profile">
|
||||||
|
<figure class="avatar">
|
||||||
|
<img class="avatar-img" :src="member.avatar" :alt="member.name" />
|
||||||
|
</figure>
|
||||||
|
<div class="data">
|
||||||
|
<h1 class="name">
|
||||||
|
<img :src="member.icon" alt="profile-icon" />
|
||||||
|
{{ member.name }}
|
||||||
|
</h1>
|
||||||
|
<p v-if="member.title || member.org" class="affiliation">
|
||||||
|
<span v-if="member.title" class="title">
|
||||||
|
{{ member.title }}
|
||||||
|
</span>
|
||||||
|
<span v-if="member.title && member.org" class="at"> @ </span>
|
||||||
|
<VPLink
|
||||||
|
v-if="member.org"
|
||||||
|
class="org"
|
||||||
|
:class="{ link: member.orgLink }"
|
||||||
|
:href="member.orgLink"
|
||||||
|
no-icon
|
||||||
|
>
|
||||||
|
{{ member.org }}
|
||||||
|
</VPLink>
|
||||||
|
</p>
|
||||||
|
<p v-if="member.desc" class="desc" v-html="member.desc" />
|
||||||
|
<div v-if="member.links" class="links">
|
||||||
|
<VPSocialLinks :links="member.links" :me="false" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="member.sponsor" class="sp">
|
||||||
|
<VPLink class="sp-link" :href="member.sponsor" no-icon>
|
||||||
|
<span class="vpi-heart sp-icon" /> {{ member.actionText || 'Sponsor' }}
|
||||||
|
</VPLink>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPTeamMembersItem {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .profile {
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .data {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .avatar {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .name {
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .affiliation {
|
||||||
|
padding-top: 4px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .desc {
|
||||||
|
padding-top: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .links {
|
||||||
|
margin: 0 -16px -20px;
|
||||||
|
padding: 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .profile {
|
||||||
|
padding: 48px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .data {
|
||||||
|
padding-top: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .avatar {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .name {
|
||||||
|
letter-spacing: 0.15px;
|
||||||
|
line-height: 28px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .affiliation {
|
||||||
|
padding-top: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .desc {
|
||||||
|
padding-top: 16px;
|
||||||
|
max-width: 288px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .links {
|
||||||
|
margin: 0 -16px -12px;
|
||||||
|
padding: 16px 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem .profile .name {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem .profile .name img {
|
||||||
|
display: inline-block;
|
||||||
|
height: 22px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile {
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: var(--vp-c-bg-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: var(--vp-shadow-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.affiliation {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.org.link {
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
transition: color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org.link:hover {
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc :deep(a) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
text-decoration-style: dotted;
|
||||||
|
transition: color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-link {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-sponsor);
|
||||||
|
background-color: var(--vp-c-bg-soft);
|
||||||
|
transition:
|
||||||
|
color 0.25s,
|
||||||
|
background-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp .sp-link.link:hover,
|
||||||
|
.sp .sp-link.link:focus {
|
||||||
|
outline: none;
|
||||||
|
color: var(--vp-c-white);
|
||||||
|
background-color: var(--vp-c-sponsor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
website/.vitepress/components/Version.vue
Normal file
7
website/.vitepress/components/Version.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { VPBadge } from 'vitepress/theme';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VPBadge type="info"> <slot />+ </VPBadge>
|
||||||
|
</template>
|
||||||
348
website/.vitepress/config.ts
Normal file
348
website/.vitepress/config.ts
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
import { defineConfig } from 'vitepress';
|
||||||
|
import githubLinksPlugin from './plugins/github-links';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs';
|
||||||
|
import {
|
||||||
|
groupIconMdPlugin,
|
||||||
|
groupIconVitePlugin,
|
||||||
|
localIconLoader
|
||||||
|
} from 'vitepress-plugin-group-icons';
|
||||||
|
import { team } from './team.ts';
|
||||||
|
import { taskDescription, taskName } from './meta.ts';
|
||||||
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
|
|
||||||
|
const version = readFileSync(
|
||||||
|
resolve(__dirname, '../../internal/version/version.txt'),
|
||||||
|
'utf8'
|
||||||
|
).trim();
|
||||||
|
|
||||||
|
const urlVersion =
|
||||||
|
process.env.NODE_ENV === 'development'
|
||||||
|
? {
|
||||||
|
current: 'https://taskfile.dev/',
|
||||||
|
next: 'http://localhost:3002/'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
current: 'https://taskfile.dev/',
|
||||||
|
next: 'https://next.taskfile.dev/'
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://vitepress.dev/reference/site-config
|
||||||
|
export default defineConfig({
|
||||||
|
title: taskName,
|
||||||
|
description: taskDescription,
|
||||||
|
lang: 'en-US',
|
||||||
|
head: [
|
||||||
|
[
|
||||||
|
'link',
|
||||||
|
{
|
||||||
|
rel: 'icon',
|
||||||
|
type: 'image/x-icon',
|
||||||
|
href: '/img/favicon.icon',
|
||||||
|
sizes: '48x48'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'link',
|
||||||
|
{
|
||||||
|
rel: 'icon',
|
||||||
|
sizes: 'any',
|
||||||
|
type: 'image/svg+xml',
|
||||||
|
href: '/img/logo.svg'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'link',
|
||||||
|
{
|
||||||
|
rel: 'canonical',
|
||||||
|
href: 'https://taskfile.dev/'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'meta',
|
||||||
|
{ name: 'author', content: `${team.map((c) => c.name).join(', ')}` }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'meta',
|
||||||
|
{
|
||||||
|
name: 'keywords',
|
||||||
|
content:
|
||||||
|
'task runner, build tool, taskfile, yaml build tool, go task runner, make alternative, cross-platform build tool, makefile alternative, automation tool, ci cd pipeline, developer productivity, build automation, command line tool, go binary, yaml configuration'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'script',
|
||||||
|
{
|
||||||
|
async: '',
|
||||||
|
src: 'https://www.googletagmanager.com/gtag/js?id=G-4RT25NXQ7N'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'script',
|
||||||
|
{},
|
||||||
|
`window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag("js", new Date());
|
||||||
|
gtag("config", "G-4RT25NXQ7N");`
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"script",
|
||||||
|
{
|
||||||
|
defer: "",
|
||||||
|
src: "https://umami.taskfile.dev/script.js",
|
||||||
|
"data-website-id": "084030b0-0e3f-4891-8d2a-0c12c40f5933"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
srcDir: 'src',
|
||||||
|
cleanUrls: true,
|
||||||
|
markdown: {
|
||||||
|
config: (md) => {
|
||||||
|
md.use(githubLinksPlugin, {
|
||||||
|
baseUrl: 'https://github.com',
|
||||||
|
repo: 'go-task/task'
|
||||||
|
});
|
||||||
|
md.use(tabsMarkdownPlugin);
|
||||||
|
md.use(groupIconMdPlugin);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
plugins: [
|
||||||
|
groupIconVitePlugin({
|
||||||
|
customIcon: {
|
||||||
|
'.taskrc.yml': localIconLoader(
|
||||||
|
import.meta.url,
|
||||||
|
'./theme/icons/task.svg'
|
||||||
|
),
|
||||||
|
'Taskfile.yml': localIconLoader(
|
||||||
|
import.meta.url,
|
||||||
|
'./theme/icons/task.svg'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
find: /^.*\/VPTeamMembersItem\.vue$/,
|
||||||
|
replacement: fileURLToPath(
|
||||||
|
new URL('./components/VPTeamMembersItem.vue', import.meta.url)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
themeConfig: {
|
||||||
|
logo: '/img/logo.svg',
|
||||||
|
carbonAds: {
|
||||||
|
code: 'CESI65QJ',
|
||||||
|
placement: 'taskfiledev'
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
provider: 'algolia',
|
||||||
|
options: {
|
||||||
|
appId: '7IZIJ13AI7',
|
||||||
|
apiKey: '34b64ae4fc8d9da43d9a13d9710aaddc',
|
||||||
|
indexName: 'taskfile'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nav: [
|
||||||
|
{ text: 'Home', link: '/' },
|
||||||
|
{
|
||||||
|
text: 'Docs',
|
||||||
|
link: '/docs/guide',
|
||||||
|
activeMatch: '^/docs'
|
||||||
|
},
|
||||||
|
{ text: 'Blog', link: '/blog', activeMatch: '^/blog' },
|
||||||
|
{ text: 'Donate', link: '/donate' },
|
||||||
|
{ text: 'Team', link: '/team' },
|
||||||
|
{
|
||||||
|
text: process.env.NODE_ENV === 'development' ? 'Next' : `v${version}`,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: `v${version}`,
|
||||||
|
link: urlVersion.current
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Next',
|
||||||
|
link: urlVersion.next
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
sidebar: {
|
||||||
|
'/blog/': [
|
||||||
|
{
|
||||||
|
text: '2025',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Built-in Core Utilities',
|
||||||
|
link: '/blog/windows-core-utils'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '2024',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Any Variables',
|
||||||
|
link: '/blog/any-variables'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '2023',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Introducing Experiments',
|
||||||
|
link: '/blog/task-in-2023'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'/': [
|
||||||
|
{
|
||||||
|
text: 'Installation',
|
||||||
|
link: '/docs/installation'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Getting Started',
|
||||||
|
link: '/docs/getting-started'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Guide',
|
||||||
|
link: '/docs/guide'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Reference',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Taskfile Schema',
|
||||||
|
link: '/docs/reference/schema'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Environment',
|
||||||
|
link: '/docs/reference/environment'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Configuration',
|
||||||
|
link: '/docs/reference/config'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'CLI',
|
||||||
|
link: '/docs/reference/cli'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Templating',
|
||||||
|
link: '/docs/reference/templating'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Package API',
|
||||||
|
link: '/docs/reference/package'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Experiments',
|
||||||
|
collapsed: true,
|
||||||
|
link: '/docs/experiments/',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Env Precedence (#1038)',
|
||||||
|
link: '/docs/experiments/env-precedence'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Gentle Force (#1200)',
|
||||||
|
link: '/docs/experiments/gentle-force'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Remote Taskfiles (#1317)',
|
||||||
|
link: '/docs/experiments/remote-taskfiles'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Deprecations',
|
||||||
|
collapsed: true,
|
||||||
|
link: '/docs/deprecations/',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Completion Scripts',
|
||||||
|
link: '/docs/deprecations/completion-scripts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Template Functions',
|
||||||
|
link: '/docs/deprecations/template-functions'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Version 2 Schema (#1197)',
|
||||||
|
link: '/docs/deprecations/version-2-schema'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Taskfile Versions',
|
||||||
|
link: '/docs/taskfile-versions'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Integrations',
|
||||||
|
link: '/docs/integrations'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Community',
|
||||||
|
link: '/docs/community'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Style Guide',
|
||||||
|
link: '/docs/styleguide'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Contributing',
|
||||||
|
link: '/docs/contributing'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Releasing',
|
||||||
|
link: '/docs/releasing'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Changelog',
|
||||||
|
link: '/docs/changelog'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'FAQ',
|
||||||
|
link: '/docs/faq'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Hacky to disable sidebar for these pages
|
||||||
|
'/donate': [],
|
||||||
|
'/team': []
|
||||||
|
},
|
||||||
|
|
||||||
|
socialLinks: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/go-task/task' },
|
||||||
|
{ icon: 'discord', link: 'https://discord.gg/6TY36E39UK' },
|
||||||
|
{ icon: 'x', link: 'https://twitter.com/taskfiledev' },
|
||||||
|
{ icon: 'bluesky', link: 'https://bsky.app/profile/taskfile.dev' },
|
||||||
|
{ icon: 'mastodon', link: 'https://fosstodon.org/@task' }
|
||||||
|
],
|
||||||
|
|
||||||
|
footer: {
|
||||||
|
message:
|
||||||
|
'Built with <a target="_blank" href="https://www.netlify.com">Netlify</a>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sitemap: {
|
||||||
|
hostname: 'https://taskfile.dev'
|
||||||
|
}
|
||||||
|
});
|
||||||
5
website/.vitepress/meta.ts
Normal file
5
website/.vitepress/meta.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const taskName = 'Task';
|
||||||
|
export const taskDescription =
|
||||||
|
'A fast, cross-platform build tool inspired by Make, designed for modern workflows.';
|
||||||
|
|
||||||
|
export const ogUrl = 'https://taskfile.dev/';
|
||||||
63
website/.vitepress/plugins/github-links.ts
Normal file
63
website/.vitepress/plugins/github-links.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import type MarkdownIt from 'markdown-it';
|
||||||
|
|
||||||
|
interface PluginOptions {
|
||||||
|
repo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function githubLinksPlugin(
|
||||||
|
md: MarkdownIt,
|
||||||
|
options: PluginOptions = {} as PluginOptions
|
||||||
|
): void {
|
||||||
|
const baseUrl = 'https://github.com';
|
||||||
|
const { repo } = options;
|
||||||
|
|
||||||
|
md.core.ruler.after('inline', 'github-links', (state): void => {
|
||||||
|
const tokens = state.tokens;
|
||||||
|
|
||||||
|
for (let i = 0; i < tokens.length; i++) {
|
||||||
|
if (tokens[i].type === 'inline' && tokens[i].children) {
|
||||||
|
const inlineTokens = tokens[i].children!;
|
||||||
|
|
||||||
|
for (let j = 0; j < inlineTokens.length; j++) {
|
||||||
|
if (inlineTokens[j].type === 'text') {
|
||||||
|
let text: string = inlineTokens[j].content!;
|
||||||
|
|
||||||
|
const protectedRefs: string[] = [];
|
||||||
|
let protectIndex: number = 0;
|
||||||
|
|
||||||
|
text = text.replace(
|
||||||
|
/[\w.-]+\/[\w.-]+#(\d+)/g,
|
||||||
|
(match: string): string => {
|
||||||
|
const placeholder: string = `__PROTECTED_${protectIndex}__`;
|
||||||
|
protectedRefs[protectIndex] = match;
|
||||||
|
protectIndex++;
|
||||||
|
return placeholder;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
text = text.replace(
|
||||||
|
/#(\d+)/g,
|
||||||
|
`<a href="${baseUrl}/${repo}/issues/$1" target="_blank" class="github-pr-link">#$1</a>`
|
||||||
|
);
|
||||||
|
|
||||||
|
text = text.replace(
|
||||||
|
/@([a-zA-Z0-9_-]+)(?![\w@.])/g,
|
||||||
|
`<a href="${baseUrl}/$1" target="_blank" class="github-user-mention">@$1</a>`
|
||||||
|
);
|
||||||
|
|
||||||
|
protectedRefs.forEach((ref: string, index: number): void => {
|
||||||
|
text = text.replace(`__PROTECTED_${index}__`, ref);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (text !== inlineTokens[j].content) {
|
||||||
|
inlineTokens[j].content = text;
|
||||||
|
inlineTokens[j].type = 'html_inline';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default githubLinksPlugin;
|
||||||
13
website/.vitepress/sponsors.ts
Normal file
13
website/.vitepress/sponsors.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export const sponsors = [
|
||||||
|
{
|
||||||
|
tier: 'Gold Sponsors',
|
||||||
|
size: 'big',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'devowl',
|
||||||
|
url: 'https://devowl.io/',
|
||||||
|
img: '/img/devowl.io.svg'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
45
website/.vitepress/team.ts
Normal file
45
website/.vitepress/team.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
export const team = [
|
||||||
|
{
|
||||||
|
slug: 'andreynering',
|
||||||
|
avatar: 'https://www.github.com/andreynering.png',
|
||||||
|
name: 'Andrey Nering',
|
||||||
|
icon: '/img/flag-brazil.svg',
|
||||||
|
title: 'Creator & Maintainer',
|
||||||
|
sponsor: 'https://github.com/sponsors/andreynering',
|
||||||
|
links: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/andreynering' },
|
||||||
|
{ icon: 'discord', link: 'https://discord.com/users/310141681926275082' },
|
||||||
|
{ icon: 'x', link: 'https://x.com/andreynering' },
|
||||||
|
{
|
||||||
|
icon: 'bluesky',
|
||||||
|
link: 'https://bsky.app/profile/andreynering.bsky.social'
|
||||||
|
},
|
||||||
|
{ icon: 'mastodon', link: 'https://mastodon.social/@andreynering' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'pd93',
|
||||||
|
avatar: 'https://www.github.com/pd93.png',
|
||||||
|
name: 'Pete Davison',
|
||||||
|
icon: '/img/flag-wales.svg',
|
||||||
|
title: 'Maintainer',
|
||||||
|
sponsor: 'https://github.com/sponsors/pd93',
|
||||||
|
links: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/pd93' },
|
||||||
|
{ icon: 'bluesky', link: 'https://bsky.app/profile/pd93.uk' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'vmaerten',
|
||||||
|
avatar: 'https://www.github.com/vmaerten.png',
|
||||||
|
name: 'Valentin Maerten',
|
||||||
|
icon: '/img/flag-france.svg',
|
||||||
|
title: 'Maintainer',
|
||||||
|
sponsor: 'https://github.com/sponsors/vmaerten',
|
||||||
|
links: [
|
||||||
|
{ icon: 'github', link: 'https://github.com/vmaerten' },
|
||||||
|
{ icon: 'x', link: 'https://x.com/v_maerten' },
|
||||||
|
{ icon: 'bluesky', link: 'https://bsky.app/profile/vmaerten.bsky.social' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
147
website/.vitepress/theme/custom.css
Normal file
147
website/.vitepress/theme/custom.css
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
:root {
|
||||||
|
--ifm-color-primary: #43aba2;
|
||||||
|
--vp-home-hero-name-color: var(--ifm-color-primary);
|
||||||
|
--vp-c-brand-1: var(--ifm-color-primary);
|
||||||
|
--vp-c-brand-2: var(--ifm-color-primary);
|
||||||
|
--vp-c-brand-3: var(--ifm-color-primary);
|
||||||
|
|
||||||
|
--vp-icon-info: #3b82f6;
|
||||||
|
--vp-icon-tip: #10b981;
|
||||||
|
--vp-icon-warning: #f59e0b;
|
||||||
|
--vp-icon-danger: #ef4444;
|
||||||
|
--vp-icon-details: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--vp-icon-info: #93c5fd;
|
||||||
|
--vp-icon-tip: #34d399;
|
||||||
|
--vp-icon-warning: #fbbf24;
|
||||||
|
--vp-icon-danger: #f87171;
|
||||||
|
--vp-icon-details: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
img[src*='shields.io'] {
|
||||||
|
display: inline;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
img[src*='custom-icon-badges.demolab.com'] {
|
||||||
|
display: inline;
|
||||||
|
height: 1em;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-user-mention {
|
||||||
|
font-weight: 700 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .blog-post:first-of-type {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post {
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post:nth-of-type(1) {
|
||||||
|
animation-delay: 0.1s;
|
||||||
|
}
|
||||||
|
.blog-post:nth-of-type(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
.blog-post:nth-of-type(3) {
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block .custom-block-title::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
-webkit-mask-position: center;
|
||||||
|
-webkit-mask-size: contain;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block.info .custom-block-title::before {
|
||||||
|
background-color: var(--vp-icon-info);
|
||||||
|
-webkit-mask-image: url('./icons/info.svg');
|
||||||
|
mask-image: url('./icons/info.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block.tip .custom-block-title::before {
|
||||||
|
background-color: var(--vp-icon-tip);
|
||||||
|
-webkit-mask-image: url('./icons/tip.svg');
|
||||||
|
mask-image: url('./icons/tip.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block.warning .custom-block-title::before {
|
||||||
|
background-color: var(--vp-icon-warning);
|
||||||
|
-webkit-mask-image: url('./icons/warning.svg');
|
||||||
|
mask-image: url('./icons/warning.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block.danger .custom-block-title::before {
|
||||||
|
background-color: var(--vp-icon-danger);
|
||||||
|
-webkit-mask-image: url('./icons/danger.svg');
|
||||||
|
mask-image: url('./icons/danger.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block.details[open] summary::before {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block .custom-block-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports not (mask-image: none) {
|
||||||
|
.custom-block .custom-block-title::before,
|
||||||
|
.custom-block.details summary::before {
|
||||||
|
font-size: 18px;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
background: none !important;
|
||||||
|
-webkit-mask: none !important;
|
||||||
|
mask: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block.info .custom-block-title::before {
|
||||||
|
content: 'ℹ️';
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block.tip .custom-block-title::before {
|
||||||
|
content: '💡';
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block.warning .custom-block-title::before {
|
||||||
|
content: '⚠️';
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-block.danger .custom-block-title::before {
|
||||||
|
content: '🔥';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.VPTeamPage > .VPTeamPageTitle {
|
||||||
|
padding-top: 0
|
||||||
|
}
|
||||||
3
website/.vitepress/theme/icons/danger.svg
Normal file
3
website/.vitepress/theme/icons/danger.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path d="M7.998 14.5c2.832 0 5-1.98 5-4.5 0-1.463-.68-2.19-1.879-3.383l-.036-.037c-1.013-1.008-2.3-2.29-2.834-4.434-.322.256-.63.579-.864.953-.432.696-.621 1.58-.046 2.73.473.947.67 2.284-.278 3.232-.61.61-1.545.84-2.403.633a2.788 2.788 0 0 1-1.436-.874A3.21 3.21 0 0 0 3 10c0 2.53 2.164 4.5 4.998 4.5zM9.533.753C9.496.34 9.16.009 8.77.146 7.035.75 4.34 3.187 5.997 6.5c.344.689.285 1.218.003 1.5-.419.419-1.54.487-2.04-.832-.173-.454-.659-.762-1.035-.454C2.036 7.44 1.5 8.702 1.5 10c0 3.512 2.998 6 6.498 6s6.5-2.5 6.5-6c0-2.137-1.128-3.26-2.312-4.438-1.19-1.184-2.436-2.425-2.653-4.81z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 681 B |
3
website/.vitepress/theme/icons/info.svg
Normal file
3
website/.vitepress/theme/icons/info.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 350 B |
|
Before Width: | Height: | Size: 435 B After Width: | Height: | Size: 435 B |
3
website/.vitepress/theme/icons/tip.svg
Normal file
3
website/.vitepress/theme/icons/tip.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path d="M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 796 B |
3
website/.vitepress/theme/icons/warning.svg
Normal file
3
website/.vitepress/theme/icons/warning.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 405 B |
24
website/.vitepress/theme/index.ts
Normal file
24
website/.vitepress/theme/index.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import DefaultTheme from 'vitepress/theme';
|
||||||
|
import type { Theme } from 'vitepress';
|
||||||
|
import './custom.css';
|
||||||
|
import HomePage from '../components/HomePage.vue';
|
||||||
|
import AuthorCard from '../components/AuthorCard.vue';
|
||||||
|
import BlogPost from '../components/BlogPost.vue';
|
||||||
|
import Version from '../components/Version.vue';
|
||||||
|
import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client';
|
||||||
|
import { h } from 'vue';
|
||||||
|
import 'virtual:group-icons.css';
|
||||||
|
export default {
|
||||||
|
extends: DefaultTheme,
|
||||||
|
Layout() {
|
||||||
|
return h(DefaultTheme.Layout, null, {
|
||||||
|
'home-features-after': () => h(HomePage)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
enhanceApp({ app }) {
|
||||||
|
app.component('AuthorCard', AuthorCard);
|
||||||
|
app.component('BlogPost', BlogPost);
|
||||||
|
app.component('Version', Version);
|
||||||
|
enhanceAppWithTabs(app);
|
||||||
|
}
|
||||||
|
} satisfies Theme;
|
||||||
@@ -1,29 +1,35 @@
|
|||||||
version: "3"
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
yarn:install:
|
install:
|
||||||
desc: Setup Docusaurus locally
|
desc: Setup VitePress locally
|
||||||
cmds:
|
cmds:
|
||||||
- yarn install
|
- pnpm install
|
||||||
sources:
|
sources:
|
||||||
- package.json
|
- package.json
|
||||||
- yarn.lock
|
- pnpm-lock.yaml
|
||||||
|
|
||||||
default:
|
default:
|
||||||
desc: Start website
|
desc: Start website
|
||||||
deps: [yarn:install]
|
deps: [install]
|
||||||
aliases: [s, start]
|
aliases: [s, start]
|
||||||
vars:
|
vars:
|
||||||
HOST: '{{default "0.0.0.0" .HOST}}'
|
HOST: '{{default "0.0.0.0" .HOST}}'
|
||||||
PORT: '{{default "3001" .PORT}}'
|
PORT: '{{default "3001" .PORT}}'
|
||||||
cmds:
|
cmds:
|
||||||
- npx docusaurus start --no-open --host={{.HOST}} --port={{.PORT}}
|
- pnpm dev --host={{.HOST}} --port={{.PORT}}
|
||||||
|
|
||||||
|
lint:
|
||||||
|
desc: Lint website
|
||||||
|
deps: [install]
|
||||||
|
cmds:
|
||||||
|
- pnpm lint
|
||||||
|
|
||||||
build:
|
build:
|
||||||
desc: Build website
|
desc: Build website
|
||||||
deps: [yarn:install]
|
deps: [install]
|
||||||
cmds:
|
cmds:
|
||||||
- npx docusaurus build
|
- pnpm build
|
||||||
|
|
||||||
preview:
|
preview:
|
||||||
desc: Preview Website
|
desc: Preview Website
|
||||||
@@ -33,20 +39,19 @@ tasks:
|
|||||||
HOST: '{{default "localhost" .HOST}}'
|
HOST: '{{default "localhost" .HOST}}'
|
||||||
PORT: '{{default "3001" .PORT}}'
|
PORT: '{{default "3001" .PORT}}'
|
||||||
cmds:
|
cmds:
|
||||||
- npx docusaurus serve --no-open --host={{.HOST}} --port={{.PORT}}
|
- pnpm preview --host={{.HOST}} --port={{.PORT}}
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
desc: Clean temp directories
|
desc: Clean temp directories
|
||||||
cmds:
|
cmds:
|
||||||
- rm -rf ./build
|
- rm -rf ./vitepress/dist
|
||||||
|
|
||||||
deploy:
|
deploy:next:
|
||||||
desc: Build and deploy Docusaurus
|
desc: Build and deploy next.taskfile.dev
|
||||||
summary: Requires GIT_USER and GIT_PASS envs to be previous set
|
|
||||||
cmds:
|
cmds:
|
||||||
- npx docusaurus deploy
|
- pnpm netlify deploy --prod --site=4e13dfcf-fc0d-4bec-ad60-b918a8dc3942
|
||||||
|
|
||||||
upgrade:
|
deploy:prod:
|
||||||
desc: Upgrade Docusaurus
|
desc: Build and deploy taskfile.dev
|
||||||
cmds:
|
cmds:
|
||||||
- yarn upgrade @docusaurus/core@latest @docusaurus/preset-classic@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest
|
- pnpm netlify deploy --prod --site=e625bc6a-1cd3-465d-ad30-7bbddaeb4f31
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export default {
|
|
||||||
presets: ['@docusaurus/core/lib/babel/preset'],
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
andreynering:
|
|
||||||
name: Andrey Nering
|
|
||||||
title: Maintainer of Task
|
|
||||||
url: https://github.com/andreynering
|
|
||||||
image_url: https://github.com/andreynering.png
|
|
||||||
pd93:
|
|
||||||
name: Pete Davison
|
|
||||||
title: Maintainer of Task
|
|
||||||
url: https://github.com/pd93
|
|
||||||
image_url: https://github.com/pd93.png
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export const GITHUB_URL = 'https://github.com/go-task/task';
|
|
||||||
export const TWITTER_URL = 'https://twitter.com/taskfiledev';
|
|
||||||
export const BLUESKY_URL = 'https://bsky.app/profile/taskfile.dev';
|
|
||||||
export const MASTODON_URL = 'https://fosstodon.org/@task';
|
|
||||||
export const DISCORD_URL = 'https://discord.gg/6TY36E39UK';
|
|
||||||
export const STACK_OVERFLOW = 'https://stackoverflow.com/questions/tagged/taskfile';
|
|
||||||
export const ANSWER_OVERFLOW = 'https://www.answeroverflow.com/c/974121106208354339';
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
slug: /community/
|
|
||||||
sidebar_position: 10
|
|
||||||
---
|
|
||||||
|
|
||||||
# Community
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Integrations
|
|
||||||
|
|
||||||
Many of our integrations are contributed and maintained by the community. You
|
|
||||||
can view the full list of community integrations
|
|
||||||
[here](/integrations#community-integrations).
|
|
||||||
|
|
||||||
## Installation methods
|
|
||||||
|
|
||||||
Some installation methods are maintained by third party:
|
|
||||||
|
|
||||||
- [GitHub Actions](https://github.com/arduino/setup-task) by @arduino
|
|
||||||
- [AUR](https://aur.archlinux.org/packages/go-task-bin) by @carlsmedstad
|
|
||||||
- [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/task.json)
|
|
||||||
- [Fedora](https://packages.fedoraproject.org/pkgs/golang-github-task/go-task/)
|
|
||||||
- [NixOS](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/tools/go-task/default.nix)
|
|
||||||
- [Conda](https://github.com/conda-forge/go-task-feedstock/)
|
|
||||||
|
|
||||||
## More
|
|
||||||
|
|
||||||
Also, thanks for all the
|
|
||||||
[code contributors](https://github.com/go-task/task/graphs/contributors),
|
|
||||||
[financial contributors](https://opencollective.com/task), all those who
|
|
||||||
[reported bugs](https://github.com/go-task/task/issues?q=is%3Aissue) and
|
|
||||||
[answered questions](https://github.com/go-task/task/discussions).
|
|
||||||
|
|
||||||
If you know something that is missing in this document, please submit a pull
|
|
||||||
request.
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
---
|
|
||||||
slug: /contributing/
|
|
||||||
sidebar_position: 12
|
|
||||||
---
|
|
||||||
|
|
||||||
# Contributing
|
|
||||||
|
|
||||||
Contributions to Task are very welcome, but we ask that you read this document
|
|
||||||
before submitting a PR.
|
|
||||||
|
|
||||||
:::note
|
|
||||||
|
|
||||||
This document applies to the core [Task][task] repository _and_ [Task for Visual
|
|
||||||
Studio Code][vscode-task].
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Before you start
|
|
||||||
|
|
||||||
- **Check existing work** - Is there an existing PR? Are there issues discussing
|
|
||||||
the feature/change you want to make? Please make sure you consider/address
|
|
||||||
these discussions in your work.
|
|
||||||
- **Backwards compatibility** - Will your change break existing Taskfiles? It is
|
|
||||||
much more likely that your change will merged if it backwards compatible. Is
|
|
||||||
there an approach you can take that maintains this compatibility? If not,
|
|
||||||
consider opening an issue first so that API changes can be discussed before
|
|
||||||
you invest your time into a PR.
|
|
||||||
- **Experiments** - If there is no way to make your change backward compatible
|
|
||||||
then there is a procedure to introduce breaking changes into minor versions.
|
|
||||||
We call these "[experiments][experiments]". If you're intending to work on an
|
|
||||||
experiment, then please read the [experiments workflow][experiments-workflow]
|
|
||||||
document carefully and submit a proposal first.
|
|
||||||
|
|
||||||
## 1. Setup
|
|
||||||
|
|
||||||
- **Go** - Task is written in [Go][go]. We always support the latest two major
|
|
||||||
Go versions, so make sure your version is recent enough.
|
|
||||||
- **Node.js** - [Node.js][nodejs] is used to host Task's documentation server
|
|
||||||
and is required if you want to run this server locally. It is also required if
|
|
||||||
you want to contribute to the Visual Studio Code extension.
|
|
||||||
- **Yarn** - [Yarn][yarn] is the Node.js package manager used by Task.
|
|
||||||
|
|
||||||
## 2. Making changes
|
|
||||||
|
|
||||||
- **Code style** - Try to maintain the existing code style where possible. Go
|
|
||||||
code should be formatted and linted by [`golangci-lint`][golangci-lint]. This
|
|
||||||
wraps the [`gofumpt`][gofumpt] and [`gci`][gci] formatters and a number of
|
|
||||||
linters. We recommend that you take a look at the [golangci-lint
|
|
||||||
docs][golangci-lint-docs] for a guide on how to setup your editor to
|
|
||||||
auto-format your code. Any Markdown or TypeScript files should be formatted
|
|
||||||
and linted by [Prettier][prettier]. This style is enforced by our CI to ensure
|
|
||||||
that we have a consistent style across the project. You can use the `task
|
|
||||||
lint` command to lint the code locally and the `task lint:fix` command to try
|
|
||||||
to automatically fix any issues that are found. You can also use the `task
|
|
||||||
fmt` command to auto-format the files if your editor doesn't do it for you.
|
|
||||||
- **Documentation** - Ensure that you add/update any relevant documentation. See
|
|
||||||
the [updating documentation](#updating-documentation) section below.
|
|
||||||
- **Tests** - Ensure that you add/update any relevant tests and that all tests
|
|
||||||
are passing before submitting the PR. See the [writing tests](#writing-tests)
|
|
||||||
section below.
|
|
||||||
|
|
||||||
### Running your changes
|
|
||||||
|
|
||||||
To run Task with working changes, you can use `go run ./cmd/task`. To run a
|
|
||||||
development build of task against a test Taskfile in `testdata`, you can use
|
|
||||||
`go run ./cmd/task --dir ./testdata/<my_test_dir> <task_name>`.
|
|
||||||
|
|
||||||
To run Task for Visual Studio Code, you can open the project in VSCode and hit
|
|
||||||
F5 (or whatever you debug keybind is set to). This will open a new VSCode window
|
|
||||||
with the extension running. Debugging this way is recommended as it will allow
|
|
||||||
you to set breakpoints and step through the code. Otherwise, you can run
|
|
||||||
`task package` which will generate a `.vsix` file that can be used to manually
|
|
||||||
install the extension.
|
|
||||||
|
|
||||||
### Updating documentation
|
|
||||||
|
|
||||||
Task uses [Docusaurus][docusaurus] to host a documentation server. The code for
|
|
||||||
this is located in the core Task repository. This can be setup and run locally
|
|
||||||
by using `task website` (requires `nodejs` & `yarn`). All content is written in
|
|
||||||
[MDX][mdx] (an extension of Markdown) and is located in the `website/docs`
|
|
||||||
directory. All Markdown documents should have an 80 character line wrap limit
|
|
||||||
(enforced by Prettier).
|
|
||||||
|
|
||||||
When making a change, consider whether a change to the [Usage Guide](/usage) is
|
|
||||||
necessary. This document contains descriptions and examples of how to use Task
|
|
||||||
features. If you're adding a new feature, try to find an appropriate place to
|
|
||||||
add a new section. If you're updating an existing feature, ensure that the
|
|
||||||
documentation and any examples are up-to-date. Ensure that any examples follow
|
|
||||||
the [Taskfile Styleguide](/styleguide).
|
|
||||||
|
|
||||||
If you added a new command or flag, ensure that you add it to the [CLI
|
|
||||||
Reference](/reference/cli). New fields also need to be added to the [Schema
|
|
||||||
Reference](/reference/schema) and [JSON Schema][json-schema]. The descriptions
|
|
||||||
for fields in the docs and the schema should match.
|
|
||||||
|
|
||||||
### Writing tests
|
|
||||||
|
|
||||||
A lot of Task's tests are held in the `task_test.go` file in the project root
|
|
||||||
and this is where you'll most likely want to add new ones too. Most of these
|
|
||||||
tests also have a subdirectory in the `testdata` directory where any
|
|
||||||
Taskfiles/data required to run the tests are stored.
|
|
||||||
|
|
||||||
When making a changes, consider whether new tests are required. These tests
|
|
||||||
should ensure that the functionality you are adding will continue to work in the
|
|
||||||
future. Existing tests may also need updating if you have changed Task's
|
|
||||||
behavior.
|
|
||||||
|
|
||||||
You may also consider adding unit tests for any new functions you have added.
|
|
||||||
The unit tests should follow the Go convention of being location in a file named
|
|
||||||
`*_test.go` in the same package as the code being tested.
|
|
||||||
|
|
||||||
## 3. Committing your code
|
|
||||||
|
|
||||||
Try to write meaningful commit messages and avoid having too many commits on the
|
|
||||||
PR. Most PRs should likely have a single commit (although for bigger PRs it may
|
|
||||||
be reasonable to split it in a few). Git squash and rebase is your friend!
|
|
||||||
|
|
||||||
If you're not sure how to format your commit message, check out [Conventional
|
|
||||||
Commits][conventional-commits]. This style is not enforced, but it is a good way
|
|
||||||
to make your commit messages more readable and consistent.
|
|
||||||
|
|
||||||
## 4. Submitting a PR
|
|
||||||
|
|
||||||
- **Describe your changes** - Ensure that you provide a comprehensive
|
|
||||||
description of your changes.
|
|
||||||
- **Issue/PR links** - Link any previous work such as related issues or PRs.
|
|
||||||
Please describe how your changes differ to/extend this work.
|
|
||||||
- **Examples** - Add any examples or screenshots that you think are useful to
|
|
||||||
demonstrate the effect of your changes.
|
|
||||||
- **Draft PRs** - If your changes are incomplete, but you would like to discuss
|
|
||||||
them, open the PR as a draft and add a comment to start a discussion. Using
|
|
||||||
comments rather than the PR description allows the description to be updated
|
|
||||||
later while preserving any discussions.
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
> I want to contribute, where do I start?
|
|
||||||
|
|
||||||
Take a look at the list of [open issues for Task][task-open-issues] or [Task for
|
|
||||||
Visual Studio Code][vscode-task-open-issues]. We have a [good first
|
|
||||||
issue][good-first-issue] label for simpler issues that are ideal for first time
|
|
||||||
contributions.
|
|
||||||
|
|
||||||
All kinds of contributions are welcome, whether its a typo fix or a shiny new
|
|
||||||
feature. You can also contribute by upvoting/commenting on issues, helping to
|
|
||||||
answer questions or contributing to other [community projects](/community).
|
|
||||||
|
|
||||||
> I'm stuck, where can I get help?
|
|
||||||
|
|
||||||
If you have questions, feel free to ask them in the `#help` forum channel on our
|
|
||||||
[Discord server][discord-server] or open a [Discussion][discussion] on GitHub.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
|
||||||
[experiments]: /experiments
|
|
||||||
[experiments-workflow]: /experiments#workflow
|
|
||||||
[task]: https://github.com/go-task/task
|
|
||||||
[vscode-task]: https://github.com/go-task/vscode-task
|
|
||||||
[go]: https://go.dev
|
|
||||||
[gofumpt]: https://github.com/mvdan/gofumpt
|
|
||||||
[gci]: https://github.com/daixiang0/gci
|
|
||||||
[golangci-lint]: https://golangci-lint.run
|
|
||||||
[golangci-lint-docs]: https://golangci-lint.run/welcome/integrations/
|
|
||||||
[prettier]: https://prettier.io
|
|
||||||
[nodejs]: https://nodejs.org/en/
|
|
||||||
[yarn]: https://yarnpkg.com/
|
|
||||||
[docusaurus]: https://docusaurus.io
|
|
||||||
[json-schema]: https://github.com/go-task/task/blob/main/website/static/schema.json
|
|
||||||
[task-open-issues]: https://github.com/go-task/task/issues
|
|
||||||
[vscode-task-open-issues]: https://github.com/go-task/vscode-task/issues
|
|
||||||
[good-first-issue]: https://github.com/go-task/task/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
|
|
||||||
[discord-server]: https://discord.gg/6TY36E39UK
|
|
||||||
[discussion]: https://github.com/go-task/task/discussions
|
|
||||||
[conventional-commits]: https://www.conventionalcommits.org
|
|
||||||
[mdx]: https://mdxjs.com/
|
|
||||||
{/* prettier-ignore-end */}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
slug: /deprecations/completion-scripts/
|
|
||||||
---
|
|
||||||
|
|
||||||
# Completion Scripts
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
|
|
||||||
This deprecation breaks the following functionality:
|
|
||||||
|
|
||||||
- Any direct references to the completion scripts in the Task git repository
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
Direct use of the completion scripts in the `completion/*` directory of the
|
|
||||||
[github.com/go-task/task][task] Git repository is deprecated. Any shell
|
|
||||||
configuration that directly refers to these scripts will potentially break in
|
|
||||||
the future as the scripts may be moved or deleted entirely. Any configuration
|
|
||||||
should be updated to use the [new method for generating shell
|
|
||||||
completions][completions] instead.
|
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
|
||||||
[completions]: ../installation.mdx#setup-completions
|
|
||||||
[task]: https://github.com/go-task/task
|
|
||||||
{/* prettier-ignore-end */}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
slug: /deprecations/
|
|
||||||
sidebar_position: 8
|
|
||||||
---
|
|
||||||
|
|
||||||
# Deprecations
|
|
||||||
|
|
||||||
As Task evolves, it occasionally outgrows some of its functionality. This can be
|
|
||||||
because they are no longer useful, because another feature has replaced it or
|
|
||||||
because of a change in the way that Task works internally.
|
|
||||||
|
|
||||||
When this happens, we mark the functionality as deprecated. This means that it
|
|
||||||
will be removed in a future version of Task. This functionality will continue to
|
|
||||||
work until that time, but we strongly recommend that you do not implement this
|
|
||||||
functionality in new Taskfiles and make a plan to migrate away from it as soon
|
|
||||||
as possible.
|
|
||||||
|
|
||||||
You can view a full list of active deprecations in the "Deprecations" section of
|
|
||||||
the sidebar.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
# This is a template for an experiments documentation
|
|
||||||
# Copy this page and fill in the details as necessary
|
|
||||||
title: '--- Template ---'
|
|
||||||
sidebar_position: -1 # Always push to the top
|
|
||||||
draft: true # Hide in production
|
|
||||||
---
|
|
||||||
|
|
||||||
# \{Name of Deprecated Feature\} (#\{Issue\})
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
|
|
||||||
This deprecation breaks the following functionality:
|
|
||||||
|
|
||||||
- \{list any existing functionality that will be broken by this deprecation\}
|
|
||||||
- \{if there are no breaking changes, remove this admonition\}
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
\{Short description of the feature/behavior and why it is being deprecated\}
|
|
||||||
|
|
||||||
\{Short explanation of any replacement features/behaviors and how users should
|
|
||||||
migrate to it\}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
slug: /deprecations/template-functions/
|
|
||||||
---
|
|
||||||
|
|
||||||
# Template Functions
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
|
|
||||||
This deprecation breaks the following functionality:
|
|
||||||
|
|
||||||
- A small set of templating functions
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
The following templating functions are deprecated. Any replacement functions are
|
|
||||||
listed besides the function being removed.
|
|
||||||
|
|
||||||
| Deprecated function | Replaced by |
|
|
||||||
| ------------------- | ----------- |
|
|
||||||
| `IsSH` | - |
|
|
||||||
| `FromSlash` | `fromSlash` |
|
|
||||||
| `ToSlash` | `toSlash` |
|
|
||||||
| `ExeExt` | `exeExt` |
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
---
|
|
||||||
slug: /deprecations/version-2-schema/
|
|
||||||
---
|
|
||||||
|
|
||||||
# Version 2 Schema (#1197)
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
|
|
||||||
This deprecation breaks the following functionality:
|
|
||||||
|
|
||||||
- Any Taskfiles that use the version 2 schema
|
|
||||||
- `Taskvar.yml` files
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
The Taskfile version 2 schema was introduced in March 2018 and replaced by
|
|
||||||
version 3 in August 2019. In May 2023 [we published a deprecation
|
|
||||||
notice][deprecation-notice] for the version 2 schema on the basis that the vast
|
|
||||||
majority of users had already upgraded to version 3 and removing support for
|
|
||||||
version 2 would allow us to tidy up the codebase and focus on new functionality
|
|
||||||
instead.
|
|
||||||
|
|
||||||
In December 2023, the final version of Task that supports the version 2 schema
|
|
||||||
([v3.33.0][v3.33.0]) was published and all legacy code was removed from Task's
|
|
||||||
main branch. To use a more recent version of Task, you will need to ensure that
|
|
||||||
your Taskfile uses the version 3 schema instead. A list of changes between
|
|
||||||
version 2 and version 3 are available in the [Task v3 Release Notes][v3.0.0].
|
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
|
||||||
[v3.0.0]: https://github.com/go-task/task/releases/tag/v3.0.0
|
|
||||||
[v3.33.0]: https://github.com/go-task/task/releases/tag/v3.33.0
|
|
||||||
[deprecation-notice]: https://github.com/go-task/task/issues/1197
|
|
||||||
{/* prettier-ignore-end */}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
---
|
|
||||||
slug: /experiments/gentle-force/
|
|
||||||
---
|
|
||||||
|
|
||||||
# Gentle Force (#1200)
|
|
||||||
|
|
||||||
:::caution
|
|
||||||
|
|
||||||
All experimental features are subject to breaking changes and/or removal _at any
|
|
||||||
time_. We strongly recommend that you do not use these features in a production
|
|
||||||
environment. They are intended for testing and feedback only.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
|
|
||||||
This experiment breaks the following functionality:
|
|
||||||
|
|
||||||
- The `--force` flag
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::info
|
|
||||||
|
|
||||||
To enable this experiment, set the environment variable:
|
|
||||||
`TASK_X_GENTLE_FORCE=1`. Check out [our guide to enabling experiments
|
|
||||||
][enabling-experiments] for more information.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
The `--force` flag currently forces _all_ tasks to run regardless of the status
|
|
||||||
checks. This can be useful, but we have found that most of the time users only
|
|
||||||
expect the direct task they are calling to be forced and _not_ all of its
|
|
||||||
dependant tasks.
|
|
||||||
|
|
||||||
This experiment changes the `--force` flag to only force the directly called
|
|
||||||
task. All dependant tasks will have their statuses checked as normal and will
|
|
||||||
only run if Task considers them to be out of date. A new `--force-all` flag will
|
|
||||||
also be added to maintain the current behavior for users that need this
|
|
||||||
functionality.
|
|
||||||
|
|
||||||
If you want to migrate, but continue to force all dependant tasks to run, you
|
|
||||||
should replace all uses of the `--force` flag with `--force-all`. Alternatively,
|
|
||||||
if you want to adopt the new behavior, you can continue to use the `--force`
|
|
||||||
flag as you do now!
|
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
|
||||||
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
|
||||||
{/* prettier-ignore-end */}
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
---
|
|
||||||
slug: /experiments/remote-taskfiles/
|
|
||||||
---
|
|
||||||
|
|
||||||
import Tabs from '@theme/Tabs';
|
|
||||||
import TabItem from '@theme/TabItem';
|
|
||||||
|
|
||||||
# Remote Taskfiles (#1317)
|
|
||||||
|
|
||||||
:::caution
|
|
||||||
|
|
||||||
All experimental features are subject to breaking changes and/or removal _at any
|
|
||||||
time_. We strongly recommend that you do not use these features in a production
|
|
||||||
environment. They are intended for testing and feedback only.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::info
|
|
||||||
|
|
||||||
To enable this experiment, set the environment variable:
|
|
||||||
`TASK_X_REMOTE_TASKFILES=1`. Check out [our guide to enabling experiments
|
|
||||||
][enabling-experiments] for more information.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::danger
|
|
||||||
Never run remote Taskfiles from sources that you do not trust.
|
|
||||||
:::
|
|
||||||
|
|
||||||
This experiment allows you to use Taskfiles which are stored in remote
|
|
||||||
locations. This applies to both the root Taskfile (aka. Entrypoint) and also
|
|
||||||
when including Taskfiles.
|
|
||||||
|
|
||||||
Task uses "nodes" to reference remote Taskfiles. There are a few different types
|
|
||||||
of node which you can use:
|
|
||||||
|
|
||||||
<Tabs groupId="method" queryString>
|
|
||||||
<TabItem value="http" label="HTTP/HTTPS">
|
|
||||||
|
|
||||||
`https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml`
|
|
||||||
|
|
||||||
This is the most basic type of remote node and works by downloading the file
|
|
||||||
from the specified URL. The file must be a valid Taskfile and can be of any
|
|
||||||
name. If a file is not found at the specified URL, Task will append each of the
|
|
||||||
[supported file names][supported-file-names] in turn until it finds a valid
|
|
||||||
file. If it still does not find a valid Taskfile, an error is returned.
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="git-http" label="Git over HTTP">
|
|
||||||
|
|
||||||
`https://github.com/go-task/task.git//website/static/Taskfile.yml?ref=main`
|
|
||||||
|
|
||||||
This type of node works by downloading the file from a Git repository over
|
|
||||||
HTTP/HTTPS. The first part of the URL is the base URL of the Git repository.
|
|
||||||
This is the same URL that you would use to clone the repo over HTTP.
|
|
||||||
|
|
||||||
- You can optionally add the path to the Taskfile in the repository by appending
|
|
||||||
`//<path>` to the URL.
|
|
||||||
- You can also optionally specify a branch or tag to use by appending
|
|
||||||
`?ref=<ref>` to the end of the URL. If you omit a reference, the default branch
|
|
||||||
will be used.
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="git-ssh" label="Git over SSH">
|
|
||||||
|
|
||||||
`git@github.com/go-task/task.git//website/static/Taskfile.yml?ref=main`
|
|
||||||
|
|
||||||
This type of node works by downloading the file from a Git repository over SSH.
|
|
||||||
The first part of the URL is the user and base URL of the Git repository. This
|
|
||||||
is the same URL that you would use to clone the repo over SSH.
|
|
||||||
|
|
||||||
To use Git over SSH, you need to make sure that your SSH agent has your private
|
|
||||||
SSH keys added so that they can be used during authentication.
|
|
||||||
|
|
||||||
- You can optionally add the path to the Taskfile in the repository by appending
|
|
||||||
`//<path>` to the URL.
|
|
||||||
- You can also optionally specify a branch or tag to use by appending
|
|
||||||
`?ref=<ref>` to the end of the URL. If you omit a reference, the default branch
|
|
||||||
will be used.
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
Task has an [example remote Taskfile][example-remote-taskfile] in our repository
|
|
||||||
that you can use for testing and that we will use throughout this document:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
cmds:
|
|
||||||
- task: hello
|
|
||||||
|
|
||||||
hello:
|
|
||||||
cmds:
|
|
||||||
- echo "Hello Task!"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Specifying a remote entrypoint
|
|
||||||
|
|
||||||
By default, Task will look for one of the [supported file
|
|
||||||
names][supported-file-names] on your local filesystem. If you want to use a
|
|
||||||
remote file instead, you can pass its URI into the `--taskfile`/`-t` flag just
|
|
||||||
like you would to specify a different local file. For example:
|
|
||||||
|
|
||||||
<Tabs groupId="method" queryString>
|
|
||||||
<TabItem value="http" label="HTTP/HTTPS">
|
|
||||||
```shell
|
|
||||||
$ task --taskfile https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml
|
|
||||||
task: [hello] echo "Hello Task!"
|
|
||||||
Hello Task!
|
|
||||||
```
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="git-http" label="Git over HTTP">
|
|
||||||
```shell
|
|
||||||
$ task --taskfile https://github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
|
|
||||||
task: [hello] echo "Hello Task!"
|
|
||||||
Hello Task!
|
|
||||||
```
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="git-ssh" label="Git over SSH">
|
|
||||||
```shell
|
|
||||||
$ task --taskfile git@github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
|
|
||||||
task: [hello] echo "Hello Task!"
|
|
||||||
Hello Task!
|
|
||||||
```
|
|
||||||
</TabItem>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Including remote Taskfiles
|
|
||||||
|
|
||||||
Including a remote file works exactly the same way that including a local file
|
|
||||||
does. You just need to replace the local path with a remote URI. Any tasks in
|
|
||||||
the remote Taskfile will be available to run from your main Taskfile.
|
|
||||||
|
|
||||||
<Tabs groupId="method" queryString>
|
|
||||||
<TabItem value="http" label="HTTP/HTTPS">
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
my-remote-namespace: https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml
|
|
||||||
```
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="git-http" label="Git over HTTP">
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
my-remote-namespace: https://github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
|
|
||||||
```
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="git-ssh" label="Git over SSH">
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
my-remote-namespace: git@github.com/go-task/task.git//website/static/Taskfile.yml?ref=main
|
|
||||||
```
|
|
||||||
</TabItem>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ task my-remote-namespace:hello
|
|
||||||
task: [hello] echo "Hello Task!"
|
|
||||||
Hello Task!
|
|
||||||
```
|
|
||||||
|
|
||||||
### Authenticating using environment variables
|
|
||||||
|
|
||||||
The Taskfile location is processed by the templating system, so you can
|
|
||||||
reference environment variables in your URL if you need to add authentication.
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
my-remote-namespace: https://{{.TOKEN}}@raw.githubusercontent.com/my-org/my-repo/main/Taskfile.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
### Automatic checksums
|
|
||||||
|
|
||||||
Running commands from sources that you do not control is always a potential
|
|
||||||
security risk. For this reason, we have added some automatic checks when using
|
|
||||||
remote Taskfiles:
|
|
||||||
|
|
||||||
1. When running a task from a remote Taskfile for the first time, Task will
|
|
||||||
print a warning to the console asking you to check that you are sure that you
|
|
||||||
trust the source of the Taskfile. If you do not accept the prompt, then Task
|
|
||||||
will exit with code `104` (not trusted) and nothing will run. If you accept
|
|
||||||
the prompt, the remote Taskfile will run and further calls to the remote
|
|
||||||
Taskfile will not prompt you again.
|
|
||||||
2. Whenever you run a remote Taskfile, Task will create and store a checksum of
|
|
||||||
the file that you are running. If the checksum changes, then Task will print
|
|
||||||
another warning to the console to inform you that the contents of the remote
|
|
||||||
file has changed. If you do not accept the prompt, then Task will exit with
|
|
||||||
code `104` (not trusted) and nothing will run. If you accept the prompt, the
|
|
||||||
checksum will be updated and the remote Taskfile will run.
|
|
||||||
|
|
||||||
Sometimes you need to run Task in an environment that does not have an
|
|
||||||
interactive terminal, so you are not able to accept a prompt. In these cases you
|
|
||||||
are able to tell task to accept these prompts automatically by using the `--yes`
|
|
||||||
flag. Before enabling this flag, you should:
|
|
||||||
|
|
||||||
1. Be sure that you trust the source and contents of the remote Taskfile.
|
|
||||||
2. Consider using a pinned version of the remote Taskfile (e.g. A link
|
|
||||||
containing a commit hash) to prevent Task from automatically accepting a
|
|
||||||
prompt that says a remote Taskfile has changed.
|
|
||||||
|
|
||||||
### Manual checksum pinning
|
|
||||||
|
|
||||||
Alternatively, if you expect the contents of your remote files to be a constant
|
|
||||||
value, you can pin the checksum of the included file instead:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
included:
|
|
||||||
taskfile: https://taskfile.dev
|
|
||||||
checksum: c153e97e0b3a998a7ed2e61064c6ddaddd0de0c525feefd6bba8569827d8efe9
|
|
||||||
```
|
|
||||||
|
|
||||||
This will disable the automatic checksum prompts discussed above. However, if
|
|
||||||
the checksums do not match, Task will exit immediately with an error. When
|
|
||||||
setting this up for the first time, you may not know the correct value of the
|
|
||||||
checksum. There are a couple of ways you can obtain this:
|
|
||||||
|
|
||||||
1. Add the include normally without the `checksum` key. The first time you run
|
|
||||||
the included Taskfile, a `.task/remote` temporary directory is created. Find
|
|
||||||
the correct set of files for your included Taskfile and open the file that
|
|
||||||
ends with `.checksum`. You can copy the contents of this file and paste it
|
|
||||||
into the `checksum` key of your include. This method is safest as it allows
|
|
||||||
you to inspect the downloaded Taskfile before you pin it.
|
|
||||||
2. Alternatively, add the include with a temporary random value in the
|
|
||||||
`checksum` key. When you try to run the Taskfile, you will get an error that
|
|
||||||
will report the incorrect expected checksum and the actual checksum. You can
|
|
||||||
copy the actual checksum and replace your temporary random value.
|
|
||||||
|
|
||||||
### TLS
|
|
||||||
|
|
||||||
Task currently supports both `http` and `https` URLs. However, the `http`
|
|
||||||
requests will not execute by default unless you run the task with the
|
|
||||||
`--insecure` flag. This is to protect you from accidentally running a remote
|
|
||||||
Taskfile that is downloaded via an unencrypted connection. Sources that are not
|
|
||||||
protected by TLS are vulnerable to [man-in-the-middle
|
|
||||||
attacks][man-in-the-middle-attacks] and should be avoided unless you know what
|
|
||||||
you are doing.
|
|
||||||
|
|
||||||
## Caching & Running Offline
|
|
||||||
|
|
||||||
Whenever you run a remote Taskfile, the latest copy will be downloaded from the
|
|
||||||
internet and cached locally. This cached file will be used for all future
|
|
||||||
invocations of the Taskfile until the cache expires. Once it expires, Task will
|
|
||||||
download the latest copy of the file and update the cache. By default, the cache
|
|
||||||
is set to expire immediately. This means that Task will always fetch the latest
|
|
||||||
version. However, the cache expiry duration can be modified by setting the
|
|
||||||
`--expiry` flag.
|
|
||||||
|
|
||||||
If for any reason you lose access to the internet or you are running Task in
|
|
||||||
offline mode (via the `--offline` flag or `TASK_OFFLINE` environment variable),
|
|
||||||
Task will run the any available cached files _even if they are expired_. This
|
|
||||||
means that you should never be stuck without the ability to run your tasks as
|
|
||||||
long as you have downloaded a remote Taskfile at least once.
|
|
||||||
|
|
||||||
By default, Task will timeout requests to download remote files after 10 seconds
|
|
||||||
and look for a cached copy instead. This timeout can be configured by setting
|
|
||||||
the `--timeout` flag and specifying a duration. For example, `--timeout 5s` will
|
|
||||||
set the timeout to 5 seconds.
|
|
||||||
|
|
||||||
By default, the cache is stored in the Task temp directory, represented by the
|
|
||||||
`TASK_TEMP_DIR` [environment variable](../reference/environment.mdx) You can
|
|
||||||
override the location of the cache by setting the `TASK_REMOTE_DIR` environment
|
|
||||||
variable. This way, you can share the cache between different projects.
|
|
||||||
|
|
||||||
You can force Task to ignore the cache and download the latest version
|
|
||||||
by using the `--download` flag.
|
|
||||||
|
|
||||||
You can use the `--clear-cache` flag to clear all cached remote files.
|
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
|
||||||
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
|
||||||
[man-in-the-middle-attacks]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
|
|
||||||
[supported-file-names]: https://taskfile.dev/usage/#supported-file-names
|
|
||||||
[example-remote-taskfile]: https://raw.githubusercontent.com/go-task/task/main/website/static/Taskfile.yml
|
|
||||||
{/* prettier-ignore-end */}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
# This is a template for an experiments documentation
|
|
||||||
# Copy this page and fill in the details as necessary
|
|
||||||
title: '--- Template ---'
|
|
||||||
sidebar_position: -1 # Always push to the top
|
|
||||||
draft: true # Hide in production
|
|
||||||
---
|
|
||||||
|
|
||||||
# \{Name of Experiment\} (#\{Issue\})
|
|
||||||
|
|
||||||
:::caution
|
|
||||||
|
|
||||||
All experimental features are subject to breaking changes and/or removal _at any
|
|
||||||
time_. We strongly recommend that you do not use these features in a production
|
|
||||||
environment. They are intended for testing and feedback only.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
|
|
||||||
This experiment breaks the following functionality:
|
|
||||||
|
|
||||||
- \{list any existing functionality that will be broken by this experiment\}
|
|
||||||
- \{if there are no breaking changes, remove this admonition\}
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::info
|
|
||||||
|
|
||||||
To enable this experiment, set the environment variable: `TASK_X_{feature}=1`.
|
|
||||||
Check out [our guide to enabling experiments ][enabling-experiments] for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
\{Short description of the feature\}
|
|
||||||
|
|
||||||
\{Short explanation of how users should migrate to the new behavior\}
|
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
|
||||||
[enabling-experiments]: ./experiments.mdx#enabling-experiments
|
|
||||||
{/* prettier-ignore-end */}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
---
|
|
||||||
slug: /faq/
|
|
||||||
sidebar_position: 15
|
|
||||||
---
|
|
||||||
|
|
||||||
# FAQ
|
|
||||||
|
|
||||||
This page contains a list of frequently asked questions about Task.
|
|
||||||
|
|
||||||
## When will \<feature\> be released? / ETAs
|
|
||||||
|
|
||||||
Task is _free_ and _open source_ project maintained by a small group of
|
|
||||||
volunteers with full time jobs and lives outside of the project. Because of
|
|
||||||
this, it is difficult to predict how much time we will be able to dedicate to
|
|
||||||
the project in advance and we don't want to make any promises that we can't
|
|
||||||
keep. For this reason, we are unable to provide ETAs for new features or
|
|
||||||
releases. We make a "best effort" to provide regular releases and fix bugs in a
|
|
||||||
timely fashion, but sometimes our personal lives must take priority.
|
|
||||||
|
|
||||||
ETAs are probably the number one question we (and maintainers of other open
|
|
||||||
source projects) get asked. We understand that you are passionate about the
|
|
||||||
project, but it can be overwhelming to be asked this question so often. Please
|
|
||||||
be patient and avoid asking for ETAs.
|
|
||||||
|
|
||||||
The best way to speed things up is to contribute to the project yourself. We
|
|
||||||
always appreciate new contributors. If you are interested in contributing, check
|
|
||||||
out the [contributing guide](./contributing.mdx).
|
|
||||||
|
|
||||||
## Why won't my task update my shell environment?
|
|
||||||
|
|
||||||
This is a limitation of how shells work. Task runs as a subprocess of your
|
|
||||||
current shell, so it can't change the environment of the shell that started it.
|
|
||||||
This limitation is shared by other task runners and build tools too.
|
|
||||||
|
|
||||||
A common way to work around this is to create a task that will generate output
|
|
||||||
that can be parsed by your shell. For example, to set an environment variable on
|
|
||||||
your shell you can write a task like this:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
my-shell-env:
|
|
||||||
cmds:
|
|
||||||
- echo "export FOO=foo"
|
|
||||||
- echo "export BAR=bar"
|
|
||||||
```
|
|
||||||
|
|
||||||
Now run `eval $(task my-shell-env)` and the variables `$FOO` and `$BAR` will be
|
|
||||||
available in your shell.
|
|
||||||
|
|
||||||
## I can't reuse my shell in a task's commands
|
|
||||||
|
|
||||||
Task runs each command as a separate shell process, so something you do in one
|
|
||||||
command won't effect any future commands. For example, this won't work:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
cmds:
|
|
||||||
- a=foo
|
|
||||||
- echo $a
|
|
||||||
# outputs ""
|
|
||||||
```
|
|
||||||
|
|
||||||
To work around this you can either use a multiline command:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
a=foo
|
|
||||||
echo $a
|
|
||||||
# outputs "foo"
|
|
||||||
```
|
|
||||||
|
|
||||||
Or for more complex multi-line commands it is recommended to move your code into
|
|
||||||
a separate file and call that instead:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
foo:
|
|
||||||
cmds:
|
|
||||||
- ./foo-printer.bash
|
|
||||||
```
|
|
||||||
|
|
||||||
```shell
|
|
||||||
#!/bin/bash
|
|
||||||
a=foo
|
|
||||||
echo $a
|
|
||||||
```
|
|
||||||
|
|
||||||
## 'x' builtin command doesn't work on Windows
|
|
||||||
|
|
||||||
The default shell on Windows (`cmd` and `powershell`) do not have commands like
|
|
||||||
`rm` and `cp` available as builtins. This means that these commands won't work.
|
|
||||||
If you want to make your Taskfile fully cross-platform, you'll need to work
|
|
||||||
around this limitation using one of the following methods:
|
|
||||||
|
|
||||||
- Use the `{{OS}}` function to run an OS-specific script.
|
|
||||||
- Use something like `{{if eq OS "windows"}}powershell {{end}}<my_cmd>` to
|
|
||||||
detect windows and run the command in Powershell directly.
|
|
||||||
- Use a shell on Windows that supports these commands as builtins, such as [Git
|
|
||||||
Bash][git-bash] or [WSL][wsl].
|
|
||||||
|
|
||||||
We want to make improvements to this part of Task and the issues below track
|
|
||||||
this work. Constructive comments and contributions are very welcome!
|
|
||||||
|
|
||||||
- #197
|
|
||||||
- [mvdan/sh#93](https://github.com/mvdan/sh/issues/93)
|
|
||||||
- [mvdan/sh#97](https://github.com/mvdan/sh/issues/97)
|
|
||||||
|
|
||||||
{/* prettier-ignore-start */}
|
|
||||||
[git-bash]: https://gitforwindows.org/
|
|
||||||
[wsl]: https://learn.microsoft.com/en-us/windows/wsl/install
|
|
||||||
{/* prettier-ignore-end */}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user