mirror of
https://github.com/go-task/task.git
synced 2026-05-18 21:26:37 +02:00
Compare commits
84 Commits
split-exec
...
tui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca93c2be86 | ||
|
|
4e84c6bb76 | ||
|
|
0f9baf62a1 | ||
|
|
979ad523ef | ||
|
|
975c07688e | ||
|
|
67a02255b5 | ||
|
|
028ae1a660 | ||
|
|
68b1d2783d | ||
|
|
12793c350d | ||
|
|
8716ab81be | ||
|
|
c2a4e4470b | ||
|
|
f5a8ec8a0c | ||
|
|
048d92709a | ||
|
|
8dc9637e7a | ||
|
|
700bf00107 | ||
|
|
4836d42828 | ||
|
|
5762d5ef8e | ||
|
|
9f2fe0da61 | ||
|
|
d1a5771839 | ||
|
|
7663abdcde | ||
|
|
1e42e1f817 | ||
|
|
5f7ae5d32e | ||
|
|
17db402e4b | ||
|
|
f2242958a6 | ||
|
|
ea4b695b5a | ||
|
|
209c88c341 | ||
|
|
bd94f9f607 | ||
|
|
9f6b78ec84 | ||
|
|
fbde227167 | ||
|
|
fc06e92a87 | ||
|
|
a0cab3f5ec | ||
|
|
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
|
||||
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_size = 2
|
||||
|
||||
41
.github/workflows/lint.yml
vendored
41
.github/workflows/lint.yml
vendored
@@ -13,14 +13,14 @@ jobs:
|
||||
name: Lint
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.23.x, 1.24.x]
|
||||
go-version: [1.24.x, 1.25.x]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
@@ -32,43 +32,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
python-version: 3.13
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: install check-jsonschema
|
||||
run: python -m pip install 'check-jsonschema==0.27.3'
|
||||
|
||||
- name: check-jsonschema (metaschema)
|
||||
run: check-jsonschema --check-metaschema website/static/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.')
|
||||
run: check-jsonschema --check-metaschema website/src/public/schema.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:
|
||||
workflow_dispatch:
|
||||
@@ -9,14 +9,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
go-version: 1.25.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
@@ -27,3 +27,4 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
|
||||
|
||||
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@@ -10,14 +10,26 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
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: go-task/setup-task@v1
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
package_json_file: 'website/package.json'
|
||||
run_install: 'true'
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
@@ -28,3 +40,9 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||
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
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.23.x, 1.24.x]
|
||||
go-version: [1.24.x, 1.25.x]
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{matrix.platform}}
|
||||
steps:
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
|
||||
@@ -68,6 +68,7 @@ nfpms:
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
- apk
|
||||
file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}'
|
||||
contents:
|
||||
- src: completion/bash/task.bash
|
||||
@@ -135,3 +136,37 @@ winget:
|
||||
owner: microsoft
|
||||
name: winget-pkgs
|
||||
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
|
||||
|
||||
13
.vscode/settings-sample.json
vendored
13
.vscode/settings-sample.json
vendored
@@ -1,15 +1,12 @@
|
||||
{
|
||||
"yaml.schemas": {
|
||||
"./website/static/schema.json": [
|
||||
"Taskfile.yml",
|
||||
"tmp/**/*.yml"
|
||||
"./website/src/public/schema.json": [
|
||||
"Taskfile.yml",
|
||||
"Taskfile.yaml",
|
||||
"taskfile.yml",
|
||||
"taskfile.yaml"
|
||||
]
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/versioned_docs": true,
|
||||
"**/versioned_sidesbars": true,
|
||||
"**/i18n": true
|
||||
},
|
||||
"gopls": {
|
||||
"formatting.local": "github.com/go-task"
|
||||
},
|
||||
|
||||
67
CHANGELOG.md
67
CHANGELOG.md
@@ -1,5 +1,72 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Fixed bug that made a generic message, instead of an useful one, appear when
|
||||
a Taskfile could not be found (#2431 by @andreynering).
|
||||
|
||||
## v3.45.4 - 2025-09-17
|
||||
|
||||
- Fixed a bug where `cache-expiry` could not be defined in `.taskrc.yml` (#2423
|
||||
by @vmaerten).
|
||||
- Fixed a bug where `.taskrc.yml` files in parent folders were not read
|
||||
correctly (#2424 by @vmaerten).
|
||||
- Fixed a bug where autocomplete in subfolders did not work with zsh (#2425 by
|
||||
@vmaerten).
|
||||
|
||||
## v3.45.3 - 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.45.0-v3.45.2 - 2025-09-15
|
||||
|
||||
Failed due to an issue with our release process.
|
||||
|
||||
## v3.44.1 - 2025-07-23
|
||||
|
||||
- Internal tasks will no longer be shown as suggestions since they cannot be
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div align="center">
|
||||
<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>
|
||||
|
||||
<h1>Task</h1>
|
||||
@@ -19,7 +19,7 @@
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
25
Taskfile.yml
25
Taskfile.yml
@@ -8,6 +8,7 @@ includes:
|
||||
|
||||
vars:
|
||||
BIN: "{{.ROOT_DIR}}/bin"
|
||||
GOTESTSUM_FORMAT: '{{if .CI}}github-actions{{else}}pkgname{{end}}'
|
||||
|
||||
env:
|
||||
CGO_ENABLED: '0'
|
||||
@@ -131,29 +132,37 @@ tasks:
|
||||
test:
|
||||
desc: Runs test suite
|
||||
aliases: [t]
|
||||
deps: [gotestsum:install]
|
||||
sources:
|
||||
- "**/*.go"
|
||||
- "testdata/**/*"
|
||||
cmds:
|
||||
- go test ./...
|
||||
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./...
|
||||
|
||||
test:watch:
|
||||
desc: Runs test suite with watch tests included
|
||||
deps: [sleepit:build]
|
||||
deps: [sleepit:build, gotestsum:install]
|
||||
cmds:
|
||||
- go test ./... -tags 'watch'
|
||||
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./... -tags 'watch'
|
||||
|
||||
test:all:
|
||||
desc: Runs test suite with signals and watch tests included
|
||||
deps: [sleepit:build]
|
||||
deps: [sleepit:build, gotestsum:install]
|
||||
cmds:
|
||||
- go test -tags 'signals watch' ./...
|
||||
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' -tags 'signals watch' ./...
|
||||
|
||||
goreleaser:test:
|
||||
desc: Tests release process without publishing
|
||||
cmds:
|
||||
- goreleaser --snapshot --clean
|
||||
|
||||
gotestsum:install:
|
||||
desc: Installs gotestsum
|
||||
status:
|
||||
- command -v gotestsum
|
||||
cmds:
|
||||
- go install gotest.tools/gotestsum@latest
|
||||
|
||||
goreleaser:install:
|
||||
desc: Installs goreleaser
|
||||
cmds:
|
||||
@@ -203,7 +212,6 @@ tasks:
|
||||
Please wait for the CI to finish and then do the following:
|
||||
|
||||
- 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
|
||||
preconditions:
|
||||
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
|
||||
@@ -222,8 +230,3 @@ tasks:
|
||||
- "git push origin tag v{{.VERSION}}"
|
||||
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
|
||||
silent: true
|
||||
|
||||
npm:publish:
|
||||
desc: Publish release to npm
|
||||
cmds:
|
||||
- npm publish --access=public
|
||||
|
||||
@@ -3,33 +3,23 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/otiai10/copy"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
changelogSource = "CHANGELOG.md"
|
||||
changelogTarget = "website/docs/changelog.mdx"
|
||||
docsSource = "website/docs"
|
||||
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"
|
||||
changelogSource = "CHANGELOG.md"
|
||||
changelogTarget = "website/src/docs/changelog.md"
|
||||
versionFile = "internal/version/version.txt"
|
||||
)
|
||||
|
||||
var (
|
||||
changelogReleaseRegex = regexp.MustCompile(`## Unreleased`)
|
||||
versionRegex = regexp.MustCompile(`(?m)^ "version": "\d+\.\d+\.\d+",$`)
|
||||
)
|
||||
var changelogReleaseRegex = regexp.MustCompile(`## Unreleased`)
|
||||
|
||||
// Flags
|
||||
var (
|
||||
@@ -53,7 +43,7 @@ func release() error {
|
||||
return errors.New("error: expected version number")
|
||||
}
|
||||
|
||||
version, err := getVersion()
|
||||
version, err := getVersion(versionFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -71,36 +61,18 @@ func release() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setVersionFile("internal/version/version.txt", 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 {
|
||||
if err := setVersionFile(versionFile, version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVersion() (*semver.Version, error) {
|
||||
cmd := exec.Command("git", "describe", "--tags", "--abbrev=0")
|
||||
b, err := cmd.Output()
|
||||
func getVersion(filename string) (*semver.Version, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/flags"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/tui"
|
||||
"github.com/go-task/task/v3/internal/version"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -102,6 +102,10 @@ func run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if flags.TUI {
|
||||
return tui.Run()
|
||||
}
|
||||
|
||||
if flags.Completion != "" {
|
||||
script, err := task.Completion(flags.Completion)
|
||||
if err != nil {
|
||||
@@ -111,63 +115,16 @@ func run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := experiments.Validate(); err != nil {
|
||||
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(
|
||||
e := task.NewExecutor(
|
||||
flags.WithFlags(),
|
||||
taskfile.WithTempDir(tempDir.Remote),
|
||||
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")
|
||||
}),
|
||||
task.WithVersionCheck(true),
|
||||
)
|
||||
|
||||
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}
|
||||
}
|
||||
if err := e.Setup(); err != nil {
|
||||
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 {
|
||||
cachePath := filepath.Join(executor.TempDir.Remote, "remote")
|
||||
cachePath := filepath.Join(e.TempDir.Remote, "remote")
|
||||
return os.RemoveAll(cachePath)
|
||||
}
|
||||
|
||||
@@ -176,12 +133,13 @@ func run() error {
|
||||
flags.ListAll,
|
||||
flags.ListJson,
|
||||
flags.NoStatus,
|
||||
flags.Nested,
|
||||
)
|
||||
if listOptions.ShouldListTasks() {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -213,17 +171,17 @@ func run() error {
|
||||
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
||||
executor.Taskfile.Vars.Merge(globals, nil)
|
||||
e.Taskfile.Vars.Merge(globals, nil)
|
||||
|
||||
if !flags.Watch {
|
||||
executor.InterceptInterruptSignals()
|
||||
e.InterceptInterruptSignals()
|
||||
}
|
||||
|
||||
ctx = context.Background()
|
||||
ctx := context.Background()
|
||||
|
||||
if flags.Status {
|
||||
return executor.Status(ctx, calls...)
|
||||
return e.Status(ctx, calls...)
|
||||
}
|
||||
|
||||
return executor.Run(ctx, calls...)
|
||||
return e.Run(ctx, calls...)
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
|
||||
defer cancel()
|
||||
if err := run(ctx); err != nil {
|
||||
fmt.Println(ctx.Err())
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context) error {
|
||||
req, err := http.NewRequest("GET", "https://taskfile.dev/schema.json", nil)
|
||||
if err != nil {
|
||||
fmt.Println(1)
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
fmt.Println(2)
|
||||
return err
|
||||
}
|
||||
fmt.Println(3)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -16,21 +16,18 @@ function __task_list() {
|
||||
|
||||
|
||||
if [[ -n "$taskfile" && -f "$taskfile" ]]; then
|
||||
enabled=1
|
||||
cmd+=(--taskfile "$taskfile")
|
||||
else
|
||||
for taskfile in {T,t}askfile{,.dist}.{yaml,yml}; do
|
||||
if [[ -f "$taskfile" ]]; then
|
||||
enabled=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
if output=$("${cmd[@]}" $_GO_TASK_COMPLETION_LIST_OPTION 2>/dev/null); then
|
||||
enabled=1
|
||||
fi
|
||||
|
||||
(( enabled )) || return 0
|
||||
|
||||
scripts=()
|
||||
for item in "${(@)${(f)$("${cmd[@]}" $_GO_TASK_COMPLETION_LIST_OPTION)}[2,-1]#\* }"; do
|
||||
for item in "${(@)${(f)output}[2,-1]#\* }"; do
|
||||
task="${item%%:[[:space:]]*}"
|
||||
desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
|
||||
@@ -162,7 +162,7 @@ func (v MissingVar) String() string {
|
||||
}
|
||||
|
||||
func (err *TaskMissingRequiredVarsError) Error() string {
|
||||
var vars []string
|
||||
vars := make([]string, 0, len(err.MissingVars))
|
||||
for _, v := range err.MissingVars {
|
||||
vars = append(vars, v.String())
|
||||
}
|
||||
|
||||
@@ -11,14 +11,18 @@ import (
|
||||
// TaskfileNotFoundError is returned when no appropriate Taskfile is found when
|
||||
// searching the filesystem.
|
||||
type TaskfileNotFoundError struct {
|
||||
URI string
|
||||
Walk bool
|
||||
URI string
|
||||
Walk bool
|
||||
AskInit bool
|
||||
}
|
||||
|
||||
func (err TaskfileNotFoundError) Error() string {
|
||||
var walkText string
|
||||
if err.Walk {
|
||||
walkText = " (or any of the parent directories)"
|
||||
walkText = " (or any of the parent directories)."
|
||||
}
|
||||
if err.AskInit {
|
||||
walkText += " Run `task --init` to create a new Taskfile."
|
||||
}
|
||||
return fmt.Sprintf(`task: No Taskfile found at %q%s`, err.URI, walkText)
|
||||
}
|
||||
|
||||
150
executor.go
150
executor.go
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -27,21 +26,27 @@ type (
|
||||
// within them.
|
||||
Executor struct {
|
||||
// Flags
|
||||
Dir string
|
||||
TempDir *TempDir
|
||||
Force bool
|
||||
ForceAll bool
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
AssumeYes bool
|
||||
AssumeTerm bool // Used for testing
|
||||
Dry bool
|
||||
Summary bool
|
||||
Parallel bool
|
||||
Color bool
|
||||
Concurrency int
|
||||
Interval time.Duration
|
||||
Dir string
|
||||
Entrypoint string
|
||||
TempDir TempDir
|
||||
Force bool
|
||||
ForceAll bool
|
||||
Insecure bool
|
||||
Download bool
|
||||
Offline bool
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
AssumeYes bool
|
||||
AssumeTerm bool // Used for testing
|
||||
Dry bool
|
||||
Summary bool
|
||||
Parallel bool
|
||||
Color bool
|
||||
Concurrency int
|
||||
Interval time.Duration
|
||||
|
||||
// I/O
|
||||
Stdin io.Reader
|
||||
@@ -67,17 +72,17 @@ type (
|
||||
executionHashesMutex sync.Mutex
|
||||
watchedDirs *xsync.MapOf[string, bool]
|
||||
}
|
||||
TempDir struct {
|
||||
Remote string
|
||||
Fingerprint string
|
||||
}
|
||||
)
|
||||
|
||||
// NewExecutor creates a new [Executor] and applies the given functional options
|
||||
// to it.
|
||||
func NewExecutor(graph *ast.TaskfileGraph, opts ...ExecutorOption) (*Executor, error) {
|
||||
tf, err := graph.Merge()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewExecutor(opts ...ExecutorOption) *Executor {
|
||||
e := &Executor{
|
||||
Taskfile: tf,
|
||||
Timeout: time.Second * 10,
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
@@ -95,10 +100,7 @@ func NewExecutor(graph *ast.TaskfileGraph, opts ...ExecutorOption) (*Executor, e
|
||||
executionHashesMutex: sync.Mutex{},
|
||||
}
|
||||
e.Options(opts...)
|
||||
if err := e.setup(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
return e
|
||||
}
|
||||
|
||||
// Options loops through the given [ExecutorOption] functions and applies them
|
||||
@@ -120,23 +122,33 @@ type dirOption struct {
|
||||
}
|
||||
|
||||
func (o *dirOption) ApplyToExecutor(e *Executor) {
|
||||
absDir, err := filepath.Abs(o.dir)
|
||||
if err != nil {
|
||||
e.Dir = o.dir
|
||||
return
|
||||
}
|
||||
e.Dir = absDir
|
||||
e.Dir = o.dir
|
||||
}
|
||||
|
||||
// WithEntrypoint sets the entrypoint (main Taskfile) of the [Executor]. By
|
||||
// default, Task will search for one of the default Taskfiles in the given
|
||||
// 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
|
||||
// storing temporary files like checksums and cached remote files. By default,
|
||||
// the temporary directory is set to the user's temporary directory.
|
||||
func WithTempDir(tempDir *TempDir) ExecutorOption {
|
||||
func WithTempDir(tempDir TempDir) ExecutorOption {
|
||||
return &tempDirOption{tempDir}
|
||||
}
|
||||
|
||||
type tempDirOption struct {
|
||||
tempDir *TempDir
|
||||
tempDir TempDir
|
||||
}
|
||||
|
||||
func (o *tempDirOption) ApplyToExecutor(e *Executor) {
|
||||
@@ -171,6 +183,76 @@ func (o *forceAllOption) ApplyToExecutor(e *Executor) {
|
||||
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 0 (disabled).
|
||||
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
|
||||
// for changes to the fingerprint of the tasks that are run. When changes are
|
||||
// detected, a new task run is triggered.
|
||||
|
||||
314
executor_test.go
314
executor_test.go
@@ -3,11 +3,9 @@ package task_test
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/sebdah/goldie/v2"
|
||||
@@ -16,7 +14,6 @@ import (
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -36,12 +33,7 @@ type (
|
||||
task string
|
||||
vars map[string]any
|
||||
input string
|
||||
nodeDir string
|
||||
nodeEntrypoint string
|
||||
nodeInsecure bool
|
||||
readerOpts []taskfile.ReaderOption
|
||||
executorOpts []task.ExecutorOption
|
||||
wantReaderError bool
|
||||
wantSetupError bool
|
||||
wantRunError bool
|
||||
wantStatusError bool
|
||||
@@ -54,9 +46,8 @@ type (
|
||||
func NewExecutorTest(t *testing.T, opts ...ExecutorTestOption) {
|
||||
t.Helper()
|
||||
tt := &ExecutorTest{
|
||||
task: "default",
|
||||
vars: map[string]any{},
|
||||
nodeDir: ".",
|
||||
task: "default",
|
||||
vars: map[string]any{},
|
||||
TaskTest: TaskTest{
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
fixtureTemplateData: map[string]any{},
|
||||
@@ -153,52 +144,11 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
f := func(t *testing.T) {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a new root node for the given entrypoint
|
||||
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
|
||||
opts := append(
|
||||
tt.executorOpts,
|
||||
// Force the input/output streams to be set to the test buffer
|
||||
[]task.ExecutorOption{
|
||||
task.WithStdout(&buf),
|
||||
task.WithStderr(&buf),
|
||||
},
|
||||
task.WithStdout(&buf),
|
||||
task.WithStderr(&buf),
|
||||
)
|
||||
|
||||
// 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 != "" {
|
||||
var reader bytes.Buffer
|
||||
reader.WriteString(tt.input)
|
||||
executorOpts = append(executorOpts, task.WithStdin(&reader))
|
||||
opts = append(opts, task.WithStdin(&reader))
|
||||
}
|
||||
|
||||
// Set up the task executor
|
||||
executor, err := task.NewExecutor(graph, executorOpts...)
|
||||
if tt.wantSetupError {
|
||||
e := task.NewExecutor(opts...)
|
||||
|
||||
// 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)
|
||||
tt.writeFixtureErrSetup(t, g, err)
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
@@ -231,7 +188,8 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
tt.writeFixtureErrRun(t, g, err)
|
||||
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 tt.wantStatusError {
|
||||
if err := executor.Status(ctx, call); err != nil {
|
||||
if err := e.Status(ctx, call); err != nil {
|
||||
tt.writeFixtureStatus(t, g, err.Error())
|
||||
}
|
||||
}
|
||||
@@ -261,16 +219,19 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
func TestEmptyTask(t *testing.T) {
|
||||
t.Parallel()
|
||||
NewExecutorTest(t,
|
||||
WithNodeDir("testdata/empty_task"),
|
||||
WithExecutorOptions(),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/empty_task"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func TestEmptyTaskfile(t *testing.T) {
|
||||
t.Parallel()
|
||||
NewExecutorTest(t,
|
||||
WithNodeDir("testdata/empty_taskfile"),
|
||||
WithReaderError(),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/empty_taskfile"),
|
||||
),
|
||||
WithSetupError(),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
@@ -279,15 +240,15 @@ func TestEnv(t *testing.T) {
|
||||
t.Setenv("QUX", "from_os")
|
||||
NewExecutorTest(t,
|
||||
WithName("env precedence disabled"),
|
||||
WithNodeDir("testdata/env"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/env"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("env precedence enabled"),
|
||||
WithNodeDir("testdata/env"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/env"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithExperiment(&experiments.EnvPrecedence, 1),
|
||||
@@ -297,8 +258,8 @@ func TestEnv(t *testing.T) {
|
||||
func TestVars(t *testing.T) {
|
||||
t.Parallel()
|
||||
NewExecutorTest(t,
|
||||
WithNodeDir("testdata/vars"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/vars"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
)
|
||||
@@ -308,19 +269,25 @@ func TestRequires(t *testing.T) {
|
||||
t.Parallel()
|
||||
NewExecutorTest(t,
|
||||
WithName("required var missing"),
|
||||
WithNodeDir("testdata/requires"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("missing-var"),
|
||||
WithRunError(),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("required var ok"),
|
||||
WithNodeDir("testdata/requires"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("missing-var"),
|
||||
WithVar("FOO", "bar"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("fails validation"),
|
||||
WithNodeDir("testdata/requires"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("validation-var"),
|
||||
WithVar("ENV", "dev"),
|
||||
WithVar("FOO", "bar"),
|
||||
@@ -328,37 +295,48 @@ func TestRequires(t *testing.T) {
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("passes validation"),
|
||||
WithNodeDir("testdata/requires"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("validation-var"),
|
||||
WithVar("FOO", "one"),
|
||||
WithVar("ENV", "dev"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("required var missing + fails validation"),
|
||||
WithNodeDir("testdata/requires"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("validation-var"),
|
||||
WithRunError(),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("required var missing + fails validation"),
|
||||
WithNodeDir("testdata/requires"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("validation-var-dynamic"),
|
||||
WithVar("FOO", "one"),
|
||||
WithVar("ENV", "dev"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("require before compile"),
|
||||
WithNodeDir("testdata/requires"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("require-before-compile"),
|
||||
WithRunError(),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("var defined in task"),
|
||||
WithNodeDir("testdata/requires"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/requires"),
|
||||
),
|
||||
WithTask("var-defined-in-task"),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: mock fs
|
||||
func TestSpecialVars(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -379,13 +357,12 @@ func TestSpecialVars(t *testing.T) {
|
||||
"included:print-taskfile-dir",
|
||||
}
|
||||
|
||||
for _, executorDir := range []string{dir, subdir} {
|
||||
for _, dir := range []string{dir, subdir} {
|
||||
for _, test := range tests {
|
||||
name := fmt.Sprintf("%s-%s", executorDir, test)
|
||||
NewExecutorTest(t,
|
||||
WithName(name),
|
||||
WithNodeDir(executorDir),
|
||||
WithName(fmt.Sprintf("%s-%s", dir, test)),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
task.WithSilent(true),
|
||||
task.WithVersionCheck(true),
|
||||
),
|
||||
@@ -399,8 +376,8 @@ func TestSpecialVars(t *testing.T) {
|
||||
func TestConcurrency(t *testing.T) {
|
||||
t.Parallel()
|
||||
NewExecutorTest(t,
|
||||
WithNodeDir("testdata/concurrency"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/concurrency"),
|
||||
task.WithConcurrency(1),
|
||||
),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
@@ -410,8 +387,8 @@ func TestConcurrency(t *testing.T) {
|
||||
func TestParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
NewExecutorTest(t,
|
||||
WithNodeDir("testdata/params"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/params"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
@@ -421,14 +398,15 @@ func TestParams(t *testing.T) {
|
||||
func TestDeps(t *testing.T) {
|
||||
t.Parallel()
|
||||
NewExecutorTest(t,
|
||||
WithNodeDir("testdata/deps"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/deps"),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: mock fs
|
||||
func TestStatus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -451,8 +429,8 @@ func TestStatus(t *testing.T) {
|
||||
// gen-foo creates foo.txt, and will always fail it's status check.
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-foo 1 silent"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithTask("gen-foo"),
|
||||
@@ -463,8 +441,8 @@ func TestStatus(t *testing.T) {
|
||||
// only exists after the first run.
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-bar 1 silent"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithTask("gen-bar"),
|
||||
@@ -473,8 +451,8 @@ func TestStatus(t *testing.T) {
|
||||
// if e.Verbose is set to true.
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-baz silent"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
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
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-bar 2 silent"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
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.
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-bar 3 silent"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithTask("gen-bar"),
|
||||
@@ -511,8 +489,8 @@ func TestStatus(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-bar 4 silent"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
task.WithSilent(true),
|
||||
),
|
||||
WithTask("gen-bar"),
|
||||
@@ -520,44 +498,56 @@ func TestStatus(t *testing.T) {
|
||||
// all: not up-to-date
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-foo 2"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
),
|
||||
WithTask("gen-foo"),
|
||||
)
|
||||
// status: not up-to-date
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-foo 3"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
),
|
||||
WithTask("gen-foo"),
|
||||
)
|
||||
// sources: not up-to-date
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-bar 5"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
),
|
||||
WithTask("gen-bar"),
|
||||
)
|
||||
// all: up-to-date
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-bar 6"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
),
|
||||
WithTask("gen-bar"),
|
||||
)
|
||||
// sources: not up-to-date, no output produced.
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-baz 2"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
),
|
||||
WithTask("gen-silent-baz"),
|
||||
)
|
||||
// up-to-date, no output produced
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-baz 3"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
),
|
||||
WithTask("gen-silent-baz"),
|
||||
)
|
||||
// up-to-date, output produced due to Verbose mode.
|
||||
NewExecutorTest(t,
|
||||
WithName("run gen-baz 4 verbose"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
task.WithVerbose(true),
|
||||
),
|
||||
WithTask("gen-silent-baz"),
|
||||
@@ -570,24 +560,32 @@ func TestPrecondition(t *testing.T) {
|
||||
const dir = "testdata/precondition"
|
||||
NewExecutorTest(t,
|
||||
WithName("a precondition has been met"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
),
|
||||
WithTask("foo"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("a precondition was not met"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
),
|
||||
WithTask("impossible"),
|
||||
WithRunError(),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("precondition in dependency fails the task"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
),
|
||||
WithTask("depends_on_impossible"),
|
||||
WithRunError(),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("precondition in cmd fails the task"),
|
||||
WithNodeDir(dir),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(dir),
|
||||
),
|
||||
WithTask("executes_failing_task_as_cmd"),
|
||||
WithRunError(),
|
||||
)
|
||||
@@ -598,21 +596,25 @@ func TestAlias(t *testing.T) {
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("alias"),
|
||||
WithNodeDir("testdata/alias"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/alias"),
|
||||
),
|
||||
WithTask("f"),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("duplicate alias"),
|
||||
WithNodeDir("testdata/alias"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/alias"),
|
||||
),
|
||||
WithTask("x"),
|
||||
WithRunError(),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("alias summary"),
|
||||
WithNodeDir("testdata/alias"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/alias"),
|
||||
task.WithSummary(true),
|
||||
),
|
||||
WithTask("f"),
|
||||
@@ -624,14 +626,16 @@ func TestLabel(t *testing.T) {
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("up to date"),
|
||||
WithNodeDir("testdata/label_uptodate"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/label_uptodate"),
|
||||
),
|
||||
WithTask("foo"),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("summary"),
|
||||
WithNodeDir("testdata/label_summary"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/label_summary"),
|
||||
task.WithSummary(true),
|
||||
),
|
||||
WithTask("foo"),
|
||||
@@ -639,20 +643,26 @@ func TestLabel(t *testing.T) {
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("status"),
|
||||
WithNodeDir("testdata/label_status"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/label_status"),
|
||||
),
|
||||
WithTask("foo"),
|
||||
WithStatusError(),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("var"),
|
||||
WithNodeDir("testdata/label_var"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/label_var"),
|
||||
),
|
||||
WithTask("foo"),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("label in summary"),
|
||||
WithNodeDir("testdata/label_summary"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/label_summary"),
|
||||
),
|
||||
WithTask("foo"),
|
||||
)
|
||||
}
|
||||
@@ -679,8 +689,8 @@ func TestPromptInSummary(t *testing.T) {
|
||||
|
||||
opts := []ExecutorTestOption{
|
||||
WithName(test.name),
|
||||
WithNodeDir("testdata/prompt"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/prompt"),
|
||||
task.WithAssumeTerm(true),
|
||||
),
|
||||
WithTask("foo"),
|
||||
@@ -698,8 +708,8 @@ func TestPromptWithIndirectTask(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithNodeDir("testdata/prompt"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/prompt"),
|
||||
task.WithAssumeTerm(true),
|
||||
),
|
||||
WithTask("bar"),
|
||||
@@ -712,8 +722,8 @@ func TestPromptAssumeYes(t *testing.T) {
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("--yes flag should skip prompt"),
|
||||
WithNodeDir("testdata/prompt"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/prompt"),
|
||||
task.WithAssumeTerm(true),
|
||||
task.WithAssumeYes(true),
|
||||
),
|
||||
@@ -723,8 +733,8 @@ func TestPromptAssumeYes(t *testing.T) {
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("task should raise errors.TaskCancelledError"),
|
||||
WithNodeDir("testdata/prompt"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/prompt"),
|
||||
task.WithAssumeTerm(true),
|
||||
),
|
||||
WithTask("foo"),
|
||||
@@ -761,8 +771,8 @@ func TestForCmds(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
opts := []ExecutorTestOption{
|
||||
WithName(test.name),
|
||||
WithNodeDir("testdata/for/cmds"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/for/cmds"),
|
||||
task.WithSilent(true),
|
||||
task.WithForce(true),
|
||||
),
|
||||
@@ -804,8 +814,8 @@ func TestForDeps(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
opts := []ExecutorTestOption{
|
||||
WithName(test.name),
|
||||
WithNodeDir("testdata/for/deps"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/for/deps"),
|
||||
task.WithSilent(true),
|
||||
task.WithForce(true),
|
||||
// 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 {
|
||||
NewExecutorTest(t,
|
||||
WithName(test.name),
|
||||
WithNodeDir("testdata/var_references"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/var_references"),
|
||||
task.WithSilent(true),
|
||||
task.WithForce(true),
|
||||
),
|
||||
@@ -918,8 +928,8 @@ func TestVarInheritance(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
NewExecutorTest(t,
|
||||
WithName(test.name),
|
||||
WithNodeDir(fmt.Sprintf("testdata/var_inheritance/v3/%s", test.name)),
|
||||
WithExecutorOptions(
|
||||
task.WithDir(fmt.Sprintf("testdata/var_inheritance/v3/%s", test.name)),
|
||||
task.WithSilent(true),
|
||||
task.WithForce(true),
|
||||
),
|
||||
@@ -933,20 +943,26 @@ func TestFuzzyModel(t *testing.T) {
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("fuzzy"),
|
||||
WithNodeDir("testdata/fuzzy"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/fuzzy"),
|
||||
),
|
||||
WithTask("instal"),
|
||||
WithRunError(),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("not-fuzzy"),
|
||||
WithNodeDir("testdata/fuzzy"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/fuzzy"),
|
||||
),
|
||||
WithTask("install"),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("intern"),
|
||||
WithNodeDir("testdata/fuzzy"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/fuzzy"),
|
||||
),
|
||||
WithTask("intern"),
|
||||
WithRunError(),
|
||||
)
|
||||
@@ -957,65 +973,17 @@ func TestIncludeChecksum(t *testing.T) {
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("correct"),
|
||||
WithNodeDir("testdata/includes_checksum/correct"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/includes_checksum/correct"),
|
||||
),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("incorrect"),
|
||||
WithNodeDir("testdata/includes_checksum/incorrect"),
|
||||
WithReaderError(),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/includes_checksum/incorrect"),
|
||||
),
|
||||
WithSetupError(),
|
||||
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/go-task/task/v3/taskrc"
|
||||
"github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
|
||||
const envPrefix = "TASK_X_"
|
||||
@@ -31,16 +32,15 @@ var (
|
||||
var xList []Experiment
|
||||
|
||||
func Parse(dir string) {
|
||||
config, _ := taskrc.GetConfig(dir)
|
||||
|
||||
ParseWithConfig(dir, config)
|
||||
}
|
||||
|
||||
func ParseWithConfig(dir string, config *ast.TaskRC) {
|
||||
// Read any .env files
|
||||
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
|
||||
GentleForce = New("GENTLE_FORCE", config, 1)
|
||||
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)
|
||||
|
||||
@@ -2,9 +2,7 @@ package task_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/sebdah/goldie/v2"
|
||||
@@ -12,7 +10,6 @@ import (
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -29,17 +26,12 @@ type (
|
||||
// running `task gen:fixtures`.
|
||||
FormatterTest struct {
|
||||
TaskTest
|
||||
task string
|
||||
vars map[string]any
|
||||
nodeDir string
|
||||
nodeEntrypoint string
|
||||
nodeInsecure bool
|
||||
readerOpts []taskfile.ReaderOption
|
||||
executorOpts []task.ExecutorOption
|
||||
listOptions task.ListOptions
|
||||
wantReaderError bool
|
||||
wantSetupError bool
|
||||
wantListError bool
|
||||
task string
|
||||
vars map[string]any
|
||||
executorOpts []task.ExecutorOption
|
||||
listOptions task.ListOptions
|
||||
wantSetupError bool
|
||||
wantListError bool
|
||||
}
|
||||
)
|
||||
|
||||
@@ -49,9 +41,8 @@ type (
|
||||
func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) {
|
||||
t.Helper()
|
||||
tt := &FormatterTest{
|
||||
task: "default",
|
||||
vars: map[string]any{},
|
||||
nodeDir: ".",
|
||||
task: "default",
|
||||
vars: map[string]any{},
|
||||
TaskTest: TaskTest{
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
fixtureTemplateData: map[string]any{},
|
||||
@@ -123,57 +114,23 @@ func (tt *FormatterTest) run(t *testing.T) {
|
||||
f := func(t *testing.T) {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a new root node for the given entrypoint
|
||||
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
|
||||
opts := append(
|
||||
tt.executorOpts,
|
||||
// Force the input/output streams to be set to the test buffer
|
||||
[]task.ExecutorOption{
|
||||
task.WithStdout(&buf),
|
||||
task.WithStderr(&buf),
|
||||
},
|
||||
task.WithStdout(&buf),
|
||||
task.WithStderr(&buf),
|
||||
)
|
||||
|
||||
// Set up the task executor
|
||||
executor, err := task.NewExecutor(graph, executorOpts...)
|
||||
if tt.wantSetupError {
|
||||
e := task.NewExecutor(opts...)
|
||||
|
||||
// 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)
|
||||
tt.writeFixtureErrSetup(t, g, err)
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
@@ -189,7 +146,7 @@ func (tt *FormatterTest) run(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
tt.writeFixtureErrList(t, g, err)
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
@@ -213,7 +170,9 @@ func TestNoLabelInList(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithNodeDir("testdata/label_list"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/label_list"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
ListOnlyTasksWithDescriptions: true,
|
||||
}),
|
||||
@@ -225,7 +184,9 @@ func TestListAllShowsNoDesc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithNodeDir("testdata/list_mixed_desc"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/list_mixed_desc"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
ListAllTasks: true,
|
||||
}),
|
||||
@@ -237,7 +198,9 @@ func TestListCanListDescOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithNodeDir("testdata/list_mixed_desc"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/list_mixed_desc"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
ListOnlyTasksWithDescriptions: true,
|
||||
}),
|
||||
@@ -248,7 +211,9 @@ func TestListDescInterpolation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithNodeDir("testdata/list_desc_interpolation"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/list_desc_interpolation"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
ListOnlyTasksWithDescriptions: true,
|
||||
}),
|
||||
@@ -259,7 +224,9 @@ func TestJsonListFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithNodeDir("testdata/json_list_format"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/json_list_format"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
FormatTaskListAsJSON: true,
|
||||
}),
|
||||
|
||||
39
go.mod
39
go.mod
@@ -1,12 +1,16 @@
|
||||
module github.com/go-task/task/v3
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/Ladicle/tabwriter v1.0.0
|
||||
github.com/Masterminds/semver/v3 v3.4.0
|
||||
github.com/alecthomas/chroma/v2 v2.20.0
|
||||
github.com/chainguard-dev/git-urls v1.0.2
|
||||
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1
|
||||
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3
|
||||
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3
|
||||
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250930200525-31788bbe6486
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dominikbraun/graph v0.23.0
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0
|
||||
@@ -19,16 +23,16 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
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/sajari/fuzzy v1.0.0
|
||||
github.com/sebdah/goldie/v2 v2.7.1
|
||||
github.com/spf13/pflag v1.0.7
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/term v0.33.0
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/term v0.35.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20250921194925-171492585d48
|
||||
mvdan.cc/sh/v3 v3.12.0
|
||||
)
|
||||
|
||||
@@ -36,26 +40,45 @@ require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.3.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250501183327-ad3bc78c6a81 // indirect
|
||||
github.com/charmbracelet/x/exp/color v0.0.0-20250915100343-2c2e5896ae6e // indirect
|
||||
github.com/charmbracelet/x/input v0.3.5-0.20250424101541-abb4d9a9b197 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.2.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // 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/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // 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/pgzip v1.2.6 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.1 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // 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/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/crypto v0.37.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
|
||||
)
|
||||
|
||||
91
go.sum
91
go.sum
@@ -11,18 +11,44 @@ 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/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/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/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||
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/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/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
||||
github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ=
|
||||
github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
|
||||
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1 h1:swACzss0FjnyPz1enfX56GKkLiuKg5FlyVmOLIlU2kE=
|
||||
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
|
||||
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3 h1:5A2e3myxXMpCES+kjEWgGsaf9VgZXjZbLi5iMTH7j40=
|
||||
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3/go.mod h1:ZFDg5oPjyRYrPAa3iFrtP1DO8xy+LUQxd9JFHEcuwJY=
|
||||
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
|
||||
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
|
||||
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3 h1:W6DpZX6zSkZr0iFq6JVh1vItLoxfYtNlaxOJtWp8Kis=
|
||||
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3/go.mod h1:65HTtKURcv/ict9ZQhr6zT84JqIjMcJbyrZYHHKNfKA=
|
||||
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
||||
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250501183327-ad3bc78c6a81 h1:iGrflaL5jQW6crML+pZx/ulWAVZQR3CQoRGvFsr2Tyg=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250501183327-ad3bc78c6a81/go.mod h1:poPFOXFTsJsnLbkV3H2KxAAXT7pdjxxLujLocWjkyzM=
|
||||
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250930200525-31788bbe6486 h1:bbbzFbDGxMnOlPyuBoMJwpSmwhO0nV60D1yKBRgzNxg=
|
||||
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250930200525-31788bbe6486/go.mod h1:URW1lCfdUqp9aey3HTV+jRvaGLuaj2d0jVk2NTRpklY=
|
||||
github.com/charmbracelet/x/exp/color v0.0.0-20250915100343-2c2e5896ae6e h1:tgRdEJh8/v6MBs2ZR7g2m1fABjnGft5wXyI+X/l5WF8=
|
||||
github.com/charmbracelet/x/exp/color v0.0.0-20250915100343-2c2e5896ae6e/go.mod h1:/tsSyfR1O2EokQP9iNzNK/fnf5FGdB4w0MOaJTBRp5Q=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a h1:FsHEJ52OC4VuTzU8t+n5frMjLvpYWEznSr/u8tnkCYw=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/input v0.3.5-0.20250424101541-abb4d9a9b197 h1:fsWj8NF5njyMVzELc7++HsvRDvgz3VcgGAUgWBDWWWM=
|
||||
github.com/charmbracelet/x/input v0.3.5-0.20250424101541-abb4d9a9b197/go.mod h1:xseGeVftoP9rVI+/8WKYrJFH6ior6iERGvklwwHz5+s=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/charmbracelet/x/windows v0.2.1 h1:3x7vnbpQrjpuq/4L+I4gNsG5htYoCiA5oe9hLjAij5I=
|
||||
github.com/charmbracelet/x/windows v0.2.1/go.mod h1:ptZp16h40gDYqs5TSawSVW+yiLB13j4kSMA0lSCHL0M=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
@@ -36,6 +62,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/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||
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/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
|
||||
@@ -76,8 +104,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/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/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/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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -85,19 +117,25 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
|
||||
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
|
||||
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -107,8 +145,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
|
||||
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
||||
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
|
||||
@@ -119,18 +162,24 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
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/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.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
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.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
@@ -138,13 +187,13 @@ 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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
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-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
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.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
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/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -154,11 +203,11 @@ 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.5.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.34.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.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
@@ -173,5 +222,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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20250921194925-171492585d48 h1:j0QO6IrQsn7rpqs9HxwwP7ae3uZDbzRZfu0gi8IDFiE=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20250921194925-171492585d48/go.mod h1:Of9PCedbLDYT8b3EyiYG64rNnx5nOp27OLCVdDrjJyo=
|
||||
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=
|
||||
|
||||
65
help.go
65
help.go
@@ -24,15 +24,17 @@ type ListOptions struct {
|
||||
ListAllTasks bool
|
||||
FormatTaskListAsJSON bool
|
||||
NoStatus bool
|
||||
Nested bool
|
||||
}
|
||||
|
||||
// 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{
|
||||
ListOnlyTasksWithDescriptions: list,
|
||||
ListAllTasks: listAll,
|
||||
FormatTaskListAsJSON: listAsJson,
|
||||
NoStatus: noStatus,
|
||||
Nested: nested,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +65,7 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
if o.FormatTaskListAsJSON {
|
||||
output, err := e.ToEditorOutput(tasks, o.NoStatus)
|
||||
output, err := e.ToEditorOutput(tasks, o.NoStatus, o.Nested)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -135,33 +137,17 @@ func (e *Executor) ListTaskNames(allTasks bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Taskfile, error) {
|
||||
o := &editors.Taskfile{
|
||||
Tasks: make([]editors.Task, len(tasks)),
|
||||
Location: e.Taskfile.Location,
|
||||
}
|
||||
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool) (*editors.Namespace, error) {
|
||||
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 {
|
||||
aliases := []string{}
|
||||
if len(tasks[i].Aliases) > 0 {
|
||||
aliases = tasks[i].Aliases
|
||||
}
|
||||
g.Go(func() error {
|
||||
o.Tasks[i] = editors.Task{
|
||||
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,
|
||||
},
|
||||
}
|
||||
editorTask := editors.NewTask(tasks[i])
|
||||
|
||||
if noStatus {
|
||||
editorTasks[i] = editorTask
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -180,10 +166,35 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
||||
return err
|
||||
}
|
||||
|
||||
o.Tasks[i].UpToDate = upToDate
|
||||
|
||||
editorTask.UpToDate = &upToDate
|
||||
editorTasks[i] = editorTask
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
type (
|
||||
// Taskfile wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
||||
Taskfile struct {
|
||||
Tasks []Task `json:"tasks"`
|
||||
Location string `json:"location"`
|
||||
// Namespace wraps task list output for use in editor integrations (e.g. VSCode, etc)
|
||||
Namespace struct {
|
||||
Tasks []Task `json:"tasks"`
|
||||
Namespaces map[string]*Namespace `json:"namespaces,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
}
|
||||
// Task describes a single task
|
||||
Task struct {
|
||||
@@ -13,7 +18,7 @@ type (
|
||||
Desc string `json:"desc"`
|
||||
Summary string `json:"summary"`
|
||||
Aliases []string `json:"aliases"`
|
||||
UpToDate bool `json:"up_to_date"`
|
||||
UpToDate *bool `json:"up_to_date,omitempty"`
|
||||
Location *Location `json:"location"`
|
||||
}
|
||||
// Location describes a task's location in a taskfile
|
||||
@@ -23,3 +28,59 @@ type (
|
||||
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"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"mvdan.cc/sh/moreinterp/coreutils"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
@@ -59,7 +59,7 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
r, err := interp.New(
|
||||
interp.Params(params...),
|
||||
interp.Env(expand.ListEnviron(environ...)),
|
||||
interp.ExecHandlers(execHandler),
|
||||
interp.ExecHandlers(execHandlers()...),
|
||||
interp.OpenHandler(openHandler),
|
||||
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
|
||||
dirOption(opts.Dir),
|
||||
@@ -143,8 +143,11 @@ func ExpandFields(s string) ([]string, error) {
|
||||
return expand.Fields(cfg, words...)
|
||||
}
|
||||
|
||||
func execHandler(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
|
||||
return interp.DefaultExecHandler(15 * time.Second)
|
||||
func execHandlers() (handlers []func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc) {
|
||||
if useGoCoreUtils {
|
||||
handlers = append(handlers, coreutils.ExecHandler)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package fingerprint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -164,7 +163,7 @@ func TestIsTaskUpToDate(t *testing.T) {
|
||||
}
|
||||
|
||||
result, err := IsTaskUpToDate(
|
||||
context.Background(),
|
||||
t.Context(),
|
||||
tt.task,
|
||||
WithStatusChecker(mockStatusChecker),
|
||||
WithSourcesChecker(mockSourcesChecker),
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
@@ -13,10 +12,10 @@ import (
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"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/taskfile"
|
||||
"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...]
|
||||
@@ -45,6 +44,7 @@ var (
|
||||
Version bool
|
||||
Help bool
|
||||
Init bool
|
||||
TUI bool
|
||||
Completion string
|
||||
List bool
|
||||
ListAll bool
|
||||
@@ -52,6 +52,7 @@ var (
|
||||
TaskSort string
|
||||
Status bool
|
||||
NoStatus bool
|
||||
Nested bool
|
||||
Insecure bool
|
||||
Force bool
|
||||
ForceAll bool
|
||||
@@ -96,7 +97,9 @@ func init() {
|
||||
|
||||
// Parse the experiments
|
||||
dir = cmp.Or(dir, filepath.Dir(entrypoint))
|
||||
experiments.Parse(dir)
|
||||
|
||||
config, _ := taskrc.GetConfig(dir)
|
||||
experiments.ParseWithConfig(dir, config)
|
||||
|
||||
// Parse the rest of the flags
|
||||
log.SetFlags(0)
|
||||
@@ -105,13 +108,11 @@ func init() {
|
||||
log.Print(usage)
|
||||
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.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(&TUI, "tui", "T", false, "Runs Task in TUI mode.")
|
||||
pflag.StringVar(&Completion, "completion", "", "Generates shell completion script.")
|
||||
pflag.BoolVarP(&List, "list", "l", false, "Lists tasks with description of current Taskfile.")
|
||||
pflag.BoolVarP(&ListAll, "list-all", "a", false, "Lists tasks with or without a description.")
|
||||
@@ -119,9 +120,10 @@ func init() {
|
||||
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(&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(&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(&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.")
|
||||
@@ -135,7 +137,7 @@ func init() {
|
||||
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.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.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.")
|
||||
@@ -151,12 +153,11 @@ func init() {
|
||||
// Remote Taskfiles experiment will adds the "download" and "offline" flags
|
||||
if experiments.RemoteTaskfiles.Enabled() {
|
||||
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.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote 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", 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.DurationVar(&CacheExpiryDuration, "expiry", 0, "Expiry duration for cached remote Taskfiles.")
|
||||
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
|
||||
}
|
||||
|
||||
pflag.Parse()
|
||||
}
|
||||
|
||||
@@ -197,12 +198,16 @@ func Validate() error {
|
||||
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
|
||||
}
|
||||
|
||||
// WithFlags is a special internal functional option that is used to pass flags
|
||||
// from the CLI into any constructor that accepts functional options.
|
||||
func WithFlags() *flagsOption {
|
||||
func WithFlags() task.ExecutorOption {
|
||||
return &flagsOption{}
|
||||
}
|
||||
|
||||
@@ -229,8 +234,14 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
|
||||
e.Options(
|
||||
task.WithDir(dir),
|
||||
task.WithEntrypoint(Entrypoint),
|
||||
task.WithForce(Force),
|
||||
task.WithForceAll(ForceAll),
|
||||
task.WithInsecure(Insecure),
|
||||
task.WithDownload(Download),
|
||||
task.WithOffline(Offline),
|
||||
task.WithTimeout(Timeout),
|
||||
task.WithCacheExpiryDuration(CacheExpiryDuration),
|
||||
task.WithWatch(Watch),
|
||||
task.WithVerbose(Verbose),
|
||||
task.WithSilent(Silent),
|
||||
@@ -247,11 +258,15 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
)
|
||||
}
|
||||
|
||||
func (o *flagsOption) ApplyToReader(r *taskfile.Reader) {
|
||||
r.Options(
|
||||
taskfile.WithInsecure(Insecure),
|
||||
taskfile.WithDownload(Download),
|
||||
taskfile.WithOffline(Offline),
|
||||
taskfile.WithCacheExpiryDuration(CacheExpiryDuration),
|
||||
)
|
||||
// getConfig extracts a config value directly from a pointer field with a fallback default
|
||||
func getConfig[T any](config *taskrcast.TaskRC, fieldFunc func() *T, fallback T) T {
|
||||
if config == nil {
|
||||
return fallback
|
||||
}
|
||||
|
||||
field := fieldFunc()
|
||||
if field != nil {
|
||||
return *field
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
@@ -37,51 +37,87 @@ func DefaultDir(entrypoint, dir string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Search will look for files with the given possible filenames using the given
|
||||
// entrypoint and directory. If the entrypoint is set, it will check if the
|
||||
// ResolveDir returns an absolute path to the directory that the task should be
|
||||
// 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
|
||||
// possible filenames. Otherwise, it will walk up the file tree starting at the
|
||||
// given directory and perform a search in each directory for the possible
|
||||
// possible filenames. Otherwise, it 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 will default the directory to the
|
||||
// current working directory and perform a recursive search starting there. If a
|
||||
// match is found, the absolute path to the file will be returned with its
|
||||
// directory. If no match is found, an error will be returned.
|
||||
func Search(entrypoint, dir string, possibleFilenames []string) (string, string, error) {
|
||||
// entrypoint and directory are both empty, it defaults the directory to the
|
||||
// current working directory and performs a recursive search starting there. If
|
||||
// a match is found, the absolute path to the file is returned with its
|
||||
// directory. If no match is found, an error is returned.
|
||||
func Search(entrypoint, dir string, possibleFilenames []string) (string, error) {
|
||||
var err error
|
||||
if entrypoint != "" {
|
||||
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", err
|
||||
}
|
||||
if dir == "" {
|
||||
dir = filepath.Dir(entrypoint)
|
||||
} else {
|
||||
dir, err = filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
return entrypoint, dir, nil
|
||||
return entrypoint, nil
|
||||
}
|
||||
if dir == "" {
|
||||
dir, err = os.Getwd()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
entrypoint, err = SearchPathRecursively(dir, possibleFilenames)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", err
|
||||
}
|
||||
dir = filepath.Dir(entrypoint)
|
||||
return entrypoint, dir, nil
|
||||
return entrypoint, nil
|
||||
}
|
||||
|
||||
// Search 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
|
||||
// SearchAll 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
|
||||
// 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.
|
||||
func SearchPath(path string, possibleFilenames []string) (string, error) {
|
||||
// Get file info about the path
|
||||
@@ -111,36 +147,56 @@ func SearchPath(path string, possibleFilenames []string) (string, error) {
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
|
||||
// SearchRecursively will check if a file at the given path exists by calling
|
||||
// the exists function. If a file is not found, it will walk up the directory
|
||||
// tree calling the Search function until it finds a file or reaches the root
|
||||
// directory. On supported operating systems, it will also check if the user ID
|
||||
// of the directory changes and abort if it does.
|
||||
// SearchPathRecursively walks up the directory tree starting at the given
|
||||
// path, calling the Search function in each directory until it finds a matching
|
||||
// file or reaches the root directory. On supported operating systems, it will
|
||||
// also check if the user ID of the directory changes and abort if it does.
|
||||
func SearchPathRecursively(path string, possibleFilenames []string) (string, error) {
|
||||
owner, err := sysinfo.Owner(path)
|
||||
paths, err := SearchNPathRecursively(path, possibleFilenames, 1)
|
||||
if err != nil {
|
||||
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)
|
||||
if err == nil {
|
||||
return fpath, nil
|
||||
paths = append(paths, fpath)
|
||||
}
|
||||
|
||||
// Get the parent path/user id
|
||||
parentPath := filepath.Dir(path)
|
||||
parentOwner, err := sysinfo.Owner(parentPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Error if we reached the root directory and still haven't found a file
|
||||
// OR if the user id of the directory changes
|
||||
if path == parentPath || (parentOwner != owner) {
|
||||
return "", os.ErrNotExist
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
owner = parentOwner
|
||||
path = parentPath
|
||||
}
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
@@ -71,35 +71,30 @@ func TestSearch(t *testing.T) {
|
||||
dir string
|
||||
possibleFilenames []string
|
||||
expectedEntrypoint string
|
||||
expectedDir string
|
||||
}{
|
||||
{
|
||||
name: "find foo.txt using relative entrypoint",
|
||||
entrypoint: "./testdata/foo.txt",
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: 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"),
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using relative dir",
|
||||
dir: "./testdata",
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using absolute dir",
|
||||
dir: filepath.Join(wd, "testdata"),
|
||||
possibleFilenames: []string{"foo.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt using relative dir and relative entrypoint",
|
||||
@@ -107,7 +102,6 @@ func TestSearch(t *testing.T) {
|
||||
dir: "./testdata/some/other/dir",
|
||||
possibleFilenames: []string{"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",
|
||||
@@ -115,7 +109,6 @@ func TestSearch(t *testing.T) {
|
||||
dir: "",
|
||||
possibleFilenames: []string{"fs.go"},
|
||||
expectedEntrypoint: filepath.Join(wd, "fs.go"),
|
||||
expectedDir: wd,
|
||||
},
|
||||
{
|
||||
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
|
||||
@@ -123,30 +116,109 @@ func TestSearch(t *testing.T) {
|
||||
dir: "",
|
||||
possibleFilenames: []string{"Taskfile.yml"},
|
||||
expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
|
||||
expectedDir: filepath.Join(wd, "..", ".."),
|
||||
},
|
||||
{
|
||||
name: "find foo.txt first if listed first in possible filenames",
|
||||
entrypoint: "./testdata",
|
||||
possibleFilenames: []string{"foo.txt", "bar.txt"},
|
||||
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
|
||||
expectedDir: filepath.Join(wd, "testdata"),
|
||||
},
|
||||
{
|
||||
name: "find bar.txt first if listed first in possible filenames",
|
||||
entrypoint: "./testdata",
|
||||
possibleFilenames: []string{"bar.txt", "foo.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"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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.Equal(t, tt.expectedEntrypoint, entrypoint)
|
||||
require.Equal(t, tt.expectedDir, dir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
43
internal/tui/list.go
Normal file
43
internal/tui/list.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/charmbracelet/bubbles/v2/list"
|
||||
"github.com/charmbracelet/bubbles/v2/spinner"
|
||||
tea "github.com/charmbracelet/bubbletea/v2"
|
||||
)
|
||||
|
||||
type listModel struct {
|
||||
spinner spinner.Model
|
||||
list list.Model
|
||||
selectedIndex int
|
||||
isLoading bool
|
||||
}
|
||||
|
||||
func newListModel() listModel {
|
||||
return listModel{
|
||||
spinner: newSpinner(),
|
||||
list: list.New([]list.Item{}, list.NewDefaultDelegate(), 0, 0),
|
||||
isLoading: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (m listModel) Init() tea.Cmd {
|
||||
return m.spinner.Tick
|
||||
}
|
||||
|
||||
func (m listModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
m.spinner, cmd = m.spinner.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m listModel) View() string {
|
||||
switch {
|
||||
case m.isLoading:
|
||||
return fmt.Sprintf("%s %s", m.spinner.View(), "Loading tasks...")
|
||||
default:
|
||||
return "todo!"
|
||||
}
|
||||
}
|
||||
59
internal/tui/main.go
Normal file
59
internal/tui/main.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea/v2"
|
||||
)
|
||||
|
||||
type page int
|
||||
|
||||
const (
|
||||
pageList = iota + 1
|
||||
)
|
||||
|
||||
type mainModel struct {
|
||||
page page
|
||||
listPage listModel
|
||||
isQuitting bool
|
||||
}
|
||||
|
||||
func newMainModel() mainModel {
|
||||
return mainModel{
|
||||
page: pageList,
|
||||
listPage: newListModel(),
|
||||
}
|
||||
}
|
||||
|
||||
func (m mainModel) Init() tea.Cmd {
|
||||
return m.listPage.Init()
|
||||
}
|
||||
|
||||
func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "ctrl+q":
|
||||
m.isQuitting = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
switch m.page {
|
||||
case pageList:
|
||||
model, cmd := m.listPage.Update(msg)
|
||||
m.listPage = model.(listModel)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m mainModel) View() string {
|
||||
switch {
|
||||
case m.isQuitting:
|
||||
return "Quitting..."
|
||||
case m.page == pageList:
|
||||
return m.listPage.View()
|
||||
default:
|
||||
return "..."
|
||||
}
|
||||
}
|
||||
16
internal/tui/spinner.go
Normal file
16
internal/tui/spinner.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/v2/spinner"
|
||||
"github.com/charmbracelet/lipgloss/v2"
|
||||
"github.com/charmbracelet/x/exp/charmtone"
|
||||
)
|
||||
|
||||
var spinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(charmtone.Violet.Hex()))
|
||||
|
||||
func newSpinner() spinner.Model {
|
||||
return spinner.New(
|
||||
spinner.WithSpinner(spinner.MiniDot),
|
||||
spinner.WithStyle(spinnerStyle),
|
||||
)
|
||||
}
|
||||
13
internal/tui/tui.go
Normal file
13
internal/tui/tui.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea/v2"
|
||||
)
|
||||
|
||||
func Run() error {
|
||||
m := newMainModel()
|
||||
p := tea.NewProgram(m)
|
||||
|
||||
_, err := p.Run()
|
||||
return err
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
3.44.1
|
||||
3.45.4
|
||||
|
||||
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,19 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/sajari/fuzzy"
|
||||
|
||||
"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/fsext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/internal/version"
|
||||
@@ -18,8 +24,18 @@ import (
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
func (e *Executor) setup() error {
|
||||
func (e *Executor) Setup() error {
|
||||
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.setupStdFiles()
|
||||
if err := e.setupOutput(); err != nil {
|
||||
@@ -39,6 +55,53 @@ func (e *Executor) setup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) getRootNode() (taskfile.Node, error) {
|
||||
node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.TaskfileNotFoundError{
|
||||
URI: fsext.DefaultDir(e.Entrypoint, e.Dir),
|
||||
Walk: true,
|
||||
AskInit: true,
|
||||
}
|
||||
}
|
||||
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() {
|
||||
if e.Taskfile == nil {
|
||||
return
|
||||
@@ -60,6 +123,52 @@ func (e *Executor) setupFuzzyModel() {
|
||||
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() {
|
||||
if e.Stdin == nil {
|
||||
e.Stdin = os.Stdin
|
||||
@@ -105,7 +214,7 @@ func (e *Executor) setupCompiler() error {
|
||||
|
||||
e.Compiler = &Compiler{
|
||||
Dir: e.Dir,
|
||||
Entrypoint: e.Taskfile.Location,
|
||||
Entrypoint: e.Entrypoint,
|
||||
UserWorkingDir: e.UserWorkingDir,
|
||||
TaskfileEnv: e.Taskfile.Env,
|
||||
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 != "" {
|
||||
method = t.Method
|
||||
}
|
||||
|
||||
upToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
|
||||
fingerprint.WithMethod(method),
|
||||
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
||||
@@ -467,7 +466,6 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
|
||||
DidYouMean: didYouMean,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
IncludeVars *Vars
|
||||
IncludedTaskfileVars *Vars
|
||||
|
||||
FullName string
|
||||
}
|
||||
|
||||
func (t *Task) Name() string {
|
||||
if t.Label != "" {
|
||||
return t.Label
|
||||
}
|
||||
if t.FullName != "" {
|
||||
return t.FullName
|
||||
}
|
||||
return t.Task
|
||||
}
|
||||
|
||||
func (t *Task) LocalName() string {
|
||||
name := t.Task
|
||||
name := t.FullName
|
||||
name = strings.TrimPrefix(name, t.Namespace)
|
||||
name = strings.TrimPrefix(name, ":")
|
||||
return name
|
||||
@@ -220,6 +225,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
Location: t.Location.DeepCopy(),
|
||||
Requires: t.Requires.DeepCopy(),
|
||||
Namespace: t.Namespace,
|
||||
FullName: t.FullName,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ type Var struct {
|
||||
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
key := node.Content[0].Value
|
||||
key := "<none>"
|
||||
if len(node.Content) > 0 {
|
||||
key = node.Content[0].Value
|
||||
}
|
||||
switch key {
|
||||
case "sh", "ref", "map":
|
||||
var m struct {
|
||||
|
||||
@@ -3,6 +3,7 @@ package taskfile
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
giturls "github.com/chainguard-dev/git-urls"
|
||||
|
||||
@@ -32,6 +33,7 @@ func NewRootNode(
|
||||
entrypoint string,
|
||||
dir string,
|
||||
insecure bool,
|
||||
timeout time.Duration,
|
||||
) (Node, error) {
|
||||
dir = fsext.DefaultDir(entrypoint, dir)
|
||||
// If the entrypoint is "-", we read from stdin
|
||||
|
||||
@@ -18,15 +18,21 @@ type FileNode struct {
|
||||
}
|
||||
|
||||
func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
|
||||
var err error
|
||||
base := NewBaseNode(dir, opts...)
|
||||
entrypoint, base.dir, err = fsext.Search(entrypoint, base.dir, defaultTaskfiles)
|
||||
// Find the entrypoint file
|
||||
resolvedEntrypoint, err := fsext.Search(entrypoint, dir, defaultTaskfiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Resolve the directory
|
||||
resolvedDir, err := fsext.ResolveDir(entrypoint, resolvedEntrypoint, dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FileNode{
|
||||
baseNode: base,
|
||||
entrypoint: entrypoint,
|
||||
baseNode: NewBaseNode(resolvedDir, opts...),
|
||||
entrypoint: resolvedEntrypoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ func (node *HTTPNode) ReadContext(ctx context.Context) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("GET", url.String(), nil)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url.String(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.TaskfileFetchFailedError{URI: node.Location()}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,48 @@
|
||||
package ast
|
||||
|
||||
import "github.com/Masterminds/semver/v3"
|
||||
import (
|
||||
"cmp"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
)
|
||||
|
||||
type TaskRC struct {
|
||||
Version *semver.Version `yaml:"version"`
|
||||
Verbose *bool `yaml:"verbose"`
|
||||
Concurrency *int `yaml:"concurrency"`
|
||||
Remote Remote `yaml:"remote"`
|
||||
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
|
||||
|
||||
import "github.com/go-task/task/v3/internal/fsext"
|
||||
import (
|
||||
"github.com/go-task/task/v3/internal/fsext"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
entrypoint string
|
||||
dir string
|
||||
}
|
||||
|
||||
func NewNode(
|
||||
entrypoint string,
|
||||
dir string,
|
||||
possibleFileNames []string,
|
||||
) (*Node, error) {
|
||||
dir = fsext.DefaultDir(entrypoint, dir)
|
||||
var err error
|
||||
entrypoint, dir, err = fsext.Search(entrypoint, dir, defaultTaskRCs)
|
||||
resolvedEntrypoint, err := fsext.SearchPath(dir, possibleFileNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Node{
|
||||
entrypoint: entrypoint,
|
||||
dir: dir,
|
||||
entrypoint: resolvedEntrypoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,92 @@
|
||||
package taskrc
|
||||
|
||||
var defaultTaskRCs = []string{
|
||||
".taskrc.yml",
|
||||
".taskrc.yaml",
|
||||
import (
|
||||
"os"
|
||||
"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
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entrypoints, err := fsext.SearchAll("", absDir, 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:
|
||||
- ./generated.txt
|
||||
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:
|
||||
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
|
||||
cmds:
|
||||
- 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 {
|
||||
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}
|
||||
|
||||
new := ast.Task{
|
||||
Task: origTask.Task,
|
||||
Label: templater.Replace(origTask.Label, cache),
|
||||
@@ -76,6 +81,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
Requires: origTask.Requires,
|
||||
Watch: origTask.Watch,
|
||||
Namespace: origTask.Namespace,
|
||||
FullName: fullName,
|
||||
}
|
||||
new.Dir, err = execext.ExpandLiteral(new.Dir)
|
||||
if err != nil {
|
||||
|
||||
18
website/.gitignore
vendored
18
website/.gitignore
vendored
@@ -1,21 +1,9 @@
|
||||
# Dependencies
|
||||
/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*
|
||||
yarn-debug.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>
|
||||
333
website/.vitepress/config.ts
Normal file
333
website/.vitepress/config.ts
Normal file
@@ -0,0 +1,333 @@
|
||||
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",
|
||||
{
|
||||
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:
|
||||
yarn:install:
|
||||
desc: Setup Docusaurus locally
|
||||
install:
|
||||
desc: Setup VitePress locally
|
||||
cmds:
|
||||
- yarn install
|
||||
- pnpm install
|
||||
sources:
|
||||
- package.json
|
||||
- yarn.lock
|
||||
- pnpm-lock.yaml
|
||||
|
||||
default:
|
||||
desc: Start website
|
||||
deps: [yarn:install]
|
||||
deps: [install]
|
||||
aliases: [s, start]
|
||||
vars:
|
||||
HOST: '{{default "0.0.0.0" .HOST}}'
|
||||
PORT: '{{default "3001" .PORT}}'
|
||||
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:
|
||||
desc: Build website
|
||||
deps: [yarn:install]
|
||||
deps: [install]
|
||||
cmds:
|
||||
- npx docusaurus build
|
||||
- pnpm build
|
||||
|
||||
preview:
|
||||
desc: Preview Website
|
||||
@@ -33,20 +39,19 @@ tasks:
|
||||
HOST: '{{default "localhost" .HOST}}'
|
||||
PORT: '{{default "3001" .PORT}}'
|
||||
cmds:
|
||||
- npx docusaurus serve --no-open --host={{.HOST}} --port={{.PORT}}
|
||||
- pnpm preview --host={{.HOST}} --port={{.PORT}}
|
||||
|
||||
clean:
|
||||
desc: Clean temp directories
|
||||
cmds:
|
||||
- rm -rf ./build
|
||||
- rm -rf ./vitepress/dist
|
||||
|
||||
deploy:
|
||||
desc: Build and deploy Docusaurus
|
||||
summary: Requires GIT_USER and GIT_PASS envs to be previous set
|
||||
deploy:next:
|
||||
desc: Build and deploy next.taskfile.dev
|
||||
cmds:
|
||||
- npx docusaurus deploy
|
||||
- pnpm netlify deploy --prod --site=4e13dfcf-fc0d-4bec-ad60-b918a8dc3942
|
||||
|
||||
upgrade:
|
||||
desc: Upgrade Docusaurus
|
||||
deploy:prod:
|
||||
desc: Build and deploy taskfile.dev
|
||||
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 */}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user