mirror of
https://github.com/go-task/task.git
synced 2026-05-18 13:15:41 +02:00
Compare commits
79 Commits
v3.43.0
...
bump-versi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c30a8066d | ||
|
|
26ef693417 | ||
|
|
952f32d388 | ||
|
|
e72c35f79f | ||
|
|
72991d4f04 | ||
|
|
6f965e3043 | ||
|
|
1c6d686356 | ||
|
|
dac5aa1954 | ||
|
|
303bd6ccb2 | ||
|
|
f736cfaaf1 | ||
|
|
53f97889bc | ||
|
|
fe2da74ea3 | ||
|
|
64fb66895b | ||
|
|
d2bd834c81 | ||
|
|
8a43ca5d8f | ||
|
|
a10a9faabf | ||
|
|
3d3ed0e403 | ||
|
|
47dc87a2c9 | ||
|
|
3b0a746f85 | ||
|
|
281edfe5b3 | ||
|
|
7289ffce0b | ||
|
|
61e1af50ff | ||
|
|
715a143735 | ||
|
|
a0b1605634 | ||
|
|
69fc13bd13 | ||
|
|
b42a52ba77 | ||
|
|
cb812476b3 | ||
|
|
b09c6870fe | ||
|
|
86e4a3aac7 | ||
|
|
7782bc92ae | ||
|
|
9cc2d65091 | ||
|
|
b932e539d9 | ||
|
|
be45eb04d9 | ||
|
|
6b878980dc | ||
|
|
cd910abd45 | ||
|
|
6e524bb2fa | ||
|
|
b4c8f5a0fe | ||
|
|
09f85844ba | ||
|
|
d54d2ccabc | ||
|
|
cf81ab3112 | ||
|
|
aaa7b7772d | ||
|
|
71eb8cdeea | ||
|
|
68ce8b1d84 | ||
|
|
5323990c72 | ||
|
|
ec4e68d601 | ||
|
|
bb5b045293 | ||
|
|
89f29cb75b | ||
|
|
da4ce5b0a5 | ||
|
|
fb68a5f79a | ||
|
|
f40f389cb4 | ||
|
|
a459eeaabb | ||
|
|
84f02a822f | ||
|
|
55d1aa260d | ||
|
|
e7084cdf26 | ||
|
|
ca55e9b621 | ||
|
|
6528b36caa | ||
|
|
f8736c5f77 | ||
|
|
6896accf86 | ||
|
|
c12ed49acb | ||
|
|
d1bfd3e9f7 | ||
|
|
fc17343fcc | ||
|
|
d3e9be1520 | ||
|
|
d850d03c96 | ||
|
|
0058f18676 | ||
|
|
b3c4007756 | ||
|
|
9e8fd54be9 | ||
|
|
a33544101a | ||
|
|
1c35358fcc | ||
|
|
13daa6dc35 | ||
|
|
20c1ffe098 | ||
|
|
bd8ccb8d03 | ||
|
|
8162b05f59 | ||
|
|
68d5095761 | ||
|
|
6cb0a5a2f2 | ||
|
|
08056924e0 | ||
|
|
39706105e1 | ||
|
|
bf4e7960cb | ||
|
|
3d36616e9e | ||
|
|
3976e8372a |
20
.github/workflows/lint.yml
vendored
20
.github/workflows/lint.yml
vendored
@@ -23,9 +23,9 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v7
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: v2.0.2
|
||||
version: v2.1.0
|
||||
|
||||
lint-jsonschema:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -56,3 +56,19 @@ jobs:
|
||||
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.')
|
||||
|
||||
29
.github/workflows/release-nightly.yml
vendored
Normal file
29
.github/workflows/release-nightly.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Realease nightly
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
args: release --clean --nightly
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||
@@ -5,8 +5,10 @@ formatters:
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gci
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
rewrite-rules:
|
||||
- pattern: interface{}
|
||||
replacement: any
|
||||
@@ -15,6 +17,12 @@ formatters:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/go-task
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/go-task)
|
||||
- localmodule
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
|
||||
@@ -36,7 +36,7 @@ gomod:
|
||||
proxy: true
|
||||
|
||||
archives:
|
||||
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}"
|
||||
- name_template: '{{.Binary}}_{{.Os}}_{{.Arch}}'
|
||||
files:
|
||||
- README.md
|
||||
- LICENSE
|
||||
@@ -48,24 +48,35 @@ archives:
|
||||
release:
|
||||
draft: true
|
||||
|
||||
|
||||
git:
|
||||
ignore_tags:
|
||||
- "{{if not .IsNightly}}nightly{{end}}"
|
||||
|
||||
nightly:
|
||||
publish_release: true
|
||||
keep_single_release: true
|
||||
version_template: "{{incminor .Version}}-nightly"
|
||||
|
||||
snapshot:
|
||||
version_template: "{{.Version}}"
|
||||
version_template: '{{.Version}}'
|
||||
|
||||
checksum:
|
||||
name_template: "task_checksums.txt"
|
||||
name_template: 'task_checksums.txt'
|
||||
|
||||
nfpms:
|
||||
- vendor: Task
|
||||
homepage: https://taskfile.dev
|
||||
maintainer: The Task authors <task@taskfile.dev>
|
||||
description: Simple task runner written in Go
|
||||
section: golang
|
||||
license: MIT
|
||||
conflicts:
|
||||
- taskwarrior
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}"
|
||||
file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}'
|
||||
contents:
|
||||
- src: completion/bash/task.bash
|
||||
dst: /etc/bash_completion.d/task
|
||||
@@ -83,8 +94,7 @@ brews:
|
||||
repository:
|
||||
owner: go-task
|
||||
name: homebrew-tap
|
||||
test:
|
||||
system "#{bin}/task", "--help"
|
||||
test: system "#{bin}/task", "--help"
|
||||
install: |-
|
||||
bin.install "task"
|
||||
bash_completion.install "completion/bash/task.bash" => "task"
|
||||
@@ -107,7 +117,7 @@ winget:
|
||||
commit_author:
|
||||
name: task-bot
|
||||
email: 106601941+task-bot@users.noreply.github.com
|
||||
commit_msg_template: "chore: bump {{.PackageIdentifier}} to {{.Tag}}"
|
||||
commit_msg_template: 'chore: release {{.PackageIdentifier}} {{.Tag}}'
|
||||
release_notes_url: https://github.com/go-task/task/releases/tag/{{.Tag}}
|
||||
tags:
|
||||
- build
|
||||
@@ -121,13 +131,15 @@ winget:
|
||||
- task-runner
|
||||
- taskfile
|
||||
- tool
|
||||
skip_upload: true
|
||||
repository:
|
||||
owner: microsoft
|
||||
owner: go-task
|
||||
name: winget-pkgs
|
||||
branch: 'chore/task-{{.Version}}'
|
||||
pull_request:
|
||||
enabled: true
|
||||
draft: false
|
||||
check_boxes: true
|
||||
base:
|
||||
owner: go-task
|
||||
owner: microsoft
|
||||
name: winget-pkgs
|
||||
branch: "bump-task-to-{{.Tag}}"
|
||||
branch: master
|
||||
|
||||
53
CHANGELOG.md
53
CHANGELOG.md
@@ -1,6 +1,57 @@
|
||||
# Changelog
|
||||
|
||||
## v3.43.0 - 2025-04-21
|
||||
## v3.44.1 - 2025-07-23
|
||||
|
||||
- Internal tasks will no longer be shown as suggestions since they cannot be
|
||||
called (#2309, #2323 by @maxmzkrcensys)
|
||||
- Fixed install script for some ARM platforms (#1516, #2291 by @trulede).
|
||||
- Fixed a regression where fingerprinting was not working correctly if the path
|
||||
to you Taskfile contained a space (#2321, #2322 by @pd93).
|
||||
- Reverted a breaking change to `randInt` (#2312, #2316 by @pd93).
|
||||
- Made new variables `TEST_NAME` and `TEST_DIR` available in fixture tests
|
||||
(#2265 by @pd93).
|
||||
|
||||
## v3.44.0 - 2025-06-08
|
||||
|
||||
- Added `uuid`, `randInt` and `randIntN` template functions (#1346, #2225 by
|
||||
@pd93).
|
||||
- Added new `CLI_ARGS_LIST` array variable which contains the arguments passed
|
||||
to Task after the `--` (the same as `CLI_ARGS`, but an array instead of a
|
||||
string). (#2138, #2139, #2140 by @pd93).
|
||||
- Added `toYaml` and `fromYaml` templating functions (#2217, #2219 by @pd93).
|
||||
- Added `task` field the `--list --json` output (#2256 by @aleksandersh).
|
||||
- Added the ability to
|
||||
[pin included taskfiles](https://taskfile.dev/next/experiments/remote-taskfiles/#manual-checksum-pinning)
|
||||
by specifying a checksum. This works with both local and remote Taskfiles
|
||||
(#2222, #2223 by @pd93).
|
||||
- When using the
|
||||
[Remote Taskfiles experiment](https://github.com/go-task/task/issues/1317),
|
||||
any credentials used in the URL will now be redacted in Task's output (#2100,
|
||||
#2220 by @pd93).
|
||||
- Fixed fuzzy suggestions not working when misspelling a task name (#2192, #2200
|
||||
by @vmaerten).
|
||||
- Fixed a bug where taskfiles in directories containing spaces created
|
||||
directories in the wrong location (#2208, #2216 by @pd93).
|
||||
- Added support for dual JSON schema files, allowing changes without affecting
|
||||
the current schema. The current schemas will only be updated during releases.
|
||||
(#2211 by @vmaerten).
|
||||
- Improved fingerprint documentation by specifying that the method can be set at
|
||||
the root level to apply to all tasks (#2233 by @vmaerten).
|
||||
- Fixed some watcher regressions after #2048 (#2199, #2202, #2241, #2196 by
|
||||
@wazazaby, #2271 by @andreynering).
|
||||
|
||||
## v3.43.3 - 2025-04-27
|
||||
|
||||
Reverted the changes made in #2113 and #2186 that affected the
|
||||
`USER_WORKING_DIR` and built-in variables. This fixes #2206, #2195, #2207 and
|
||||
#2208.
|
||||
|
||||
## v3.43.2 - 2025-04-21
|
||||
|
||||
- Fixed regresion of `CLI_ARGS` being exposed as the wrong type (#2190, #2191 by
|
||||
@vmaerten).
|
||||
|
||||
## v3.43.1 - 2025-04-21
|
||||
|
||||
- Significant improvements were made to the watcher. We migrated from
|
||||
[watcher](https://github.com/radovskyb/watcher) to
|
||||
|
||||
16
Taskfile.yml
16
Taskfile.yml
@@ -53,9 +53,12 @@ tasks:
|
||||
generate:fixtures:
|
||||
desc: Runs tests and generates golden fixture files
|
||||
aliases: [gen:fixtures, g:fixtures]
|
||||
env:
|
||||
GOLDIE_UPDATE: 'true'
|
||||
GOLDIE_TEMPLATE: 'true'
|
||||
cmds:
|
||||
- find ./testdata -name '*.golden' -delete
|
||||
- go test -update ./...
|
||||
- go test ./...
|
||||
|
||||
install:mockery:
|
||||
desc: Installs mockgen; a tool to generate mock files
|
||||
@@ -87,6 +90,7 @@ tasks:
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- .golangci.yml
|
||||
- go.mod
|
||||
cmds:
|
||||
- golangci-lint run
|
||||
|
||||
@@ -95,9 +99,19 @@ tasks:
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- .golangci.yml
|
||||
- go.mod
|
||||
cmds:
|
||||
- golangci-lint run --fix
|
||||
|
||||
format:
|
||||
desc: Runs golangci-lint and formats any Go files
|
||||
aliases: [fmt, f]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
- .golangci.yml
|
||||
cmds:
|
||||
- golangci-lint fmt
|
||||
|
||||
sleepit:build:
|
||||
desc: Builds the sleepit test helper
|
||||
sources:
|
||||
|
||||
24
args/args.go
24
args/args.go
@@ -20,17 +20,7 @@ func Get() ([]string, []string, error) {
|
||||
if doubleDashPos == -1 {
|
||||
return args, nil, nil
|
||||
}
|
||||
|
||||
var quotedCliArgs []string
|
||||
for _, arg := range args[doubleDashPos:] {
|
||||
quotedCliArg, err := syntax.Quote(arg, syntax.LangBash)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
quotedCliArgs = append(quotedCliArgs, quotedCliArg)
|
||||
}
|
||||
|
||||
return args[:doubleDashPos], quotedCliArgs, nil
|
||||
return args[:doubleDashPos], args[doubleDashPos:], nil
|
||||
}
|
||||
|
||||
// Parse parses command line argument: tasks and global variables
|
||||
@@ -51,6 +41,18 @@ func Parse(args ...string) ([]*task.Call, *ast.Vars) {
|
||||
return calls, globals
|
||||
}
|
||||
|
||||
func ToQuotedString(args []string) (string, error) {
|
||||
var quotedCliArgs []string
|
||||
for _, arg := range args {
|
||||
quotedCliArg, err := syntax.Quote(arg, syntax.LangBash)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
quotedCliArgs = append(quotedCliArgs, quotedCliArg)
|
||||
}
|
||||
return strings.Join(quotedCliArgs, " "), nil
|
||||
}
|
||||
|
||||
func splitVar(s string) (string, string) {
|
||||
pair := strings.SplitN(s, "=", 2)
|
||||
return pair[0], pair[1]
|
||||
|
||||
@@ -16,10 +16,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
changelogSource = "CHANGELOG.md"
|
||||
changelogTarget = "website/docs/changelog.mdx"
|
||||
docsSource = "website/docs"
|
||||
docsTarget = "website/versioned_docs/version-latest"
|
||||
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"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -83,6 +87,10 @@ func release() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := schema(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -175,3 +183,13 @@ func docs() error {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/args"
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/flags"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
@@ -76,7 +76,7 @@ func run() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, args, err := args.Get()
|
||||
args, _, err := args.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -144,18 +144,23 @@ func run() error {
|
||||
}
|
||||
|
||||
// Parse the remaining arguments
|
||||
argv, cliArgs, err := args.Get()
|
||||
cliArgsPreDash, cliArgsPostDash, err := args.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
calls, globals := args.Parse(argv...)
|
||||
calls, globals := args.Parse(cliArgsPreDash...)
|
||||
|
||||
// If there are no calls, run the default task instead
|
||||
if len(calls) == 0 {
|
||||
calls = append(calls, &task.Call{Task: "default"})
|
||||
}
|
||||
|
||||
globals.Set("CLI_ARGS", ast.Var{Value: cliArgs})
|
||||
cliArgsPostDashQuoted, err := args.ToQuotedString(cliArgsPostDash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
globals.Set("CLI_ARGS", ast.Var{Value: cliArgsPostDashQuoted})
|
||||
globals.Set("CLI_ARGS_LIST", ast.Var{Value: cliArgsPostDash})
|
||||
globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
CodeTaskfileNetworkTimeout
|
||||
CodeTaskfileInvalid
|
||||
CodeTaskfileCycle
|
||||
CodeTaskfileDoesNotMatchChecksum
|
||||
)
|
||||
|
||||
// Task related exit codes
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -46,8 +47,9 @@ func (err *TaskRunError) Code() int {
|
||||
}
|
||||
|
||||
func (err *TaskRunError) TaskExitCode() int {
|
||||
if c, ok := interp.IsExitStatus(err.Err); ok {
|
||||
return int(c)
|
||||
var exit interp.ExitStatus
|
||||
if errors.As(err.Err, &exit) {
|
||||
return int(exit)
|
||||
}
|
||||
return err.Code()
|
||||
}
|
||||
|
||||
@@ -187,3 +187,24 @@ func (err TaskfileCycleError) Error() string {
|
||||
func (err TaskfileCycleError) Code() int {
|
||||
return CodeTaskfileCycle
|
||||
}
|
||||
|
||||
// TaskfileDoesNotMatchChecksum is returned when a Taskfile's checksum does not
|
||||
// match the one pinned in the parent Taskfile.
|
||||
type TaskfileDoesNotMatchChecksum struct {
|
||||
URI string
|
||||
ExpectedChecksum string
|
||||
ActualChecksum string
|
||||
}
|
||||
|
||||
func (err *TaskfileDoesNotMatchChecksum) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"task: The checksum of the Taskfile at %q does not match!\ngot: %q\nwant: %q",
|
||||
err.URI,
|
||||
err.ActualChecksum,
|
||||
err.ExpectedChecksum,
|
||||
)
|
||||
}
|
||||
|
||||
func (err *TaskfileDoesNotMatchChecksum) Code() int {
|
||||
return CodeTaskfileDoesNotMatchChecksum
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -123,7 +122,6 @@ type dirOption struct {
|
||||
}
|
||||
|
||||
func (o *dirOption) ApplyToExecutor(e *Executor) {
|
||||
e.UserWorkingDir, _ = filepath.Abs(o.dir)
|
||||
e.Dir = o.dir
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
@@ -50,7 +50,8 @@ func NewExecutorTest(t *testing.T, opts ...ExecutorTestOption) {
|
||||
task: "default",
|
||||
vars: map[string]any{},
|
||||
TaskTest: TaskTest{
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
fixtureTemplateData: map[string]any{},
|
||||
},
|
||||
}
|
||||
// Apply the functional options
|
||||
@@ -232,7 +233,7 @@ func TestEmptyTaskfile(t *testing.T) {
|
||||
task.WithDir("testdata/empty_taskfile"),
|
||||
),
|
||||
WithSetupError(),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -367,7 +368,7 @@ func TestSpecialVars(t *testing.T) {
|
||||
task.WithVersionCheck(true),
|
||||
),
|
||||
WithTask(test),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -551,7 +552,7 @@ func TestStatus(t *testing.T) {
|
||||
task.WithVerbose(true),
|
||||
),
|
||||
WithTask("gen-silent-baz"),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -777,7 +778,7 @@ func TestForCmds(t *testing.T) {
|
||||
task.WithForce(true),
|
||||
),
|
||||
WithTask(test.name),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
WithFixtureTemplating(),
|
||||
}
|
||||
if test.wantErr {
|
||||
opts = append(opts, WithRunError())
|
||||
@@ -822,7 +823,7 @@ func TestForDeps(t *testing.T) {
|
||||
task.WithOutputStyle(ast.Output{Name: "group"}),
|
||||
),
|
||||
WithTask(test.name),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
WithFixtureTemplating(),
|
||||
WithPostProcessFn(PPSortedLines),
|
||||
}
|
||||
if test.wantErr {
|
||||
@@ -937,3 +938,53 @@ func TestVarInheritance(t *testing.T) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuzzyModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("fuzzy"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/fuzzy"),
|
||||
),
|
||||
WithTask("instal"),
|
||||
WithRunError(),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("not-fuzzy"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/fuzzy"),
|
||||
),
|
||||
WithTask("install"),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("intern"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/fuzzy"),
|
||||
),
|
||||
WithTask("intern"),
|
||||
WithRunError(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestIncludeChecksum(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("correct"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/includes_checksum/correct"),
|
||||
),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("incorrect"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/includes_checksum/incorrect"),
|
||||
),
|
||||
WithSetupError(),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -44,7 +44,8 @@ func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) {
|
||||
task: "default",
|
||||
vars: map[string]any{},
|
||||
TaskTest: TaskTest{
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
experiments: map[*experiments.Experiment]int{},
|
||||
fixtureTemplateData: map[string]any{},
|
||||
},
|
||||
}
|
||||
// Apply the functional options
|
||||
@@ -218,3 +219,17 @@ func TestListDescInterpolation(t *testing.T) {
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func TestJsonListFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
NewFormatterTest(t,
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/json_list_format"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
FormatTaskListAsJSON: true,
|
||||
}),
|
||||
WithFixtureTemplating(),
|
||||
)
|
||||
}
|
||||
|
||||
21
go.mod
21
go.mod
@@ -4,8 +4,8 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/Ladicle/tabwriter v1.0.0
|
||||
github.com/Masterminds/semver/v3 v3.3.1
|
||||
github.com/alecthomas/chroma/v2 v2.16.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/davecgh/go-spew v1.1.1
|
||||
github.com/dominikbraun/graph v0.23.0
|
||||
@@ -13,22 +13,23 @@ require (
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/go-git/go-billy/v5 v5.6.2
|
||||
github.com/go-git/go-git/v5 v5.16.0
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0
|
||||
github.com/go-task/template v0.1.0
|
||||
github.com/go-task/template v0.2.0
|
||||
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.5.5
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/sebdah/goldie/v2 v2.7.1
|
||||
github.com/spf13/pflag v1.0.7
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
golang.org/x/sync v0.13.0
|
||||
golang.org/x/term v0.31.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/term v0.33.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/sh/v3 v3.11.0
|
||||
mvdan.cc/sh/v3 v3.12.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -55,6 +56,6 @@ require (
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
72
go.sum
72
go.sum
@@ -2,21 +2,19 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
|
||||
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
|
||||
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
||||
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
|
||||
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||
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.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
|
||||
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
|
||||
github.com/alecthomas/chroma/v2 v2.16.0 h1:QC5ZMizk67+HzxFDjQ4ASjni5kWBTGiigRG1u23IGvA=
|
||||
github.com/alecthomas/chroma/v2 v2.16.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
||||
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/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
@@ -25,8 +23,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
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/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
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,8 +32,6 @@ github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGL
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
|
||||
@@ -50,8 +44,6 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
@@ -62,22 +54,20 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
|
||||
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
|
||||
github.com/go-git/go-git/v5 v5.15.0 h1:f5Qn0W0F7ry1iN0ZwIU5m/n7/BKB4hiZfc+zlZx7ly0=
|
||||
github.com/go-git/go-git/v5 v5.15.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ=
|
||||
github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
||||
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-task/template v0.1.0 h1:ym/r2G937RZA1bsgiWedNnY9e5kxDT+3YcoAnuIetTE=
|
||||
github.com/go-task/template v0.1.0/go.mod h1:RgwRaZK+kni/hJJ7/AaOE2lPQFPbAdji/DyhC6pxo4k=
|
||||
github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviBE=
|
||||
github.com/go-task/template v0.2.0/go.mod h1:dbdoUb6qKnHQi1y6o+IdIrs0J4o/SEhSTA6bbzZmdtc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
@@ -121,16 +111,16 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
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.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
|
||||
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
|
||||
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
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.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/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=
|
||||
@@ -146,21 +136,15 @@ github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
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/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
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=
|
||||
@@ -170,18 +154,14 @@ 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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
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/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -193,5 +173,5 @@ 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/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
|
||||
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
|
||||
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=
|
||||
|
||||
1
help.go
1
help.go
@@ -149,6 +149,7 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Ta
|
||||
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,
|
||||
|
||||
@@ -64,21 +64,15 @@ get_binaries() {
|
||||
case "$PLATFORM" in
|
||||
darwin/amd64) BINARIES="task" ;;
|
||||
darwin/arm64) BINARIES="task" ;;
|
||||
darwin/armv5) BINARIES="task" ;;
|
||||
darwin/armv6) BINARIES="task" ;;
|
||||
darwin/armv7) BINARIES="task" ;;
|
||||
darwin/arm) BINARIES="task" ;;
|
||||
linux/386) BINARIES="task" ;;
|
||||
linux/amd64) BINARIES="task" ;;
|
||||
linux/arm64) BINARIES="task" ;;
|
||||
linux/armv5) BINARIES="task" ;;
|
||||
linux/armv6) BINARIES="task" ;;
|
||||
linux/armv7) BINARIES="task" ;;
|
||||
linux/arm) BINARIES="task" ;;
|
||||
windows/386) BINARIES="task" ;;
|
||||
windows/amd64) BINARIES="task" ;;
|
||||
windows/arm64) BINARIES="task" ;;
|
||||
windows/armv5) BINARIES="task" ;;
|
||||
windows/armv6) BINARIES="task" ;;
|
||||
windows/armv7) BINARIES="task" ;;
|
||||
windows/arm) BINARIES="task" ;;
|
||||
*)
|
||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
||||
exit 1
|
||||
|
||||
@@ -9,6 +9,7 @@ type (
|
||||
// Task describes a single task
|
||||
Task struct {
|
||||
Name string `json:"name"`
|
||||
Task string `json:"task"`
|
||||
Desc string `json:"desc"`
|
||||
Summary string `json:"summary"`
|
||||
Aliases []string `json:"aliases"`
|
||||
|
||||
2
internal/env/env.go
vendored
2
internal/env/env.go
vendored
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
|
||||
@@ -106,25 +106,17 @@ func ExpandLiteral(s string) (string, error) {
|
||||
if s == "" {
|
||||
return "", nil
|
||||
}
|
||||
s = escape(s)
|
||||
p := syntax.NewParser()
|
||||
var words []*syntax.Word
|
||||
err := p.Words(strings.NewReader(s), func(w *syntax.Word) bool {
|
||||
words = append(words, w)
|
||||
return true
|
||||
})
|
||||
word, err := p.Document(strings.NewReader(s))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(words) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
cfg := &expand.Config{
|
||||
Env: expand.FuncEnviron(os.Getenv),
|
||||
ReadDir2: os.ReadDir,
|
||||
GlobStar: true,
|
||||
}
|
||||
return expand.Literal(cfg, words[0])
|
||||
return expand.Literal(cfg, word)
|
||||
}
|
||||
|
||||
// ExpandFields is a wrapper around [expand.Fields]. It will escape the input
|
||||
@@ -146,6 +138,7 @@ func ExpandFields(s string) ([]string, error) {
|
||||
Env: expand.FuncEnviron(os.Getenv),
|
||||
ReadDir2: os.ReadDir,
|
||||
GlobStar: true,
|
||||
NullGlob: true,
|
||||
}
|
||||
return expand.Fields(cfg, words...)
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ 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/experiments"
|
||||
"github.com/go-task/task/v3/internal/sort"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@ package fsnotifyext
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
@@ -11,7 +10,6 @@ import (
|
||||
type Deduper struct {
|
||||
w *fsnotify.Watcher
|
||||
waitTime time.Duration
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewDeduper(w *fsnotify.Watcher, waitTime time.Duration) *Deduper {
|
||||
@@ -21,31 +19,28 @@ func NewDeduper(w *fsnotify.Watcher, waitTime time.Duration) *Deduper {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Deduper) GetChan() chan fsnotify.Event {
|
||||
// GetChan returns a chan of deduplicated [fsnotify.Event].
|
||||
//
|
||||
// [fsnotify.Chmod] operations will be skipped.
|
||||
func (d *Deduper) GetChan() <-chan fsnotify.Event {
|
||||
channel := make(chan fsnotify.Event)
|
||||
timers := make(map[string]*time.Timer)
|
||||
|
||||
go func() {
|
||||
timers := make(map[string]*time.Timer)
|
||||
for {
|
||||
event, ok := <-d.w.Events
|
||||
switch {
|
||||
case !ok:
|
||||
return
|
||||
case event.Op == fsnotify.Chmod:
|
||||
case event.Has(fsnotify.Chmod):
|
||||
continue
|
||||
}
|
||||
|
||||
d.mutex.Lock()
|
||||
timer, ok := timers[event.String()]
|
||||
d.mutex.Unlock()
|
||||
|
||||
if !ok {
|
||||
timer = time.AfterFunc(math.MaxInt64, func() { channel <- event })
|
||||
timer.Stop()
|
||||
|
||||
d.mutex.Lock()
|
||||
timers[event.String()] = timer
|
||||
d.mutex.Unlock()
|
||||
}
|
||||
|
||||
timer.Reset(d.waitTime)
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"github.com/fatih/color"
|
||||
|
||||
"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/experiments"
|
||||
"github.com/go-task/task/v3/internal/term"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@ package templater
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"math/rand/v2"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/google/uuid"
|
||||
"gopkg.in/yaml.v3"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
|
||||
@@ -18,58 +21,27 @@ var templateFuncs template.FuncMap
|
||||
|
||||
func init() {
|
||||
taskFuncs := template.FuncMap{
|
||||
"OS": func() string { return runtime.GOOS },
|
||||
"ARCH": func() string { return runtime.GOARCH },
|
||||
"numCPU": func() int { return runtime.NumCPU() },
|
||||
"catLines": func(s string) string {
|
||||
s = strings.ReplaceAll(s, "\r\n", " ")
|
||||
return strings.ReplaceAll(s, "\n", " ")
|
||||
},
|
||||
"splitLines": func(s string) []string {
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||
return strings.Split(s, "\n")
|
||||
},
|
||||
"fromSlash": func(path string) string {
|
||||
return filepath.FromSlash(path)
|
||||
},
|
||||
"toSlash": func(path string) string {
|
||||
return filepath.ToSlash(path)
|
||||
},
|
||||
"exeExt": func() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return ".exe"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"shellQuote": func(str string) (string, error) {
|
||||
return syntax.Quote(str, syntax.LangBash)
|
||||
},
|
||||
"splitArgs": func(s string) ([]string, error) {
|
||||
return shell.Fields(s, nil)
|
||||
},
|
||||
// IsSH is deprecated.
|
||||
"IsSH": func() bool { return true },
|
||||
"joinPath": func(elem ...string) string {
|
||||
return filepath.Join(elem...)
|
||||
},
|
||||
"relPath": func(basePath, targetPath string) (string, error) {
|
||||
return filepath.Rel(basePath, targetPath)
|
||||
},
|
||||
"merge": func(base map[string]any, v ...map[string]any) map[string]any {
|
||||
cap := len(v)
|
||||
for _, m := range v {
|
||||
cap += len(m)
|
||||
}
|
||||
result := make(map[string]any, cap)
|
||||
maps.Copy(result, base)
|
||||
for _, m := range v {
|
||||
maps.Copy(result, m)
|
||||
}
|
||||
return result
|
||||
},
|
||||
"spew": func(v any) string {
|
||||
return spew.Sdump(v)
|
||||
},
|
||||
"OS": os,
|
||||
"ARCH": arch,
|
||||
"numCPU": runtime.NumCPU,
|
||||
"catLines": catLines,
|
||||
"splitLines": splitLines,
|
||||
"fromSlash": filepath.FromSlash,
|
||||
"toSlash": filepath.ToSlash,
|
||||
"exeExt": exeExt,
|
||||
"shellQuote": shellQuote,
|
||||
"splitArgs": splitArgs,
|
||||
"IsSH": IsSH, // Deprecated
|
||||
"joinPath": filepath.Join,
|
||||
"relPath": filepath.Rel,
|
||||
"merge": merge,
|
||||
"spew": spew.Sdump,
|
||||
"fromYaml": fromYaml,
|
||||
"mustFromYaml": mustFromYaml,
|
||||
"toYaml": toYaml,
|
||||
"mustToYaml": mustToYaml,
|
||||
"uuid": uuid.New,
|
||||
"randIntN": rand.IntN,
|
||||
}
|
||||
|
||||
// aliases
|
||||
@@ -83,3 +55,78 @@ func init() {
|
||||
templateFuncs = template.FuncMap(sprig.TxtFuncMap())
|
||||
maps.Copy(templateFuncs, taskFuncs)
|
||||
}
|
||||
|
||||
func os() string {
|
||||
return runtime.GOOS
|
||||
}
|
||||
|
||||
func arch() string {
|
||||
return runtime.GOARCH
|
||||
}
|
||||
|
||||
func catLines(s string) string {
|
||||
s = strings.ReplaceAll(s, "\r\n", " ")
|
||||
return strings.ReplaceAll(s, "\n", " ")
|
||||
}
|
||||
|
||||
func splitLines(s string) []string {
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||
return strings.Split(s, "\n")
|
||||
}
|
||||
|
||||
func exeExt() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return ".exe"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func shellQuote(str string) (string, error) {
|
||||
return syntax.Quote(str, syntax.LangBash)
|
||||
}
|
||||
|
||||
func splitArgs(s string) ([]string, error) {
|
||||
return shell.Fields(s, nil)
|
||||
}
|
||||
|
||||
// Deprecated: now always returns true
|
||||
func IsSH() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func merge(base map[string]any, v ...map[string]any) map[string]any {
|
||||
cap := len(v)
|
||||
for _, m := range v {
|
||||
cap += len(m)
|
||||
}
|
||||
result := make(map[string]any, cap)
|
||||
maps.Copy(result, base)
|
||||
for _, m := range v {
|
||||
maps.Copy(result, m)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func fromYaml(v string) any {
|
||||
output, _ := mustFromYaml(v)
|
||||
return output
|
||||
}
|
||||
|
||||
func mustFromYaml(v string) (any, error) {
|
||||
var output any
|
||||
err := yaml.Unmarshal([]byte(v), &output)
|
||||
return output, err
|
||||
}
|
||||
|
||||
func toYaml(v any) string {
|
||||
output, _ := yaml.Marshal(v)
|
||||
return string(output)
|
||||
}
|
||||
|
||||
func mustToYaml(v any) (string, error) {
|
||||
output, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ import (
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/template"
|
||||
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
"github.com/go-task/template"
|
||||
)
|
||||
|
||||
// Cache is a help struct that allow us to call "replaceX" funcs multiple
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
_ "embed"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -46,6 +48,10 @@ func getCommit(info *debug.BuildInfo) string {
|
||||
// However, it can also be overridden at build time using:
|
||||
// -ldflags="-X 'github.com/go-task/task/v3/internal/version.version=vX.X.X'".
|
||||
func GetVersion() string {
|
||||
// If its a development version, we bump the minor version.
|
||||
if commit != "" || dirty {
|
||||
return semver.MustParse(version).IncMinor().String()
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
@@ -61,7 +67,7 @@ func GetVersionWithBuildInfo() string {
|
||||
buildMetadata = append(buildMetadata, "dirty")
|
||||
}
|
||||
if len(buildMetadata) > 0 {
|
||||
return version + "+" + strings.Join(buildMetadata, ".")
|
||||
return GetVersion() + "+" + strings.Join(buildMetadata, ".")
|
||||
}
|
||||
return version
|
||||
return GetVersion()
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.43.0
|
||||
3.44.1
|
||||
|
||||
58
internal/version/version_test.go
Normal file
58
internal/version/version_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestVersionTxt(t *testing.T) {
|
||||
// Check that the version.txt is a valid semver version.
|
||||
require.NotEmpty(t, GetVersion(), "version.txt is not semver compliant")
|
||||
}
|
||||
|
||||
func TestGetVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
version string
|
||||
commit string
|
||||
dirty bool
|
||||
want string
|
||||
}{
|
||||
{"1.2.3", "", false, "1.2.3"},
|
||||
{"1.2.3", "", true, "1.3.0"},
|
||||
{"1.2.3", "abcdefg", false, "1.3.0"},
|
||||
{"1.2.3", "abcdefg", true, "1.3.0"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
version = tt.version
|
||||
commit = tt.commit
|
||||
dirty = tt.dirty
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, GetVersion())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVersionWithBuildInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
version string
|
||||
commit string
|
||||
dirty bool
|
||||
want string
|
||||
}{
|
||||
{"1.2.3", "", false, "1.2.3"},
|
||||
{"1.2.3", "", true, "1.3.0+dirty"},
|
||||
{"1.2.3", "abcdefg", false, "1.3.0+abcdefg"},
|
||||
{"1.2.3", "abcdefg", true, "1.3.0+abcdefg.dirty"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
version = tt.version
|
||||
commit = tt.commit
|
||||
dirty = tt.dirty
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, GetVersionWithBuildInfo())
|
||||
})
|
||||
}
|
||||
}
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.43.0",
|
||||
"version": "3.44.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.43.0",
|
||||
"version": "3.44.1",
|
||||
"description": "A task runner / simpler Make alternative written in Go",
|
||||
"scripts": {
|
||||
"postinstall": "go-npm install",
|
||||
|
||||
5
setup.go
5
setup.go
@@ -95,7 +95,7 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
|
||||
}
|
||||
|
||||
func (e *Executor) setupFuzzyModel() {
|
||||
if e.Taskfile != nil {
|
||||
if e.Taskfile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -104,6 +104,9 @@ func (e *Executor) setupFuzzyModel() {
|
||||
|
||||
var words []string
|
||||
for name, task := range e.Taskfile.Tasks.All(nil) {
|
||||
if task.Internal {
|
||||
continue
|
||||
}
|
||||
words = append(words, name)
|
||||
words = slices.Concat(words, task.Aliases)
|
||||
}
|
||||
|
||||
15
task.go
15
task.go
@@ -8,6 +8,9 @@ import (
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
@@ -19,9 +22,6 @@ import (
|
||||
"github.com/go-task/task/v3/internal/summary"
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -220,13 +220,13 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: error cleaning status on error: %v\n", err2)
|
||||
}
|
||||
|
||||
exitCode, isExitError := interp.IsExitStatus(err)
|
||||
if isExitError {
|
||||
var exitCode interp.ExitStatus
|
||||
if errors.As(err, &exitCode) {
|
||||
if t.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: task error ignored: %v\n", err)
|
||||
continue
|
||||
}
|
||||
deferredExitCode = exitCode
|
||||
deferredExitCode = uint8(exitCode)
|
||||
}
|
||||
|
||||
if call.Indirect {
|
||||
@@ -356,7 +356,8 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
|
||||
if closeErr := closer(err); closeErr != nil {
|
||||
e.Logger.Errf(logger.Red, "task: unable to close writer: %v\n", closeErr)
|
||||
}
|
||||
if _, isExitError := interp.IsExitStatus(err); isExitError && cmd.IgnoreError {
|
||||
var exitCode interp.ExitStatus
|
||||
if errors.As(err, &exitCode) && cmd.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] command error ignored: %v\n", t.Name(), err)
|
||||
return nil
|
||||
}
|
||||
|
||||
91
task_test.go
91
task_test.go
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"maps"
|
||||
rand "math/rand/v2"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -27,7 +28,7 @@ import (
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
@@ -42,9 +43,11 @@ type (
|
||||
FormatterTestOption
|
||||
}
|
||||
TaskTest struct {
|
||||
name string
|
||||
experiments map[*experiments.Experiment]int
|
||||
postProcessFns []PostProcessFn
|
||||
name string
|
||||
experiments map[*experiments.Experiment]int
|
||||
postProcessFns []PostProcessFn
|
||||
fixtureTemplateData map[string]any
|
||||
fixtureTemplatingEnabled bool
|
||||
}
|
||||
)
|
||||
|
||||
@@ -79,7 +82,22 @@ func (tt *TaskTest) writeFixture(
|
||||
if goldenFileSuffix != "" {
|
||||
goldenFileName += "-" + goldenFileSuffix
|
||||
}
|
||||
g.Assert(t, goldenFileName, b)
|
||||
// Create a set of data to be made available to every test fixture
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
if tt.fixtureTemplatingEnabled {
|
||||
fixtureTemplateData := map[string]any{
|
||||
"TEST_NAME": t.Name(),
|
||||
"TEST_DIR": wd,
|
||||
}
|
||||
// If the test has additional template data, copy it into the map
|
||||
if tt.fixtureTemplateData != nil {
|
||||
maps.Copy(fixtureTemplateData, tt.fixtureTemplateData)
|
||||
}
|
||||
g.AssertWithTemplate(t, goldenFileName, fixtureTemplateData, b)
|
||||
} else {
|
||||
g.Assert(t, goldenFileName, b)
|
||||
}
|
||||
}
|
||||
|
||||
// writeFixtureBuffer is a wrapper for writing the main output of the task to a
|
||||
@@ -234,23 +252,52 @@ func (opt *setupErrorTestOption) applyToFormatterTest(t *FormatterTest) {
|
||||
t.wantSetupError = true
|
||||
}
|
||||
|
||||
// WithFixtureTemplating enables templating for the golden fixture files with
|
||||
// the default set of data. This is useful if the golden file is dynamic in some
|
||||
// way (e.g. contains user-specific directories). To add more data, see
|
||||
// WithFixtureTemplateData.
|
||||
func WithFixtureTemplating() TestOption {
|
||||
return &fixtureTemplatingTestOption{}
|
||||
}
|
||||
|
||||
type fixtureTemplatingTestOption struct{}
|
||||
|
||||
func (opt *fixtureTemplatingTestOption) applyToExecutorTest(t *ExecutorTest) {
|
||||
t.fixtureTemplatingEnabled = true
|
||||
}
|
||||
|
||||
func (opt *fixtureTemplatingTestOption) applyToFormatterTest(t *FormatterTest) {
|
||||
t.fixtureTemplatingEnabled = true
|
||||
}
|
||||
|
||||
// WithFixtureTemplateData adds data to the golden fixture file templates. Keys
|
||||
// given here will override any existing values. This option will also enable
|
||||
// global templating, so you do not need to call WithFixtureTemplating as well.
|
||||
func WithFixtureTemplateData(key string, value any) TestOption {
|
||||
return &fixtureTemplateDataTestOption{key, value}
|
||||
}
|
||||
|
||||
type fixtureTemplateDataTestOption struct {
|
||||
k string
|
||||
v any
|
||||
}
|
||||
|
||||
func (opt *fixtureTemplateDataTestOption) applyToExecutorTest(t *ExecutorTest) {
|
||||
t.fixtureTemplatingEnabled = true
|
||||
t.fixtureTemplateData[opt.k] = opt.v
|
||||
}
|
||||
|
||||
func (opt *fixtureTemplateDataTestOption) applyToFormatterTest(t *FormatterTest) {
|
||||
t.fixtureTemplatingEnabled = true
|
||||
t.fixtureTemplateData[opt.k] = opt.v
|
||||
}
|
||||
|
||||
// Post-processing
|
||||
|
||||
// A PostProcessFn is a function that can be applied to the output of a test
|
||||
// fixture before the file is written.
|
||||
type PostProcessFn func(*testing.T, []byte) []byte
|
||||
|
||||
// PPRemoveAbsolutePaths removes any absolute paths from the output of the task.
|
||||
// This is useful when the task output contains paths that are can be different
|
||||
// in different environments such as home directories. The function looks for
|
||||
// any paths that contain the current working directory and truncates them.
|
||||
func PPRemoveAbsolutePaths(t *testing.T, b []byte) []byte {
|
||||
t.Helper()
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
return bytes.ReplaceAll(b, []byte(wd), nil)
|
||||
}
|
||||
|
||||
// PPSortedLines sorts the lines of the output of the task. This is useful when
|
||||
// the order of the output is not important, but the output is expected to be
|
||||
// the same each time the task is run (e.g. when running tasks in parallel).
|
||||
@@ -702,6 +749,7 @@ func TestIncludesRemote(t *testing.T) {
|
||||
enableExperimentForTest(t, &experiments.RemoteTaskfiles, 1)
|
||||
|
||||
dir := "testdata/includes_remote"
|
||||
os.RemoveAll(filepath.Join(dir, ".task", "remote"))
|
||||
|
||||
srv := httptest.NewServer(http.FileServer(http.Dir(dir)))
|
||||
defer srv.Close()
|
||||
@@ -777,8 +825,8 @@ func TestIncludesRemote(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
for j, e := range executors {
|
||||
t.Run(fmt.Sprint(j), func(t *testing.T) {
|
||||
for _, e := range executors {
|
||||
t.Run(e.name, func(t *testing.T) {
|
||||
require.NoError(t, e.executor.Setup())
|
||||
|
||||
for k, taskCall := range taskCalls {
|
||||
@@ -933,6 +981,7 @@ func TestIncludesHttp(t *testing.T) {
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
task, err := e.CompiledTask(&task.Call{Task: tc.name})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.dir, task.Dir)
|
||||
@@ -1964,10 +2013,6 @@ task: [included3:task1] echo "VAR_1 is included-default-var1"
|
||||
VAR_1 is included-default-var1
|
||||
task: [included3:task1] echo "VAR_2 is included-default-var2"
|
||||
VAR_2 is included-default-var2
|
||||
task: [included4:task1] echo "VAR_1 is included4-var1"
|
||||
VAR_1 is included4-var1
|
||||
task: [included4:task1] echo "VAR_2 is included-default-var2"
|
||||
VAR_2 is included-default-var2
|
||||
`)
|
||||
require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task1"}))
|
||||
t.Log(buff.String())
|
||||
@@ -2151,7 +2196,7 @@ func TestUserWorkingDirectory(t *testing.T) {
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.NewExecutor(
|
||||
task.WithEntrypoint("testdata/user_working_dir/Taskfile.yml"),
|
||||
task.WithDir("testdata/user_working_dir"),
|
||||
task.WithStdout(&buff),
|
||||
task.WithStderr(&buff),
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ type (
|
||||
AdvancedImport bool
|
||||
Vars *Vars
|
||||
Flatten bool
|
||||
Checksum string
|
||||
}
|
||||
// Includes is an ordered map of namespaces to includes.
|
||||
Includes struct {
|
||||
@@ -165,6 +166,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
||||
Aliases []string
|
||||
Excludes []string
|
||||
Vars *Vars
|
||||
Checksum string
|
||||
}
|
||||
if err := node.Decode(&includedTaskfile); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
@@ -178,6 +180,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error {
|
||||
include.AdvancedImport = true
|
||||
include.Vars = includedTaskfile.Vars
|
||||
include.Flatten = includedTaskfile.Flatten
|
||||
include.Checksum = includedTaskfile.Checksum
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -200,5 +203,7 @@ func (include *Include) DeepCopy() *Include {
|
||||
AdvancedImport: include.AdvancedImport,
|
||||
Vars: include.Vars.DeepCopy(),
|
||||
Flatten: include.Flatten,
|
||||
Aliases: deepcopy.Slice(include.Aliases),
|
||||
Checksum: include.Checksum,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
giturls "github.com/chainguard-dev/git-urls"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/experiments"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/fsext"
|
||||
)
|
||||
|
||||
@@ -17,6 +17,8 @@ type Node interface {
|
||||
Parent() Node
|
||||
Location() string
|
||||
Dir() string
|
||||
Checksum() string
|
||||
Verify(checksum string) bool
|
||||
ResolveEntrypoint(entrypoint string) (string, error)
|
||||
ResolveDir(dir string) (string, error)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package taskfile
|
||||
|
||||
type (
|
||||
NodeOption func(*BaseNode)
|
||||
// BaseNode is a generic node that implements the Parent() methods of the
|
||||
NodeOption func(*baseNode)
|
||||
// baseNode is a generic node that implements the Parent() methods of the
|
||||
// NodeReader interface. It does not implement the Read() method and it
|
||||
// designed to be embedded in other node types so that this boilerplate code
|
||||
// does not need to be repeated.
|
||||
BaseNode struct {
|
||||
parent Node
|
||||
dir string
|
||||
baseNode struct {
|
||||
parent Node
|
||||
dir string
|
||||
checksum string
|
||||
}
|
||||
)
|
||||
|
||||
func NewBaseNode(dir string, opts ...NodeOption) *BaseNode {
|
||||
node := &BaseNode{
|
||||
func NewBaseNode(dir string, opts ...NodeOption) *baseNode {
|
||||
node := &baseNode{
|
||||
parent: nil,
|
||||
dir: dir,
|
||||
}
|
||||
@@ -27,15 +28,29 @@ func NewBaseNode(dir string, opts ...NodeOption) *BaseNode {
|
||||
}
|
||||
|
||||
func WithParent(parent Node) NodeOption {
|
||||
return func(node *BaseNode) {
|
||||
return func(node *baseNode) {
|
||||
node.parent = parent
|
||||
}
|
||||
}
|
||||
|
||||
func (node *BaseNode) Parent() Node {
|
||||
func WithChecksum(checksum string) NodeOption {
|
||||
return func(node *baseNode) {
|
||||
node.checksum = checksum
|
||||
}
|
||||
}
|
||||
|
||||
func (node *baseNode) Parent() Node {
|
||||
return node.parent
|
||||
}
|
||||
|
||||
func (node *BaseNode) Dir() string {
|
||||
func (node *baseNode) Dir() string {
|
||||
return node.dir
|
||||
}
|
||||
|
||||
func (node *baseNode) Checksum() string {
|
||||
return node.checksum
|
||||
}
|
||||
|
||||
func (node *baseNode) Verify(checksum string) bool {
|
||||
return node.checksum == "" || node.checksum == checksum
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ import (
|
||||
const remoteCacheDir = "remote"
|
||||
|
||||
type CacheNode struct {
|
||||
*BaseNode
|
||||
*baseNode
|
||||
source RemoteNode
|
||||
}
|
||||
|
||||
func NewCacheNode(source RemoteNode, dir string) *CacheNode {
|
||||
return &CacheNode{
|
||||
BaseNode: &BaseNode{
|
||||
baseNode: &baseNode{
|
||||
dir: filepath.Join(dir, remoteCacheDir),
|
||||
},
|
||||
source: source,
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
|
||||
// A FileNode is a node that reads a taskfile from the local filesystem.
|
||||
type FileNode struct {
|
||||
*BaseNode
|
||||
Entrypoint string
|
||||
*baseNode
|
||||
entrypoint string
|
||||
}
|
||||
|
||||
func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
|
||||
@@ -25,13 +25,13 @@ func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error)
|
||||
return nil, err
|
||||
}
|
||||
return &FileNode{
|
||||
BaseNode: base,
|
||||
Entrypoint: entrypoint,
|
||||
baseNode: base,
|
||||
entrypoint: entrypoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (node *FileNode) Location() string {
|
||||
return node.Entrypoint
|
||||
return node.entrypoint
|
||||
}
|
||||
|
||||
func (node *FileNode) Read() ([]byte, error) {
|
||||
@@ -63,7 +63,7 @@ func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) {
|
||||
|
||||
// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
|
||||
// This means that files are included relative to one another
|
||||
entrypointDir := filepath.Dir(node.Entrypoint)
|
||||
entrypointDir := filepath.Dir(node.entrypoint)
|
||||
return filepathext.SmartJoin(entrypointDir, path), nil
|
||||
}
|
||||
|
||||
@@ -79,6 +79,6 @@ func (node *FileNode) ResolveDir(dir string) (string, error) {
|
||||
|
||||
// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
|
||||
// This means that files are included relative to one another
|
||||
entrypointDir := filepath.Dir(node.Entrypoint)
|
||||
entrypointDir := filepath.Dir(node.entrypoint)
|
||||
return filepathext.SmartJoin(entrypointDir, path), nil
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
|
||||
// An GitNode is a node that reads a Taskfile from a remote location via Git.
|
||||
type GitNode struct {
|
||||
*BaseNode
|
||||
URL *url.URL
|
||||
*baseNode
|
||||
url *url.URL
|
||||
rawUrl string
|
||||
ref string
|
||||
path string
|
||||
@@ -40,23 +40,20 @@ func NewGitNode(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
basePath, path := func() (string, string) {
|
||||
x := strings.Split(u.Path, "//")
|
||||
return x[0], x[1]
|
||||
}()
|
||||
basePath, path := splitURLOnDoubleSlash(u)
|
||||
ref := u.Query().Get("ref")
|
||||
|
||||
rawUrl := u.String()
|
||||
rawUrl := u.Redacted()
|
||||
|
||||
u.RawQuery = ""
|
||||
u.Path = basePath
|
||||
|
||||
if u.Scheme == "http" && !insecure {
|
||||
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
|
||||
return nil, &errors.TaskfileNotSecureError{URI: u.Redacted()}
|
||||
}
|
||||
return &GitNode{
|
||||
BaseNode: base,
|
||||
URL: u,
|
||||
baseNode: base,
|
||||
url: u,
|
||||
rawUrl: rawUrl,
|
||||
ref: ref,
|
||||
path: path,
|
||||
@@ -79,7 +76,7 @@ func (node *GitNode) ReadContext(_ context.Context) ([]byte, error) {
|
||||
fs := memfs.New()
|
||||
storer := memory.NewStorage()
|
||||
_, err := git.Clone(storer, fs, &git.CloneOptions{
|
||||
URL: node.URL.String(),
|
||||
URL: node.url.String(),
|
||||
ReferenceName: plumbing.ReferenceName(node.ref),
|
||||
SingleBranch: true,
|
||||
Depth: 1,
|
||||
@@ -102,7 +99,7 @@ func (node *GitNode) ReadContext(_ context.Context) ([]byte, error) {
|
||||
|
||||
func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) {
|
||||
dir, _ := filepath.Split(node.path)
|
||||
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.URL, filepath.Join(dir, entrypoint))
|
||||
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.url, filepath.Join(dir, entrypoint))
|
||||
if node.ref != "" {
|
||||
return fmt.Sprintf("%s?ref=%s", resolvedEntrypoint, node.ref), nil
|
||||
}
|
||||
@@ -127,11 +124,23 @@ func (node *GitNode) ResolveDir(dir string) (string, error) {
|
||||
|
||||
func (node *GitNode) CacheKey() string {
|
||||
checksum := strings.TrimRight(checksum([]byte(node.Location())), "=")
|
||||
prefix := filepath.Base(filepath.Dir(node.path))
|
||||
lastDir := filepath.Base(node.path)
|
||||
lastDir := filepath.Base(filepath.Dir(node.path))
|
||||
prefix := filepath.Base(node.path)
|
||||
// Means it's not "", nor "." nor "/", so it's a valid directory
|
||||
if len(lastDir) > 1 {
|
||||
prefix = fmt.Sprintf("%s-%s", lastDir, prefix)
|
||||
prefix = fmt.Sprintf("%s.%s", lastDir, prefix)
|
||||
}
|
||||
return fmt.Sprintf("git.%s.%s.%s", node.url.Host, prefix, checksum)
|
||||
}
|
||||
|
||||
func splitURLOnDoubleSlash(u *url.URL) (string, string) {
|
||||
x := strings.Split(u.Path, "//")
|
||||
switch len(x) {
|
||||
case 0:
|
||||
return "", ""
|
||||
case 1:
|
||||
return x[0], ""
|
||||
default:
|
||||
return x[0], x[1]
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", prefix, checksum)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGitNode_ssh(t *testing.T) {
|
||||
@@ -13,8 +14,8 @@ func TestGitNode_ssh(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "main", node.ref)
|
||||
assert.Equal(t, "Taskfile.yml", node.path)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git//Taskfile.yml?ref=main", node.rawUrl)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git", node.URL.String())
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git//Taskfile.yml?ref=main", node.Location())
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git", node.url.String())
|
||||
entrypoint, err := node.ResolveEntrypoint("common.yml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git//common.yml?ref=main", entrypoint)
|
||||
@@ -27,8 +28,8 @@ func TestGitNode_sshWithDir(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "main", node.ref)
|
||||
assert.Equal(t, "directory/Taskfile.yml", node.path)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git//directory/Taskfile.yml?ref=main", node.rawUrl)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git", node.URL.String())
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git//directory/Taskfile.yml?ref=main", node.Location())
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git", node.url.String())
|
||||
entrypoint, err := node.ResolveEntrypoint("common.yml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ssh://git@github.com/foo/bar.git//directory/common.yml?ref=main", entrypoint)
|
||||
@@ -41,8 +42,8 @@ func TestGitNode_https(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "main", node.ref)
|
||||
assert.Equal(t, "Taskfile.yml", node.path)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git//Taskfile.yml?ref=main", node.rawUrl)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git", node.URL.String())
|
||||
assert.Equal(t, "https://github.com/foo/bar.git//Taskfile.yml?ref=main", node.Location())
|
||||
assert.Equal(t, "https://github.com/foo/bar.git", node.url.String())
|
||||
entrypoint, err := node.ResolveEntrypoint("common.yml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git//common.yml?ref=main", entrypoint)
|
||||
@@ -55,8 +56,8 @@ func TestGitNode_httpsWithDir(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "main", node.ref)
|
||||
assert.Equal(t, "directory/Taskfile.yml", node.path)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", node.rawUrl)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git", node.URL.String())
|
||||
assert.Equal(t, "https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", node.Location())
|
||||
assert.Equal(t, "https://github.com/foo/bar.git", node.url.String())
|
||||
entrypoint, err := node.ResolveEntrypoint("common.yml")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/foo/bar.git//directory/common.yml?ref=main", entrypoint)
|
||||
@@ -65,18 +66,28 @@ func TestGitNode_httpsWithDir(t *testing.T) {
|
||||
func TestGitNode_CacheKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
node, err := NewGitNode("https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main", "", false)
|
||||
assert.NoError(t, err)
|
||||
key := node.CacheKey()
|
||||
assert.Equal(t, "Taskfile.yml-directory.f1ddddac425a538870230a3e38fc0cded4ec5da250797b6cab62c82477718fbb", key)
|
||||
tests := []struct {
|
||||
entrypoint string
|
||||
expectedKey string
|
||||
}{
|
||||
{
|
||||
entrypoint: "https://github.com/foo/bar.git//directory/Taskfile.yml?ref=main",
|
||||
expectedKey: "git.github.com.directory.Taskfile.yml.f1ddddac425a538870230a3e38fc0cded4ec5da250797b6cab62c82477718fbb",
|
||||
},
|
||||
{
|
||||
entrypoint: "https://github.com/foo/bar.git//Taskfile.yml?ref=main",
|
||||
expectedKey: "git.github.com.Taskfile.yml.39d28c1ff36f973705ae188b991258bbabaffd6d60bcdde9693d157d00d5e3a4",
|
||||
},
|
||||
{
|
||||
entrypoint: "https://github.com/foo/bar.git//multiple/directory/Taskfile.yml?ref=main",
|
||||
expectedKey: "git.github.com.directory.Taskfile.yml.1b6d145e01406dcc6c0aa572e5a5d1333be1ccf2cae96d18296d725d86197d31",
|
||||
},
|
||||
}
|
||||
|
||||
node, err = NewGitNode("https://github.com/foo/bar.git//Taskfile.yml?ref=main", "", false)
|
||||
assert.NoError(t, err)
|
||||
key = node.CacheKey()
|
||||
assert.Equal(t, "Taskfile.yml-..39d28c1ff36f973705ae188b991258bbabaffd6d60bcdde9693d157d00d5e3a4", key)
|
||||
|
||||
node, err = NewGitNode("https://github.com/foo/bar.git//multiple/directory/Taskfile.yml?ref=main", "", false)
|
||||
assert.NoError(t, err)
|
||||
key = node.CacheKey()
|
||||
assert.Equal(t, "Taskfile.yml-directory.1b6d145e01406dcc6c0aa572e5a5d1333be1ccf2cae96d18296d725d86197d31", key)
|
||||
for _, tt := range tests {
|
||||
node, err := NewGitNode(tt.entrypoint, "", false)
|
||||
require.NoError(t, err)
|
||||
key := node.CacheKey()
|
||||
assert.Equal(t, tt.expectedKey, key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,8 @@ import (
|
||||
|
||||
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
|
||||
type HTTPNode struct {
|
||||
*BaseNode
|
||||
URL *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml)
|
||||
entrypoint string // stores entrypoint url. used for building graph vertices.
|
||||
*baseNode
|
||||
url *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml)
|
||||
}
|
||||
|
||||
func NewHTTPNode(
|
||||
@@ -33,18 +32,16 @@ func NewHTTPNode(
|
||||
return nil, err
|
||||
}
|
||||
if url.Scheme == "http" && !insecure {
|
||||
return nil, &errors.TaskfileNotSecureError{URI: entrypoint}
|
||||
return nil, &errors.TaskfileNotSecureError{URI: url.Redacted()}
|
||||
}
|
||||
|
||||
return &HTTPNode{
|
||||
BaseNode: base,
|
||||
URL: url,
|
||||
entrypoint: entrypoint,
|
||||
baseNode: base,
|
||||
url: url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (node *HTTPNode) Location() string {
|
||||
return node.entrypoint
|
||||
return node.url.Redacted()
|
||||
}
|
||||
|
||||
func (node *HTTPNode) Read() ([]byte, error) {
|
||||
@@ -52,14 +49,13 @@ func (node *HTTPNode) Read() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (node *HTTPNode) ReadContext(ctx context.Context) ([]byte, error) {
|
||||
url, err := RemoteExists(ctx, node.URL)
|
||||
url, err := RemoteExists(ctx, *node.url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node.URL = url
|
||||
req, err := http.NewRequest("GET", node.URL.String(), nil)
|
||||
req, err := http.NewRequest("GET", url.String(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
|
||||
return nil, errors.TaskfileFetchFailedError{URI: node.Location()}
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||
@@ -67,12 +63,12 @@ func (node *HTTPNode) ReadContext(ctx context.Context) ([]byte, error) {
|
||||
if ctx.Err() != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.TaskfileFetchFailedError{URI: node.URL.String()}
|
||||
return nil, errors.TaskfileFetchFailedError{URI: node.Location()}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.TaskfileFetchFailedError{
|
||||
URI: node.URL.String(),
|
||||
URI: node.Location(),
|
||||
HTTPStatusCode: resp.StatusCode,
|
||||
}
|
||||
}
|
||||
@@ -91,7 +87,7 @@ func (node *HTTPNode) ResolveEntrypoint(entrypoint string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return node.URL.ResolveReference(ref).String(), nil
|
||||
return node.url.ResolveReference(ref).String(), nil
|
||||
}
|
||||
|
||||
func (node *HTTPNode) ResolveDir(dir string) (string, error) {
|
||||
@@ -116,12 +112,12 @@ func (node *HTTPNode) ResolveDir(dir string) (string, error) {
|
||||
|
||||
func (node *HTTPNode) CacheKey() string {
|
||||
checksum := strings.TrimRight(checksum([]byte(node.Location())), "=")
|
||||
dir, filename := filepath.Split(node.entrypoint)
|
||||
dir, filename := filepath.Split(node.url.Path)
|
||||
lastDir := filepath.Base(dir)
|
||||
prefix := filename
|
||||
// Means it's not "", nor "." nor "/", so it's a valid directory
|
||||
if len(lastDir) > 1 {
|
||||
prefix = fmt.Sprintf("%s-%s", lastDir, filename)
|
||||
prefix = fmt.Sprintf("%s.%s", lastDir, filename)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", prefix, checksum)
|
||||
return fmt.Sprintf("http.%s.%s.%s", node.url.Host, prefix, checksum)
|
||||
}
|
||||
|
||||
49
taskfile/node_http_test.go
Normal file
49
taskfile/node_http_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package taskfile
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHTTPNode_CacheKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
entrypoint string
|
||||
expectedKey string
|
||||
}{
|
||||
{
|
||||
entrypoint: "https://github.com",
|
||||
expectedKey: "http.github.com..996e1f714b08e971ec79e3bea686287e66441f043177999a13dbc546d8fe402a",
|
||||
},
|
||||
{
|
||||
entrypoint: "https://github.com/Taskfile.yml",
|
||||
expectedKey: "http.github.com.Taskfile.yml.85b3c3ad71b78dc74e404c7b4390fc13672925cb644a4d26c21b9f97c17b5fc0",
|
||||
},
|
||||
{
|
||||
entrypoint: "https://github.com/foo",
|
||||
expectedKey: "http.github.com.foo.df3158dafc823e6847d9bcaf79328446c4877405e79b100723fa6fd545ed3e2b",
|
||||
},
|
||||
{
|
||||
entrypoint: "https://github.com/foo/Taskfile.yml",
|
||||
expectedKey: "http.github.com.foo.Taskfile.yml.aea946ea7eb6f6bb4e159e8b840b6b50975927778b2e666df988c03bbf10c4c4",
|
||||
},
|
||||
{
|
||||
entrypoint: "https://github.com/foo/bar",
|
||||
expectedKey: "http.github.com.foo.bar.d3514ad1d4daedf9cc2825225070b49ebc8db47fa5177951b2a5b9994597570c",
|
||||
},
|
||||
{
|
||||
entrypoint: "https://github.com/foo/bar/Taskfile.yml",
|
||||
expectedKey: "http.github.com.bar.Taskfile.yml.b9cf01e01e47c0e96ea536e1a8bd7b3a6f6c1f1881bad438990d2bfd4ccd0ac0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
node, err := NewHTTPNode(tt.entrypoint, "", false)
|
||||
require.NoError(t, err)
|
||||
key := node.CacheKey()
|
||||
assert.Equal(t, tt.expectedKey, key)
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,12 @@ import (
|
||||
|
||||
// A StdinNode is a node that reads a taskfile from the standard input stream.
|
||||
type StdinNode struct {
|
||||
*BaseNode
|
||||
*baseNode
|
||||
}
|
||||
|
||||
func NewStdinNode(dir string) (*StdinNode, error) {
|
||||
return &StdinNode{
|
||||
BaseNode: NewBaseNode(dir),
|
||||
baseNode: NewBaseNode(dir),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,8 @@ func (r *Reader) include(ctx context.Context, node Node) error {
|
||||
Aliases: include.Aliases,
|
||||
AdvancedImport: include.AdvancedImport,
|
||||
Excludes: include.Excludes,
|
||||
Vars: templater.ReplaceVars(include.Vars, cache),
|
||||
Vars: include.Vars,
|
||||
Checksum: include.Checksum,
|
||||
}
|
||||
if err := cache.Err(); err != nil {
|
||||
return err
|
||||
@@ -267,6 +268,7 @@ func (r *Reader) include(ctx context.Context, node Node) error {
|
||||
|
||||
includeNode, err := NewNode(entrypoint, include.Dir, r.insecure,
|
||||
WithParent(node),
|
||||
WithChecksum(include.Checksum),
|
||||
)
|
||||
if err != nil {
|
||||
if include.Optional {
|
||||
@@ -362,7 +364,24 @@ func (r *Reader) readNodeContent(ctx context.Context, node Node) ([]byte, error)
|
||||
if node, isRemote := node.(RemoteNode); isRemote {
|
||||
return r.readRemoteNodeContent(ctx, node)
|
||||
}
|
||||
return node.Read()
|
||||
|
||||
// Read the Taskfile
|
||||
b, err := node.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the given checksum doesn't match the sum pinned in the Taskfile
|
||||
checksum := checksum(b)
|
||||
if !node.Verify(checksum) {
|
||||
return nil, &errors.TaskfileDoesNotMatchChecksum{
|
||||
URI: node.Location(),
|
||||
ExpectedChecksum: node.Checksum(),
|
||||
ActualChecksum: checksum,
|
||||
}
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readRemoteNodeContent(ctx context.Context, node RemoteNode) ([]byte, error) {
|
||||
@@ -427,17 +446,29 @@ func (r *Reader) readRemoteNodeContent(ctx context.Context, node RemoteNode) ([]
|
||||
}
|
||||
|
||||
r.debugf("found remote file at %q\n", node.Location())
|
||||
checksum := checksum(downloadedBytes)
|
||||
prompt := cache.ChecksumPrompt(checksum)
|
||||
|
||||
// Prompt the user if required
|
||||
if prompt != "" {
|
||||
if err := func() error {
|
||||
r.promptMutex.Lock()
|
||||
defer r.promptMutex.Unlock()
|
||||
return r.promptf(prompt, node.Location())
|
||||
}(); err != nil {
|
||||
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
||||
// If the given checksum doesn't match the sum pinned in the Taskfile
|
||||
checksum := checksum(downloadedBytes)
|
||||
if !node.Verify(checksum) {
|
||||
return nil, &errors.TaskfileDoesNotMatchChecksum{
|
||||
URI: node.Location(),
|
||||
ExpectedChecksum: node.Checksum(),
|
||||
ActualChecksum: checksum,
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no manual checksum pin, run the automatic checks
|
||||
if node.Checksum() == "" {
|
||||
// Prompt the user if required
|
||||
prompt := cache.ChecksumPrompt(checksum)
|
||||
if prompt != "" {
|
||||
if err := func() error {
|
||||
r.promptMutex.Lock()
|
||||
defer r.promptMutex.Unlock()
|
||||
return r.promptf(prompt, node.Location())
|
||||
}(); err != nil {
|
||||
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,11 +36,11 @@ var (
|
||||
// at the given URL with any of the default Taskfile files 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 RemoteExists(ctx context.Context, u *url.URL) (*url.URL, error) {
|
||||
func RemoteExists(ctx context.Context, u url.URL) (*url.URL, error) {
|
||||
// Create a new HEAD request for the given URL to check if the resource exists
|
||||
req, err := http.NewRequestWithContext(ctx, "HEAD", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
|
||||
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted()}
|
||||
}
|
||||
|
||||
// Request the given URL
|
||||
@@ -49,7 +49,7 @@ func RemoteExists(ctx context.Context, u *url.URL) (*url.URL, error) {
|
||||
if ctx.Err() != nil {
|
||||
return nil, fmt.Errorf("checking remote file: %w", ctx.Err())
|
||||
}
|
||||
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
|
||||
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted()}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
@@ -61,7 +61,7 @@ func RemoteExists(ctx context.Context, u *url.URL) (*url.URL, error) {
|
||||
if resp.StatusCode == http.StatusOK && slices.ContainsFunc(allowedContentTypes, func(s string) bool {
|
||||
return strings.Contains(contentType, s)
|
||||
}) {
|
||||
return u, nil
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// If the request was not successful, append the default Taskfile names to
|
||||
@@ -78,7 +78,7 @@ func RemoteExists(ctx context.Context, u *url.URL) (*url.URL, error) {
|
||||
// Try the alternative URL
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.TaskfileFetchFailedError{URI: u.String()}
|
||||
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted()}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
@@ -88,5 +88,5 @@ func RemoteExists(ctx context.Context, u *url.URL) (*url.URL, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.TaskfileNotFoundError{URI: u.String(), Walk: false}
|
||||
return nil, errors.TaskfileNotFoundError{URI: u.Redacted(), Walk: false}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
task: Missing schema version in Taskfile "/testdata/empty_taskfile/Taskfile.yml"
|
||||
task: Missing schema version in Taskfile "{{.TEST_DIR}}/testdata/empty_taskfile/Taskfile.yml"
|
||||
@@ -1,2 +1,2 @@
|
||||
task: Failed to parse /testdata/for/cmds/Taskfile.yml:
|
||||
task: Failed to parse {{.TEST_DIR}}/testdata/for/cmds/Taskfile.yml:
|
||||
matrix reference ".NOT_A_LIST" must resolve to a list
|
||||
@@ -1,2 +1,2 @@
|
||||
matrix reference ".NOT_A_LIST" must resolve to a list
|
||||
task: Failed to parse /testdata/for/deps/Taskfile.yml:
|
||||
task: Failed to parse {{.TEST_DIR}}/testdata/for/deps/Taskfile.yml:
|
||||
|
||||
9
testdata/fuzzy/Taskfile.yml
vendored
Normal file
9
testdata/fuzzy/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: 3
|
||||
|
||||
tasks:
|
||||
install: echo 'install'
|
||||
|
||||
internal:
|
||||
internal: true
|
||||
cmds:
|
||||
- echo "internal"
|
||||
1
testdata/fuzzy/testdata/TestFuzzyModel-fuzzy-err-run.golden
vendored
Normal file
1
testdata/fuzzy/testdata/TestFuzzyModel-fuzzy-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Task "instal" does not exist. Did you mean "install"?
|
||||
1
testdata/fuzzy/testdata/TestFuzzyModel-fuzzy.golden
vendored
Normal file
1
testdata/fuzzy/testdata/TestFuzzyModel-fuzzy.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: No tasks with description available. Try --list-all to list all tasks
|
||||
1
testdata/fuzzy/testdata/TestFuzzyModel-intern-err-run.golden
vendored
Normal file
1
testdata/fuzzy/testdata/TestFuzzyModel-intern-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Task "intern" does not exist
|
||||
1
testdata/fuzzy/testdata/TestFuzzyModel-intern.golden
vendored
Normal file
1
testdata/fuzzy/testdata/TestFuzzyModel-intern.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: No tasks with description available. Try --list-all to list all tasks
|
||||
2
testdata/fuzzy/testdata/TestFuzzyModel-not-fuzzy.golden
vendored
Normal file
2
testdata/fuzzy/testdata/TestFuzzyModel-not-fuzzy.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
task: [install] echo 'install'
|
||||
install
|
||||
14
testdata/include_with_vars/Taskfile.yml
vendored
14
testdata/include_with_vars/Taskfile.yml
vendored
@@ -1,23 +1,16 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
VAR_1: included4-var1
|
||||
|
||||
includes:
|
||||
included1:
|
||||
taskfile: include/Taskfile.include.yml
|
||||
taskfile: include/Taskfile.include1.yml
|
||||
vars:
|
||||
VAR_1: included1-var1
|
||||
included2:
|
||||
taskfile: include/Taskfile.include.yml
|
||||
taskfile: include/Taskfile.include2.yml
|
||||
vars:
|
||||
VAR_1: included2-var1
|
||||
included3:
|
||||
taskfile: include/Taskfile.include.yml
|
||||
included4:
|
||||
taskfile: include/Taskfile.include.yml
|
||||
vars:
|
||||
VAR_1: "{{.VAR_1}}"
|
||||
taskfile: include/Taskfile.include3.yml
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
@@ -25,4 +18,3 @@ tasks:
|
||||
- task: included1:task1
|
||||
- task: included2:task1
|
||||
- task: included3:task1
|
||||
- task: included4:task1
|
||||
|
||||
11
testdata/include_with_vars/include/Taskfile.include2.yml
vendored
Normal file
11
testdata/include_with_vars/include/Taskfile.include2.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
VAR_1: '{{.VAR_1 | default "included-default-var1"}}'
|
||||
VAR_2: '{{.VAR_2 | default "included-default-var2"}}'
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
cmds:
|
||||
- echo "VAR_1 is {{.VAR_1}}"
|
||||
- echo "VAR_2 is {{.VAR_2}}"
|
||||
11
testdata/include_with_vars/include/Taskfile.include3.yml
vendored
Normal file
11
testdata/include_with_vars/include/Taskfile.include3.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
VAR_1: '{{.VAR_1 | default "included-default-var1"}}'
|
||||
VAR_2: '{{.VAR_2 | default "included-default-var2"}}'
|
||||
|
||||
tasks:
|
||||
task1:
|
||||
cmds:
|
||||
- echo "VAR_1 is {{.VAR_1}}"
|
||||
- echo "VAR_2 is {{.VAR_2}}"
|
||||
12
testdata/includes_checksum/correct/Taskfile.yml
vendored
Normal file
12
testdata/includes_checksum/correct/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: ../included.yml
|
||||
internal: true
|
||||
checksum: c97f39eb96fe3fa5fe2a610d244b8449897b06f0c93821484af02e0999781bf5
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: included:default
|
||||
2
testdata/includes_checksum/correct/testdata/TestIncludeChecksum-correct.golden
vendored
Normal file
2
testdata/includes_checksum/correct/testdata/TestIncludeChecksum-correct.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
task: [included:default] echo "Hello, World!"
|
||||
Hello, World!
|
||||
12
testdata/includes_checksum/correct_remote/Taskfile.yml
vendored
Normal file
12
testdata/includes_checksum/correct_remote/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: https://taskfile.dev
|
||||
internal: true
|
||||
checksum: c153e97e0b3a998a7ed2e61064c6ddaddd0de0c525feefd6bba8569827d8efe9
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: included:default
|
||||
6
testdata/includes_checksum/included.yml
vendored
Normal file
6
testdata/includes_checksum/included.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- echo "Hello, World!"
|
||||
12
testdata/includes_checksum/incorrect/Taskfile.yml
vendored
Normal file
12
testdata/includes_checksum/incorrect/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: ../included.yml
|
||||
internal: true
|
||||
checksum: foo
|
||||
|
||||
tasks:
|
||||
default:
|
||||
cmds:
|
||||
- task: included:default
|
||||
3
testdata/includes_checksum/incorrect/testdata/TestIncludeChecksum-incorrect-err-setup.golden
vendored
Normal file
3
testdata/includes_checksum/incorrect/testdata/TestIncludeChecksum-incorrect-err-setup.golden
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
task: The checksum of the Taskfile at "{{.TEST_DIR}}/testdata/includes_checksum/included.yml" does not match!
|
||||
got: "c97f39eb96fe3fa5fe2a610d244b8449897b06f0c93821484af02e0999781bf5"
|
||||
want: "foo"
|
||||
0
testdata/includes_checksum/incorrect/testdata/TestIncludeChecksum-incorrect.golden
vendored
Normal file
0
testdata/includes_checksum/incorrect/testdata/TestIncludeChecksum-incorrect.golden
vendored
Normal file
6
testdata/json_list_format/Taskfile.yml
vendored
Normal file
6
testdata/json_list_format/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
label: "foobar"
|
||||
desc: "task description"
|
||||
18
testdata/json_list_format/testdata/TestJsonListFormat.golden
vendored
Normal file
18
testdata/json_list_format/testdata/TestJsonListFormat.golden
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"name": "foobar",
|
||||
"task": "foo",
|
||||
"desc": "task description",
|
||||
"summary": "",
|
||||
"aliases": [],
|
||||
"up_to_date": false,
|
||||
"location": {
|
||||
"line": 4,
|
||||
"column": 3,
|
||||
"taskfile": "{{.TEST_DIR}}/testdata/json_list_format/Taskfile.yml"
|
||||
}
|
||||
}
|
||||
],
|
||||
"location": "{{.TEST_DIR}}/testdata/json_list_format/Taskfile.yml"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars
|
||||
{{.TEST_DIR}}/testdata/special_vars
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars/included
|
||||
{{.TEST_DIR}}/testdata/special_vars/included
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars/included/Taskfile.yml
|
||||
{{.TEST_DIR}}/testdata/special_vars/included/Taskfile.yml
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars
|
||||
{{.TEST_DIR}}/testdata/special_vars
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars/foo
|
||||
{{.TEST_DIR}}/testdata/special_vars/foo
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars
|
||||
{{.TEST_DIR}}/testdata/special_vars
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars/Taskfile.yml
|
||||
{{.TEST_DIR}}/testdata/special_vars/Taskfile.yml
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars
|
||||
{{.TEST_DIR}}/testdata/special_vars
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars/included
|
||||
{{.TEST_DIR}}/testdata/special_vars/included
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars/included/Taskfile.yml
|
||||
{{.TEST_DIR}}/testdata/special_vars/included/Taskfile.yml
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars
|
||||
{{.TEST_DIR}}/testdata/special_vars
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars/foo
|
||||
{{.TEST_DIR}}/testdata/special_vars/foo
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars
|
||||
{{.TEST_DIR}}/testdata/special_vars
|
||||
|
||||
@@ -1 +1 @@
|
||||
/testdata/special_vars/Taskfile.yml
|
||||
{{.TEST_DIR}}/testdata/special_vars/Taskfile.yml
|
||||
|
||||
176
testdata/vars/any/Taskfile.yml
vendored
176
testdata/vars/any/Taskfile.yml
vendored
@@ -2,107 +2,113 @@ version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
- task: dynamic
|
||||
- task: string
|
||||
- task: bool
|
||||
- task: int
|
||||
- task: string-array
|
||||
- task: map
|
||||
- task: for-string
|
||||
- task: for-int
|
||||
- task: for-map
|
||||
- task: for-multi-layer-map
|
||||
|
||||
dynamic:
|
||||
vars:
|
||||
STRING_A: '$echo "A"'
|
||||
STRING_B: '$echo {{.STRING_A}}B'
|
||||
STRING_C: '$echo {{.STRING_B}}C'
|
||||
cmds:
|
||||
- echo '{{.STRING_C}}'
|
||||
|
||||
string:
|
||||
vars:
|
||||
STRING_A: 'A'
|
||||
STRING_B: '{{.STRING_A}}B'
|
||||
STRING_C: '{{.STRING_B}}C'
|
||||
cmds:
|
||||
- echo '{{.STRING_C}}'
|
||||
|
||||
bool:
|
||||
vars:
|
||||
BOOL_TRUE: true
|
||||
BOOL_FALSE: false
|
||||
BOOL_A: '{{and .BOOL_TRUE .BOOL_FALSE}}'
|
||||
BOOL_B: '{{or .BOOL_TRUE .BOOL_FALSE}}'
|
||||
BOOL_C: '{{not .BOOL_TRUE}}'
|
||||
cmds:
|
||||
- echo '{{if .BOOL_TRUE}}A:{{.BOOL_A}} B:{{.BOOL_B}} C:{{.BOOL_C}}{{end}}'
|
||||
|
||||
int:
|
||||
vars:
|
||||
INT_100: 100
|
||||
INT_10: 10
|
||||
cmds:
|
||||
- echo '100 + 10 = {{add .INT_100 .INT_10}}'
|
||||
- echo '100 - 10 = {{sub .INT_100 .INT_10}}'
|
||||
- echo '100 * 10 = {{mul .INT_100 .INT_10}}'
|
||||
- echo '100 / 10 = {{div .INT_100 .INT_10}}'
|
||||
|
||||
string-array:
|
||||
vars:
|
||||
ARRAY_1: ['A', 'B', 'C']
|
||||
ARRAY_2: ['D', 'E', 'F']
|
||||
cmds:
|
||||
- echo '{{append .ARRAY_1 "D"}}'
|
||||
- echo '{{concat .ARRAY_1 .ARRAY_2}}'
|
||||
- echo '{{join " " .ARRAY_1}}'
|
||||
- task: nested-map
|
||||
- task: slice
|
||||
- task: ref
|
||||
- task: ref-sh
|
||||
- task: ref-dep
|
||||
- task: ref-resolver
|
||||
- task: json
|
||||
|
||||
map:
|
||||
vars:
|
||||
MAP_1: {A: 1, B: 2, C: 3}
|
||||
MAP_2: {B: 4, C: 5, D: 6}
|
||||
MAP_3: {C: 7, D: 8, E: 9}
|
||||
MAP:
|
||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||
cmds:
|
||||
- echo '{{merge .MAP_1 .MAP_2 .MAP_3}}'
|
||||
- task: print-story
|
||||
vars:
|
||||
VAR:
|
||||
ref: .MAP
|
||||
|
||||
for-string:
|
||||
nested-map:
|
||||
vars:
|
||||
LIST: [foo, bar, baz]
|
||||
FOO: "foo"
|
||||
nested:
|
||||
map:
|
||||
variables:
|
||||
work: "{{.FOO}}"
|
||||
cmds:
|
||||
- for:
|
||||
var: LIST
|
||||
cmd: echo {{.ITEM}}
|
||||
- echo {{.nested.variables.work}}
|
||||
|
||||
for-int:
|
||||
slice:
|
||||
vars:
|
||||
LIST: [1, 2, 3]
|
||||
FOO: "foo"
|
||||
BAR: "bar"
|
||||
slice_variables_work: ["{{.FOO}}","{{.BAR}}"]
|
||||
cmds:
|
||||
- for:
|
||||
var: LIST
|
||||
cmd: echo {{add .ITEM 100}}
|
||||
- echo {{index .slice_variables_work 0}} {{index .slice_variables_work 1}}
|
||||
|
||||
for-map:
|
||||
ref:
|
||||
vars:
|
||||
MAP:
|
||||
KEY_1: value_1
|
||||
KEY_2: value_2
|
||||
KEY_3: value_3
|
||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||
MAP_REF:
|
||||
ref: .MAP
|
||||
cmds:
|
||||
- for:
|
||||
var: MAP
|
||||
cmd: echo {{.KEY}} {{.ITEM}}
|
||||
- task: print-story
|
||||
vars:
|
||||
VAR:
|
||||
ref: .MAP_REF
|
||||
|
||||
for-multi-layer-map:
|
||||
ref-sh:
|
||||
vars:
|
||||
JSON_STRING:
|
||||
sh: echo '{"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}'
|
||||
JSON: "fromJson {{.JSON_STRING}}"
|
||||
MAP_REF:
|
||||
ref: .JSON
|
||||
cmds:
|
||||
- task: print-story
|
||||
vars:
|
||||
VAR:
|
||||
ref: .MAP_REF
|
||||
|
||||
ref-dep:
|
||||
vars:
|
||||
MAP:
|
||||
KEY_1:
|
||||
SUBKEY: sub_value_1
|
||||
KEY_2:
|
||||
SUBKEY: sub_value_2
|
||||
KEY_3:
|
||||
SUBKEY: sub_value_3
|
||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||
deps:
|
||||
- task: print-story
|
||||
vars:
|
||||
VAR:
|
||||
ref: .MAP
|
||||
|
||||
ref-resolver:
|
||||
vars:
|
||||
MAP:
|
||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||
MAP_REF:
|
||||
ref: .MAP
|
||||
cmds:
|
||||
- for:
|
||||
var: MAP
|
||||
cmd: echo {{.KEY}} {{.ITEM.SUBKEY}}
|
||||
- task: print-var
|
||||
vars:
|
||||
VAR:
|
||||
ref: (index .MAP_REF.children 0).name
|
||||
|
||||
json:
|
||||
vars:
|
||||
JSON_STRING:
|
||||
sh: cat example.json
|
||||
JSON:
|
||||
ref: "fromJson .JSON_STRING"
|
||||
cmds:
|
||||
- task: print-story
|
||||
vars:
|
||||
VAR:
|
||||
ref: .JSON
|
||||
|
||||
print-var:
|
||||
cmds:
|
||||
- echo "{{.VAR}}"
|
||||
|
||||
print-story:
|
||||
cmds:
|
||||
- >-
|
||||
echo "{{.VAR.name}} has {{len .VAR.children}} children called
|
||||
{{- $children := .VAR.children -}}
|
||||
{{- range $i, $child := $children -}}
|
||||
{{- if lt $i (sub (len $children) 1)}} {{$child.name -}},
|
||||
{{- else}} and {{$child.name -}}
|
||||
{{- end -}}
|
||||
{{- end -}}"
|
||||
|
||||
115
testdata/vars/any2/Taskfile.yml
vendored
115
testdata/vars/any2/Taskfile.yml
vendored
@@ -1,115 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
- task: map
|
||||
- task: nested-map
|
||||
- task: slice
|
||||
- task: ref
|
||||
- task: ref-sh
|
||||
- task: ref-dep
|
||||
- task: ref-resolver
|
||||
- task: json
|
||||
|
||||
map:
|
||||
vars:
|
||||
MAP:
|
||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||
cmds:
|
||||
- task: print-story
|
||||
vars:
|
||||
VAR:
|
||||
ref: .MAP
|
||||
|
||||
nested-map:
|
||||
vars:
|
||||
FOO: "foo"
|
||||
nested:
|
||||
map:
|
||||
variables:
|
||||
work: "{{.FOO}}"
|
||||
cmds:
|
||||
- echo {{.nested.variables.work}}
|
||||
|
||||
slice:
|
||||
vars:
|
||||
FOO: "foo"
|
||||
BAR: "bar"
|
||||
slice_variables_work: ["{{.FOO}}","{{.BAR}}"]
|
||||
cmds:
|
||||
- echo {{index .slice_variables_work 0}} {{index .slice_variables_work 1}}
|
||||
|
||||
ref:
|
||||
vars:
|
||||
MAP:
|
||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||
MAP_REF:
|
||||
ref: .MAP
|
||||
cmds:
|
||||
- task: print-story
|
||||
vars:
|
||||
VAR:
|
||||
ref: .MAP_REF
|
||||
|
||||
ref-sh:
|
||||
vars:
|
||||
JSON_STRING:
|
||||
sh: echo '{"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}'
|
||||
JSON:
|
||||
json: "{{.JSON_STRING}}"
|
||||
MAP_REF:
|
||||
ref: .JSON
|
||||
cmds:
|
||||
- task: print-story
|
||||
vars:
|
||||
VAR:
|
||||
ref: .MAP_REF
|
||||
|
||||
ref-dep:
|
||||
vars:
|
||||
MAP:
|
||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||
deps:
|
||||
- task: print-story
|
||||
vars:
|
||||
VAR:
|
||||
ref: .MAP
|
||||
|
||||
ref-resolver:
|
||||
vars:
|
||||
MAP:
|
||||
map: {"name":"Alice","age":30,"children":[{"name":"Bob","age":5},{"name":"Charlie","age":3},{"name":"Diane","age":1}]}
|
||||
MAP_REF:
|
||||
ref: .MAP
|
||||
cmds:
|
||||
- task: print-var
|
||||
vars:
|
||||
VAR:
|
||||
ref: (index .MAP_REF.children 0).name
|
||||
|
||||
json:
|
||||
vars:
|
||||
JSON_STRING:
|
||||
sh: cat example.json
|
||||
JSON:
|
||||
ref: "fromJson .JSON_STRING"
|
||||
cmds:
|
||||
- task: print-story
|
||||
vars:
|
||||
VAR:
|
||||
ref: .JSON
|
||||
|
||||
print-var:
|
||||
cmds:
|
||||
- echo "{{.VAR}}"
|
||||
|
||||
print-story:
|
||||
cmds:
|
||||
- >-
|
||||
echo "{{.VAR.name}} has {{len .VAR.children}} children called
|
||||
{{- $children := .VAR.children -}}
|
||||
{{- range $i, $child := $children -}}
|
||||
{{- if lt $i (sub (len $children) 1)}} {{$child.name -}},
|
||||
{{- else}} and {{$child.name -}}
|
||||
{{- end -}}
|
||||
{{- end -}}"
|
||||
133
watch.go
133
watch.go
@@ -19,6 +19,8 @@ import (
|
||||
"github.com/go-task/task/v3/internal/fingerprint"
|
||||
"github.com/go-task/task/v3/internal/fsnotifyext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/slicesext"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
const defaultWaitTime = 100 * time.Millisecond
|
||||
@@ -71,12 +73,9 @@ func (e *Executor) watchTasks(calls ...*Call) error {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-eventsChan:
|
||||
switch {
|
||||
case !ok:
|
||||
if !ok {
|
||||
cancel()
|
||||
return
|
||||
case event.Op == fsnotify.Chmod:
|
||||
continue
|
||||
}
|
||||
e.Logger.VerboseErrf(logger.Magenta, "task: received watch event: %v\n", event)
|
||||
|
||||
@@ -88,17 +87,22 @@ func (e *Executor) watchTasks(calls ...*Call) error {
|
||||
for _, c := range calls {
|
||||
c := c
|
||||
go func() {
|
||||
if ShouldIgnore(event.Name) {
|
||||
e.Logger.VerboseErrf(logger.Magenta, "task: event skipped for being an ignored dir: %s\n", event.Name)
|
||||
return
|
||||
}
|
||||
t, err := e.GetTask(c)
|
||||
if err != nil {
|
||||
e.Logger.Errf(logger.Red, "%v\n", err)
|
||||
return
|
||||
}
|
||||
baseDir := filepathext.SmartJoin(e.Dir, t.Dir)
|
||||
files, err := fingerprint.Globs(baseDir, t.Sources)
|
||||
files, err := e.collectSources(calls)
|
||||
if err != nil {
|
||||
e.Logger.Errf(logger.Red, "%v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !event.Has(fsnotify.Remove) && !slices.Contains(files, event.Name) {
|
||||
relPath, _ := filepath.Rel(baseDir, event.Name)
|
||||
e.Logger.VerboseErrf(logger.Magenta, "task: skipped for file not in sources: %s\n", relPath)
|
||||
@@ -161,65 +165,36 @@ func closeOnInterrupt(w *fsnotify.Watcher) {
|
||||
}
|
||||
|
||||
func (e *Executor) registerWatchedDirs(w *fsnotify.Watcher, calls ...*Call) error {
|
||||
var registerTaskDirs func(*Call) error
|
||||
registerTaskDirs = func(c *Call) error {
|
||||
task, err := e.CompiledTask(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, d := range task.Deps {
|
||||
if err := registerTaskDirs(&Call{Task: d.Task, Vars: d.Vars}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, c := range task.Cmds {
|
||||
if c.Task != "" {
|
||||
if err := registerTaskDirs(&Call{Task: c.Task, Vars: c.Vars}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files, err := fingerprint.Globs(task.Dir, task.Sources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
d := filepath.Dir(f)
|
||||
if isSet, ok := e.watchedDirs.Load(d); ok && isSet {
|
||||
continue
|
||||
}
|
||||
if ShouldIgnoreFile(d) {
|
||||
continue
|
||||
}
|
||||
if err := w.Add(d); err != nil {
|
||||
return err
|
||||
}
|
||||
e.watchedDirs.Store(d, true)
|
||||
relPath, _ := filepath.Rel(e.Dir, d)
|
||||
w.Events <- fsnotify.Event{Name: f, Op: fsnotify.Create}
|
||||
e.Logger.VerboseOutf(logger.Green, "task: watching new dir: %v\n", relPath)
|
||||
}
|
||||
return nil
|
||||
files, err := e.collectSources(calls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, c := range calls {
|
||||
if err := registerTaskDirs(c); err != nil {
|
||||
for _, f := range files {
|
||||
d := filepath.Dir(f)
|
||||
if isSet, ok := e.watchedDirs.Load(d); ok && isSet {
|
||||
continue
|
||||
}
|
||||
if ShouldIgnore(d) {
|
||||
continue
|
||||
}
|
||||
if err := w.Add(d); err != nil {
|
||||
return err
|
||||
}
|
||||
e.watchedDirs.Store(d, true)
|
||||
relPath, _ := filepath.Rel(e.Dir, d)
|
||||
e.Logger.VerboseOutf(logger.Green, "task: watching new dir: %v\n", relPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ShouldIgnoreFile(path string) bool {
|
||||
ignorePaths := []string{
|
||||
"/.task",
|
||||
"/.git",
|
||||
"/.hg",
|
||||
"/node_modules",
|
||||
}
|
||||
var ignorePaths = []string{
|
||||
"/.task",
|
||||
"/.git",
|
||||
"/.hg",
|
||||
"/node_modules",
|
||||
}
|
||||
|
||||
func ShouldIgnore(path string) bool {
|
||||
for _, p := range ignorePaths {
|
||||
if strings.Contains(path, fmt.Sprintf("%s/", p)) || strings.HasSuffix(path, p) {
|
||||
return true
|
||||
@@ -227,3 +202,47 @@ func ShouldIgnoreFile(path string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *Executor) collectSources(calls []*Call) ([]string, error) {
|
||||
var sources []string
|
||||
|
||||
err := e.traverse(calls, func(task *ast.Task) error {
|
||||
files, err := fingerprint.Globs(task.Dir, task.Sources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sources = append(sources, files...)
|
||||
return nil
|
||||
})
|
||||
|
||||
return slicesext.UniqueJoin(sources), err
|
||||
}
|
||||
|
||||
type traverseFunc func(*ast.Task) error
|
||||
|
||||
func (e *Executor) traverse(calls []*Call, yield traverseFunc) error {
|
||||
for _, c := range calls {
|
||||
task, err := e.CompiledTask(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dep := range task.Deps {
|
||||
if dep.Task != "" {
|
||||
if err := e.traverse([]*Call{{Task: dep.Task, Vars: dep.Vars}}, yield); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd.Task != "" {
|
||||
if err := e.traverse([]*Call{{Task: cmd.Task, Vars: cmd.Vars}}, yield); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := yield(task); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -31,16 +31,17 @@ task: Started watching for tasks: default
|
||||
task: [default] echo "Task running!"
|
||||
Task running!
|
||||
task: task "default" finished running
|
||||
task: Task "default" is up to date
|
||||
task: [default] echo "Task running!"
|
||||
Task running!
|
||||
task: task "default" finished running
|
||||
`)
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.NewExecutor(
|
||||
task.ExecutorWithDir(dir),
|
||||
task.ExecutorWithStdout(&buff),
|
||||
task.ExecutorWithStderr(&buff),
|
||||
task.ExecutorWithWatch(true),
|
||||
task.WithDir(dir),
|
||||
task.WithStdout(&buff),
|
||||
task.WithStderr(&buff),
|
||||
task.WithWatch(true),
|
||||
)
|
||||
|
||||
require.NoError(t, e.Setup())
|
||||
@@ -49,10 +50,10 @@ task: task "default" finished running
|
||||
dirPath := filepathext.SmartJoin(dir, "src")
|
||||
filePath := filepathext.SmartJoin(dirPath, "a")
|
||||
|
||||
err := os.MkdirAll(dirPath, 0755)
|
||||
err := os.MkdirAll(dirPath, 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = os.WriteFile(filePath, []byte("test"), 0644)
|
||||
err = os.WriteFile(filePath, []byte("test"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -71,16 +72,16 @@ task: task "default" finished running
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
err = os.WriteFile(filePath, []byte("test updated"), 0644)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
err = os.WriteFile(filePath, []byte("test updated"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
cancel()
|
||||
assert.Equal(t, expectedOutput, strings.TrimSpace(buff.String()))
|
||||
}
|
||||
|
||||
func TestShouldIgnoreFile(t *testing.T) {
|
||||
func TestShouldIgnore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
@@ -95,7 +96,7 @@ func TestShouldIgnoreFile(t *testing.T) {
|
||||
ct := ct
|
||||
t.Run(fmt.Sprintf("ignore - %d", k), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, task.ShouldIgnoreFile(ct.path), ct.expect)
|
||||
require.Equal(t, task.ShouldIgnore(ct.path), ct.expect)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,58 @@ sidebar_position: 14
|
||||
|
||||
# Changelog
|
||||
|
||||
## v3.43.0 - 2025-04-21
|
||||
## v3.44.1 - 2025-07-23
|
||||
|
||||
- Internal tasks will no longer be shown as suggestions since they cannot be
|
||||
called (#2309, #2323 by @maxmzkrcensys)
|
||||
- Fixed install script for some ARM platforms (#1516, #2291 by @trulede).
|
||||
- Fixed a regression where fingerprinting was not working correctly if the path
|
||||
to you Taskfile contained a space (#2321, #2322 by @pd93).
|
||||
- Reverted a breaking change to `randInt` (#2312, #2316 by @pd93).
|
||||
- Made new variables `TEST_NAME` and `TEST_DIR` available in fixture tests
|
||||
(#2265 by @pd93).
|
||||
|
||||
## v3.44.0 - 2025-06-08
|
||||
|
||||
- Added `uuid`, `randInt` and `randIntN` template functions (#1346, #2225 by
|
||||
@pd93).
|
||||
- Added new `CLI_ARGS_LIST` array variable which contains the arguments passed
|
||||
to Task after the `--` (the same as `CLI_ARGS`, but an array instead of a
|
||||
string). (#2138, #2139, #2140 by @pd93).
|
||||
- Added `toYaml` and `fromYaml` templating functions (#2217, #2219 by @pd93).
|
||||
- Added `task` field the `--list --json` output (#2256 by @aleksandersh).
|
||||
- Added the ability to
|
||||
[pin included taskfiles](https://taskfile.dev/next/experiments/remote-taskfiles/#manual-checksum-pinning)
|
||||
by specifying a checksum. This works with both local and remote Taskfiles
|
||||
(#2222, #2223 by @pd93).
|
||||
- When using the
|
||||
[Remote Taskfiles experiment](https://github.com/go-task/task/issues/1317),
|
||||
any credentials used in the URL will now be redacted in Task's output (#2100,
|
||||
#2220 by @pd93).
|
||||
- Fixed fuzzy suggestions not working when misspelling a task name (#2192, #2200
|
||||
by @vmaerten).
|
||||
- Fixed a bug where taskfiles in directories containing spaces created
|
||||
directories in the wrong location (#2208, #2216 by @pd93).
|
||||
- Added support for dual JSON schema files, allowing changes without affecting
|
||||
the current schema. The current schemas will only be updated during releases.
|
||||
(#2211 by @vmaerten).
|
||||
- Improved fingerprint documentation by specifying that the method can be set at
|
||||
the root level to apply to all tasks (#2233 by @vmaerten).
|
||||
- Fixed some watcher regressions after #2048 (#2199, #2202, #2241, #2196 by
|
||||
@wazazaby, #2271 by @andreynering).
|
||||
|
||||
## v3.43.3 - 2025-04-27
|
||||
|
||||
Reverted the changes made in #2113 and #2186 that affected the
|
||||
`USER_WORKING_DIR` and built-in variables. This fixes #2206, #2195, #2207 and
|
||||
#2208.
|
||||
|
||||
## v3.43.2 - 2025-04-21
|
||||
|
||||
- Fixed regresion of `CLI_ARGS` being exposed as the wrong type (#2190, #2191 by
|
||||
@vmaerten).
|
||||
|
||||
## v3.43.1 - 2025-04-21
|
||||
|
||||
- Significant improvements were made to the watcher. We migrated from
|
||||
[watcher](https://github.com/radovskyb/watcher) to
|
||||
|
||||
@@ -43,12 +43,16 @@ Studio Code][vscode-task].
|
||||
## 2. Making changes
|
||||
|
||||
- **Code style** - Try to maintain the existing code style where possible. Go
|
||||
code should be formatted by [`gofumpt`][gofumpt] and linted using
|
||||
[`golangci-lint`][golangci-lint]. 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 automatically fix any issues that are found.
|
||||
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
|
||||
@@ -73,8 +77,9 @@ install the extension.
|
||||
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
|
||||
Markdown and is located in the `website/docs` directory. All Markdown documents
|
||||
should have an 80 character line wrap limit (enforced by Prettier).
|
||||
[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
|
||||
@@ -154,7 +159,9 @@ If you have questions, feel free to ask them in the `#help` forum channel on our
|
||||
[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/
|
||||
@@ -166,4 +173,5 @@ If you have questions, feel free to ask them in the `#help` forum channel on our
|
||||
[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 */}
|
||||
|
||||
@@ -182,9 +182,11 @@ includes:
|
||||
|
||||
## Security
|
||||
|
||||
### Automatic checksums
|
||||
|
||||
Running commands from sources that you do not control is always a potential
|
||||
security risk. For this reason, we have added some checks when using remote
|
||||
Taskfiles:
|
||||
security risk. For this reason, we have added some automatic checks when using
|
||||
remote Taskfiles:
|
||||
|
||||
1. When running a task from a remote Taskfile for the first time, Task will
|
||||
print a warning to the console asking you to check that you are sure that you
|
||||
@@ -209,6 +211,38 @@ flag. Before enabling this flag, you should:
|
||||
containing a commit hash) to prevent Task from automatically accepting a
|
||||
prompt that says a remote Taskfile has changed.
|
||||
|
||||
### Manual checksum pinning
|
||||
|
||||
Alternatively, if you expect the contents of your remote files to be a constant
|
||||
value, you can pin the checksum of the included file instead:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: https://taskfile.dev
|
||||
checksum: c153e97e0b3a998a7ed2e61064c6ddaddd0de0c525feefd6bba8569827d8efe9
|
||||
```
|
||||
|
||||
This will disable the automatic checksum prompts discussed above. However, if
|
||||
the checksums do not match, Task will exit immediately with an error. When
|
||||
setting this up for the first time, you may not know the correct value of the
|
||||
checksum. There are a couple of ways you can obtain this:
|
||||
|
||||
1. Add the include normally without the `checksum` key. The first time you run
|
||||
the included Taskfile, a `.task/remote` temporary directory is created. Find
|
||||
the correct set of files for your included Taskfile and open the file that
|
||||
ends with `.checksum`. You can copy the contents of this file and paste it
|
||||
into the `checksum` key of your include. This method is safest as it allows
|
||||
you to inspect the downloaded Taskfile before you pin it.
|
||||
2. Alternatively, add the include with a temporary random value in the
|
||||
`checksum` key. When you try to run the Taskfile, you will get an error that
|
||||
will report the incorrect expected checksum and the actual checksum. You can
|
||||
copy the actual checksum and replace your temporary random value.
|
||||
|
||||
### TLS
|
||||
|
||||
Task currently supports both `http` and `https` URLs. However, the `http`
|
||||
requests will not execute by default unless you run the task with the
|
||||
`--insecure` flag. This is to protect you from accidentally running a remote
|
||||
|
||||
@@ -36,6 +36,14 @@ repository [[package](https://formulae.brew.sh/formula/go-task)]
|
||||
brew install go-task
|
||||
```
|
||||
|
||||
### [Macports][macports] ![][macos] ![][community] \{#macports}
|
||||
|
||||
Task repository is tracked by Macports [[package](https://ports.macports.org/port/go-task/details/)] [[source](https://github.com/macports/macports-ports/blob/master/devel/go-task/Portfile)]:
|
||||
|
||||
```shell
|
||||
port install go-task
|
||||
```
|
||||
|
||||
### [Snap][snapcraft] ![][macos] ![][linux] \{#snap}
|
||||
|
||||
Task is available on [Snapcraft][snapcraft] [[source](https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml)], but keep in mind that your Linux
|
||||
@@ -104,6 +112,14 @@ pacman -S go-task
|
||||
dnf install go-task
|
||||
```
|
||||
|
||||
### FreeBSD ([Ports][freebsdports]) ![][freebsd] ![][community] \{#freebsd}
|
||||
|
||||
[[package](https://cgit.freebsd.org/ports/tree/devel/task)] [[source](https://cgit.freebsd.org/ports/tree/devel/task/Makefile)]
|
||||
|
||||
```shell
|
||||
pkg install task
|
||||
```
|
||||
|
||||
### NixOS ([nix][nix]) ![][nixos] ![][linux] ![][community] \{#nix}
|
||||
|
||||
[[source](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/go/go-task/package.nix)]
|
||||
@@ -304,6 +320,7 @@ task --completion fish > ~/.config/fish/completions/task.fish
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
[homebrew]: https://brew.sh
|
||||
[macports]: https://macports.org
|
||||
[snapcraft]: https://snapcraft.io/task
|
||||
[winget]: https://github.com/microsoft/winget-cli
|
||||
[choco]: https://chocolatey.org
|
||||
@@ -317,6 +334,7 @@ task --completion fish > ~/.config/fish/completions/task.fish
|
||||
[aqua]: https://aquaproj.github.io
|
||||
[pacstall]: https://github.com/pacstall/pacstall
|
||||
[pkgx]: https://pkgx.sh
|
||||
[freebsdports]: https://ports.freebsd.org/cgi/ports.cgi
|
||||
|
||||
[go]: https://golang.org
|
||||
[godownloader]: https://github.com/goreleaser/godownloader
|
||||
@@ -332,4 +350,5 @@ task --completion fish > ~/.config/fish/completions/task.fish
|
||||
[nixos]: https://img.shields.io/badge/NixOS-5277C3?logo=nixos&logoColor=fff
|
||||
[debian]: https://img.shields.io/badge/Debian-A81D33?logo=debian&logoColor=fff
|
||||
[ubuntu]: https://img.shields.io/badge/Ubuntu-E95420?logo=ubuntu&logoColor=fff
|
||||
[freebsd]: https://img.shields.io/badge/FreeBSD-990000?logo=freebsd&logoColor=fff
|
||||
{/* prettier-ignore-end */}
|
||||
|
||||
@@ -104,6 +104,7 @@ structure:
|
||||
"tasks": [
|
||||
{
|
||||
"name": "",
|
||||
"task": "",
|
||||
"desc": "",
|
||||
"summary": "",
|
||||
"up_to_date": false,
|
||||
|
||||
@@ -34,6 +34,7 @@ toc_max_heading_level: 5
|
||||
| `internal` | `bool` | `false` | Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`. |
|
||||
| `aliases` | `[]string` | | Alternative names for the namespace of the included Taskfile. |
|
||||
| `vars` | `map[string]Variable` | | A set of variables to apply to the included Taskfile. |
|
||||
| `checksum` | `string` | | The checksum of the file you expect to include. If the checksum does not match, the file will not be included. |
|
||||
|
||||
:::info
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user