mirror of
https://github.com/go-task/task.git
synced 2026-05-18 21:26:37 +02:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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.')
|
||||
|
||||
@@ -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:
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,5 +1,40 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
|
||||
@@ -98,6 +98,15 @@ tasks:
|
||||
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:
|
||||
|
||||
28
args/args.go
28
args/args.go
@@ -13,24 +13,14 @@ import (
|
||||
// Get fetches the remaining arguments after CLI parsing and splits them into
|
||||
// two groups: the arguments before the double dash (--) and the arguments after
|
||||
// the double dash.
|
||||
func Get() ([]string, string, error) {
|
||||
func Get() ([]string, []string, error) {
|
||||
args := pflag.Args()
|
||||
doubleDashPos := pflag.CommandLine.ArgsLenAtDash()
|
||||
|
||||
if doubleDashPos == -1 {
|
||||
return args, "", nil
|
||||
return args, nil, nil
|
||||
}
|
||||
|
||||
var quotedCliArgs []string
|
||||
for _, arg := range args[doubleDashPos:] {
|
||||
quotedCliArg, err := syntax.Quote(arg, syntax.LangBash)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
quotedCliArgs = append(quotedCliArgs, quotedCliArg)
|
||||
}
|
||||
|
||||
return args[:doubleDashPos], strings.Join(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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -937,3 +937,44 @@ 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"),
|
||||
)
|
||||
}
|
||||
|
||||
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(),
|
||||
WithPostProcessFn(PPRemoveAbsolutePaths),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -218,3 +218,23 @@ func TestListDescInterpolation(t *testing.T) {
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func TestJsonListFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fp, err := filepath.Abs("testdata/json_list_format/Taskfile.yml")
|
||||
require.NoError(t, err)
|
||||
NewFormatterTest(t,
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/json_list_format"),
|
||||
),
|
||||
WithListOptions(task.ListOptions{
|
||||
FormatTaskListAsJSON: true,
|
||||
}),
|
||||
WithFixtureTemplateData(struct {
|
||||
TaskfileLocation string
|
||||
}{
|
||||
TaskfileLocation: fp,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
9
go.mod
9
go.mod
@@ -5,7 +5,7 @@ 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/alecthomas/chroma/v2 v2.18.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
|
||||
@@ -16,6 +16,7 @@ require (
|
||||
github.com/go-git/go-git/v5 v5.16.0
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0
|
||||
github.com/go-task/template v0.1.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
|
||||
@@ -25,8 +26,8 @@ require (
|
||||
github.com/spf13/pflag v1.0.6
|
||||
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.14.0
|
||||
golang.org/x/term v0.32.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/sh/v3 v3.11.0
|
||||
)
|
||||
@@ -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.33.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
46
go.sum
46
go.sum
@@ -7,16 +7,12 @@ github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr
|
||||
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.18.0 h1:6h53Q4hW83SuF+jcsp7CVhLsMozzvQvO8HBbKQW+gn4=
|
||||
github.com/alecthomas/chroma/v2 v2.18.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
||||
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 +21,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 +30,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 +42,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,10 +52,6 @@ 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-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
@@ -78,6 +64,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J
|
||||
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=
|
||||
@@ -146,21 +134,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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.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 +152,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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.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.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
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=
|
||||
|
||||
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,
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -90,15 +90,6 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
|
||||
return r.Run(ctx, p)
|
||||
}
|
||||
|
||||
func escape(s string) string {
|
||||
s = filepath.ToSlash(s)
|
||||
s = strings.ReplaceAll(s, " ", `\ `)
|
||||
s = strings.ReplaceAll(s, "&", `\&`)
|
||||
s = strings.ReplaceAll(s, "(", `\(`)
|
||||
s = strings.ReplaceAll(s, ")", `\)`)
|
||||
return s
|
||||
}
|
||||
|
||||
// ExpandLiteral is a wrapper around [expand.Literal]. It will escape the input
|
||||
// string, expand any shell symbols (such as '~') and resolve any environment
|
||||
// variables.
|
||||
@@ -106,25 +97,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
|
||||
@@ -132,7 +115,6 @@ func ExpandLiteral(s string) (string, error) {
|
||||
// variables. It also expands brace expressions ({a.b}) and globs (*/**) and
|
||||
// returns the results as a list of strings.
|
||||
func ExpandFields(s string) ([]string, error) {
|
||||
s = escape(s)
|
||||
p := syntax.NewParser()
|
||||
var words []*syntax.Word
|
||||
err := p.Words(strings.NewReader(s), func(w *syntax.Word) bool {
|
||||
@@ -146,6 +128,7 @@ func ExpandFields(s string) ([]string, error) {
|
||||
Env: expand.FuncEnviron(os.Getenv),
|
||||
ReadDir2: os.ReadDir,
|
||||
GlobStar: true,
|
||||
NullGlob: true,
|
||||
}
|
||||
return expand.Fields(cfg, words...)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,28 @@ 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,
|
||||
"randInt": rand.Int,
|
||||
"randIntN": rand.IntN,
|
||||
}
|
||||
|
||||
// aliases
|
||||
@@ -83,3 +56,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
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.43.2
|
||||
3.44.0
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.43.2",
|
||||
"version": "3.44.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@go-task/cli",
|
||||
"version": "3.43.2",
|
||||
"version": "3.44.0",
|
||||
"description": "A task runner / simpler Make alternative written in Go",
|
||||
"scripts": {
|
||||
"postinstall": "go-npm install",
|
||||
|
||||
2
setup.go
2
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
|
||||
}
|
||||
|
||||
|
||||
6
task.go
6
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 (
|
||||
|
||||
45
task_test.go
45
task_test.go
@@ -42,9 +42,10 @@ type (
|
||||
FormatterTestOption
|
||||
}
|
||||
TaskTest struct {
|
||||
name string
|
||||
experiments map[*experiments.Experiment]int
|
||||
postProcessFns []PostProcessFn
|
||||
name string
|
||||
experiments map[*experiments.Experiment]int
|
||||
postProcessFns []PostProcessFn
|
||||
fixtureTemplateData any
|
||||
}
|
||||
)
|
||||
|
||||
@@ -79,7 +80,11 @@ func (tt *TaskTest) writeFixture(
|
||||
if goldenFileSuffix != "" {
|
||||
goldenFileName += "-" + goldenFileSuffix
|
||||
}
|
||||
g.Assert(t, goldenFileName, b)
|
||||
if tt.fixtureTemplateData != nil {
|
||||
g.AssertWithTemplate(t, goldenFileName, tt.fixtureTemplateData, b)
|
||||
} else {
|
||||
g.Assert(t, goldenFileName, b)
|
||||
}
|
||||
}
|
||||
|
||||
// writeFixtureBuffer is a wrapper for writing the main output of the task to a
|
||||
@@ -234,6 +239,26 @@ func (opt *setupErrorTestOption) applyToFormatterTest(t *FormatterTest) {
|
||||
t.wantSetupError = true
|
||||
}
|
||||
|
||||
// WithFixtureTemplateData sets up data defined in the golden file using golang
|
||||
// template. Useful if the golden file can change depending on the test.
|
||||
// Example template: {{ .Value }}
|
||||
// Example data definition: struct{ Value string }{Value: "value"}
|
||||
func WithFixtureTemplateData(data any) TestOption {
|
||||
return &fixtureTemplateDataTestOption{data: data}
|
||||
}
|
||||
|
||||
type fixtureTemplateDataTestOption struct {
|
||||
data any
|
||||
}
|
||||
|
||||
func (opt *fixtureTemplateDataTestOption) applyToExecutorTest(t *ExecutorTest) {
|
||||
t.fixtureTemplateData = opt.data
|
||||
}
|
||||
|
||||
func (opt *fixtureTemplateDataTestOption) applyToFormatterTest(t *FormatterTest) {
|
||||
t.fixtureTemplateData = opt.data
|
||||
}
|
||||
|
||||
// Post-processing
|
||||
|
||||
// A PostProcessFn is a function that can be applied to the output of a test
|
||||
@@ -702,6 +727,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 +803,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 +959,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 +1991,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 +2174,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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
4
testdata/fuzzy/Taskfile.yml
vendored
Normal file
4
testdata/fuzzy/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
version: 3
|
||||
|
||||
tasks:
|
||||
install: echo 'install'
|
||||
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
|
||||
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 "/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": "{{ .TaskfileLocation }}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"location": "{{ .TaskfileLocation }}"
|
||||
}
|
||||
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,6 +5,41 @@ sidebar_position: 14
|
||||
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -102,7 +102,8 @@ special variable will be overridden.
|
||||
|
||||
| Var | Description |
|
||||
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `CLI_ARGS` | Contain all extra arguments passed after `--` when calling Task through the CLI. |
|
||||
| `CLI_ARGS` | Contain all extra arguments passed after `--` when calling Task through the CLI as a string. |
|
||||
| `CLI_ARGS_LIST` | Contain all extra arguments passed after `--` when calling Task through the CLI as a shell parsed list. |
|
||||
| `CLI_FORCE` | A boolean containing whether the `--force` or `--force-all` flags were set. |
|
||||
| `CLI_SILENT` | A boolean containing whether the `--silent` flag was set. |
|
||||
| `CLI_VERBOSE` | A boolean containing whether the `--verbose` flag was set. |
|
||||
@@ -115,7 +116,7 @@ special variable will be overridden.
|
||||
| `TASKFILE` | The absolute path of the included Taskfile. |
|
||||
| `TASKFILE_DIR` | The absolute path of the included Taskfile directory. |
|
||||
| `TASK_DIR` | The absolute path of the directory where the task is executed. |
|
||||
| `USER_WORKING_DIR` | The absolute path of the directory `task` was called from, or the value of `--dir` (`-d`) if given. |
|
||||
| `USER_WORKING_DIR` | The absolute path of the directory `task` was called from. |
|
||||
| `CHECKSUM` | The checksum of the files listed in `sources`. Only available within the `status` prop and if method is set to `checksum`. |
|
||||
| `TIMESTAMP` | The date object of the greatest timestamp of the files listed in `sources`. Only available within the `status` prop and if method is set to `timestamp`. |
|
||||
| `TASK_VERSION` | The current version of task. |
|
||||
@@ -269,6 +270,10 @@ description here for completeness. For detailed usage, please refer to the
|
||||
| `b32enc` | Encodes a string into base 32. |
|
||||
| `b32dec` | Decodes a string from base 32. |
|
||||
|
||||
:::note
|
||||
YAML encoding functions are [provided directly by Task](#task-functions).
|
||||
:::
|
||||
|
||||
#### [List Functions][list-functions]
|
||||
|
||||
| Function | Description |
|
||||
@@ -336,6 +341,10 @@ description here for completeness. For detailed usage, please refer to the
|
||||
| `osExt` | Returns the file extension of a filepath. |
|
||||
| `osIsAbs` | Checks if a filepath is absolute. |
|
||||
|
||||
:::note
|
||||
More filepath encoding functions are [provided directly by Task](#task-functions).
|
||||
:::
|
||||
|
||||
#### [Flow Control Functions][flow-control-functions]
|
||||
|
||||
| Function | Description |
|
||||
@@ -375,7 +384,7 @@ Lastly, Task itself provides a few functions:
|
||||
| Function | Description |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `OS` | Returns the operating system. Possible values are `windows`, `linux`, `darwin` (macOS) and `freebsd`. |
|
||||
| `ARCH` | Returns the architecture Task was compiled to: `386`, `amd64`, `arm` or `s390x`. |
|
||||
| `ARCH` | Returns the architecture Task was compiled to: `386`, `amd64`, `arm` or `s390x`. |
|
||||
| `numCPU` | Returns the number of logical CPU's usable by the current process. |
|
||||
| `splitLines` | Splits Unix (`\n`) and Windows (`\r\n`) styled newlines. |
|
||||
| `catLines` | Replaces Unix (`\n`) and Windows (`\r\n`) styled newlines with a space. |
|
||||
@@ -388,6 +397,11 @@ Lastly, Task itself provides a few functions:
|
||||
| `relPath` | Converts an absolute path (second argument) into a relative path, based on a base path (first argument). The same as Go's [filepath.Rel](https://pkg.go.dev/path/filepath#Rel). |
|
||||
| `merge` | Creates a new map that is a copy of the first map with the keys of each subsequent map merged into it. If there is a duplicate key, the value of the last map with that key is used. |
|
||||
| `spew` | Returns the Go representation of a specific variable. Useful for debugging. Uses the [davecgh/go-spew](https://github.com/davecgh/go-spew) package. |
|
||||
| `fromYaml`\* | Decodes a YAML string into an object. |
|
||||
| `toYaml`\* | Encodes an object as a YAML string. |
|
||||
| `uuid` | Generates a new pseudo-random UUIDv4 string. |
|
||||
| `randInt` | Generates a new pseudo-random, non-negative, 32bit integer. Generated numbers are not suitable for security-sensitive work. |
|
||||
| `randIntN` | Generates a new pseudo-random, non-negative, 32bit integer in the half-open interval `[0,n)`. Generated numbers are not suitable for security-sensitive work. |
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
[text/template]: https://pkg.go.dev/text/template
|
||||
|
||||
@@ -61,12 +61,6 @@ In this example, we can run `cd <service>` and `task up` and as long as the
|
||||
`<service>` directory contains a `docker-compose.yml`, the Docker composition
|
||||
will be brought up.
|
||||
|
||||
:::info
|
||||
|
||||
`.USER_WORKING_DIR` will contain the value of the `--dir` (`-d`) flag, if given.
|
||||
|
||||
:::
|
||||
|
||||
### Running a global Taskfile
|
||||
|
||||
If you call Task with the `--global` (alias `-g`) flag, it will look for your
|
||||
@@ -787,7 +781,10 @@ tasks:
|
||||
|
||||
If you prefer these check to be made by the modification timestamp of the files,
|
||||
instead of its checksum (content), just set the `method` property to
|
||||
`timestamp`.
|
||||
`timestamp`. This can be done at two levels:
|
||||
|
||||
At the task level for a specific task:
|
||||
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
@@ -803,6 +800,24 @@ tasks:
|
||||
method: timestamp
|
||||
```
|
||||
|
||||
At the root level of the Taskfile to apply it globally to all tasks:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
method: timestamp # Will be the default for all tasks
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- go build .
|
||||
sources:
|
||||
- ./*.go
|
||||
generates:
|
||||
- app{{exeExt}}
|
||||
```
|
||||
|
||||
|
||||
In situations where you need more flexibility the `status` keyword can be used.
|
||||
You can even combine the two. See the documentation for
|
||||
[status](#using-programmatic-checks-to-indicate-a-task-is-up-to-date) for an
|
||||
@@ -1122,14 +1137,14 @@ tasks:
|
||||
MAP:
|
||||
map: {A: 1, B: 2, C: 3}
|
||||
cmds:
|
||||
- 'echo {{.STRING}}' # Hello, World!
|
||||
- 'echo {{.BOOL}}' # true
|
||||
- 'echo {{.INT}}' # 42
|
||||
- 'echo {{.FLOAT}}' # 3.14
|
||||
- 'echo {{.ARRAY}}' # [1 2 3]
|
||||
- 'echo {{.ARRAY.0}}' # 1
|
||||
- 'echo {{.MAP}}' # map[A:1 B:2 C:3]
|
||||
- 'echo {{.MAP.A}}' # 1
|
||||
- 'echo {{.STRING}}' # Hello, World!
|
||||
- 'echo {{.BOOL}}' # true
|
||||
- 'echo {{.INT}}' # 42
|
||||
- 'echo {{.FLOAT}}' # 3.14
|
||||
- 'echo {{.ARRAY}}' # [1 2 3]
|
||||
- 'echo {{index .ARRAY 0}}' # 1
|
||||
- 'echo {{.MAP}}' # map[A:1 B:2 C:3]
|
||||
- 'echo {{.MAP.A}}' # 1
|
||||
```
|
||||
|
||||
Variables can be set in many places in a Taskfile. When executing
|
||||
@@ -2366,6 +2381,21 @@ if called by another task, either directly or as a dependency.
|
||||
|
||||
:::
|
||||
|
||||
:::caution
|
||||
|
||||
The watcher can misbehave in certain scenarios, in particular for long-running
|
||||
servers.
|
||||
There is a known bug where child processes of the running might not be killed
|
||||
appropriately. It's adviced to avoid running commands as `go run` and prefer
|
||||
`go build [...] && ./binary` instead.
|
||||
|
||||
If you are having issues, you might want to try tools specifically designed for
|
||||
live-reloading, like [Air](https://github.com/air-verse/air/). Also, be sure to
|
||||
[report any issues](https://github.com/go-task/task/issues/new?template=bug_report.yml)
|
||||
to us.
|
||||
|
||||
:::
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
[gotemplate]: https://golang.org/pkg/text/template/
|
||||
[templating-reference]: ./reference/templating.mdx
|
||||
|
||||
@@ -8,3 +8,23 @@ tasks:
|
||||
hello:
|
||||
cmds:
|
||||
- echo "Hello Task!"
|
||||
|
||||
special-variables:
|
||||
silent: true
|
||||
cmds:
|
||||
- 'echo "CLI_ARGS: {{.CLI_ARGS}}"'
|
||||
- 'echo "CLI_ARGS_LIST: {{.CLI_ARGS_LIST}}"'
|
||||
- 'echo "CLI_ARGS_FORCE: {{.CLI_ARGS_FORCE}}"'
|
||||
- 'echo "CLI_ARGS_SILENT: {{.CLI_ARGS_SILENT}}"'
|
||||
- 'echo "CLI_ARGS_VERBOSE: {{.CLI_ARGS_VERBOSE}}"'
|
||||
- 'echo "CLI_ARGS_OFFLINE: {{.CLI_ARGS_OFFLINE}}"'
|
||||
- 'echo "TASK: {{.TASK}}"'
|
||||
- 'echo "ALIAS: {{.ALIAS}}"'
|
||||
- 'echo "TASK_EXE: {{.TASK_EXE}}"'
|
||||
- 'echo "ROOT_TASKFILE: {{.ROOT_TASKFILE}}"'
|
||||
- 'echo "ROOT_DIR: {{.ROOT_DIR}}"'
|
||||
- 'echo "TASKFILE: {{.TASKFILE}}"'
|
||||
- 'echo "TASKFILE_DIR: {{.TASKFILE_DIR}}"'
|
||||
- 'echo "TASK_DIR: {{.TASK_DIR}}"'
|
||||
- 'echo "USER_WORKING_DIR: {{.USER_WORKING_DIR}}"'
|
||||
- 'echo "TASK_VERSION: {{.TASK_VERSION}}"'
|
||||
|
||||
15
website/static/next-schema-taskrc.json
Normal file
15
website/static/next-schema-taskrc.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"title": "Taskrc YAML Schema",
|
||||
"description": "Schema for .taskrc files.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"experiments": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
760
website/static/next-schema.json
Normal file
760
website/static/next-schema.json
Normal file
@@ -0,0 +1,760 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"title": "Taskfile YAML Schema",
|
||||
"description": "Schema for Taskfile files.",
|
||||
"definitions": {
|
||||
"env": {
|
||||
"$ref": "#/definitions/vars"
|
||||
},
|
||||
"platforms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/task_call"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/defer_task_call"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/defer_cmd_call"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/task"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"task": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"cmds": {
|
||||
"description": "A list of commands to be executed.",
|
||||
"$ref": "#/definitions/cmds"
|
||||
},
|
||||
"cmd": {
|
||||
"description": "The command to be executed.",
|
||||
"$ref": "#/definitions/cmd"
|
||||
},
|
||||
"deps": {
|
||||
"description": "A list of dependencies of this task. Tasks defined here will run in parallel before this task.",
|
||||
"$ref": "#/definitions/deps"
|
||||
},
|
||||
"label": {
|
||||
"description": "Overrides the name of the task in the output when a task is run. Supports variables.",
|
||||
"type": "string"
|
||||
},
|
||||
"desc": {
|
||||
"description": "A short description of the task. This is displayed when calling `task --list`.",
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"description": "One or more prompts that will be presented before a task is run. Declining will cancel running the current and any subsequent tasks.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"summary": {
|
||||
"description": "A longer description of the task. This is displayed when calling `task --summary [task]`.",
|
||||
"type": "string"
|
||||
},
|
||||
"aliases": {
|
||||
"description": "A list of alternative names by which the task can be called.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"sources": {
|
||||
"description": "A list of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/glob"
|
||||
}
|
||||
},
|
||||
"generates": {
|
||||
"description": "A list of files meant to be generated by this task. Relevant for `timestamp` method. Can be file paths or star globs.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/glob"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"description": "A list of commands to check if this task should run. The task is skipped otherwise. This overrides `method`, `sources` and `generates`.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"preconditions": {
|
||||
"description": "A list of commands to check if this task should run. If a condition is not met, the task will error.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/precondition"
|
||||
}
|
||||
},
|
||||
"dir": {
|
||||
"description": "The directory in which this task should run. Defaults to the current working directory.",
|
||||
"type": "string"
|
||||
},
|
||||
"set": {
|
||||
"description": "Enables POSIX shell options for all of a task's commands. See https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/set"
|
||||
}
|
||||
},
|
||||
"shopt": {
|
||||
"description": "Enables Bash shell options for all of a task's commands. See https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/shopt"
|
||||
}
|
||||
},
|
||||
"vars": {
|
||||
"description": "A set of variables that can be used in the task.",
|
||||
"$ref": "#/definitions/vars"
|
||||
},
|
||||
"env": {
|
||||
"description": "A set of environment variables that will be made available to shell commands.",
|
||||
"$ref": "#/definitions/env"
|
||||
},
|
||||
"dotenv": {
|
||||
"description": "A list of `.env` file paths to be parsed.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"silent": {
|
||||
"description": "Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`. When combined with the `--list` flag, task descriptions will be hidden.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"interactive": {
|
||||
"description": "Tells task that the command is interactive.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"internal": {
|
||||
"description": "Stops a task from being callable on the command line. It will also be omitted from the output when used with `--list`.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"method": {
|
||||
"description": "Defines which method is used to check the task is up-to-date. `timestamp` will compare the timestamp of the sources and generates files. `checksum` will check the checksum (You probably want to ignore the .task folder in your .gitignore file). `none` skips any validation and always run the task.",
|
||||
"type": "string",
|
||||
"enum": ["none", "checksum", "timestamp"],
|
||||
"default": "none"
|
||||
},
|
||||
"prefix": {
|
||||
"description": "Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`.",
|
||||
"type": "string"
|
||||
},
|
||||
"ignore_error": {
|
||||
"description": "Continue execution if errors happen while executing commands.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"run": {
|
||||
"description": "Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`.",
|
||||
"$ref": "#/definitions/run"
|
||||
},
|
||||
"platforms": {
|
||||
"description": "Specifies which platforms the task should be run on.",
|
||||
"$ref": "#/definitions/platforms"
|
||||
},
|
||||
"requires": {
|
||||
"description": "A list of variables which should be set if this task is to run, if any of these variables are unset the task will error and not run",
|
||||
"$ref": "#/definitions/requires_obj"
|
||||
},
|
||||
"watch": {
|
||||
"description": "Configures a task to run in watch mode automatically.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"cmds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/cmd"
|
||||
}
|
||||
},
|
||||
"cmd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/cmd_call"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/task_call"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/defer_task_call"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/defer_cmd_call"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/for_cmds_call"
|
||||
}
|
||||
]
|
||||
},
|
||||
"deps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/task_call"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/for_deps_call"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"set": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"allexport",
|
||||
"a",
|
||||
"errexit",
|
||||
"e",
|
||||
"noexec",
|
||||
"n",
|
||||
"noglob",
|
||||
"f",
|
||||
"nounset",
|
||||
"u",
|
||||
"xtrace",
|
||||
"x",
|
||||
"pipefail"
|
||||
]
|
||||
},
|
||||
"shopt": {
|
||||
"type": "string",
|
||||
"enum": ["expand_aliases", "globstar", "nullglob"]
|
||||
},
|
||||
"vars": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": ["boolean", "integer", "null", "number", "string", "array"]
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/var_subkey"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"var_subkey": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sh": {
|
||||
"type": "string",
|
||||
"description": "The value will be treated as a command and the output assigned to the variable"
|
||||
},
|
||||
"ref": {
|
||||
"type": "string",
|
||||
"description": "The value will be used to lookup the value of another variable which will then be assigned to this variable"
|
||||
},
|
||||
"map": {
|
||||
"type": "object",
|
||||
"description": "The value will be treated as a literal map type and stored in the variable"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"task_call": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task": {
|
||||
"description": "Name of the task to run",
|
||||
"type": "string"
|
||||
},
|
||||
"vars": {
|
||||
"description": "Values passed to the task called",
|
||||
"$ref": "#/definitions/vars"
|
||||
},
|
||||
"silent": {
|
||||
"description": "Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["task"]
|
||||
},
|
||||
"cmd_call": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cmd": {
|
||||
"description": "Command to run",
|
||||
"type": "string"
|
||||
},
|
||||
"silent": {
|
||||
"description": "Silent mode disables echoing of command before Task runs it",
|
||||
"type": "boolean"
|
||||
},
|
||||
"set": {
|
||||
"description": "Enables POSIX shell options for this command. See https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/set"
|
||||
}
|
||||
},
|
||||
"shopt": {
|
||||
"description": "Enables Bash shell options for this command. See https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/shopt"
|
||||
}
|
||||
},
|
||||
"ignore_error": {
|
||||
"description": "Prevent command from aborting the execution of task even after receiving a status code of 1",
|
||||
"type": "boolean"
|
||||
},
|
||||
"platforms": {
|
||||
"description": "Specifies which platforms the command should be run on.",
|
||||
"$ref": "#/definitions/platforms"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["cmd"]
|
||||
},
|
||||
"defer_task_call": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defer": {
|
||||
"description": "Run a command when the task completes. This command will run even when the task fails",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/task_call"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["defer"]
|
||||
},
|
||||
"defer_cmd_call": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defer": {
|
||||
"description": "Name of the command to defer",
|
||||
"type": "string"
|
||||
},
|
||||
"silent": {
|
||||
"description": "Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["defer"]
|
||||
},
|
||||
"for_cmds_call": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"for": {
|
||||
"$ref": "#/definitions/for"
|
||||
},
|
||||
"cmd": {
|
||||
"description": "Command to run",
|
||||
"type": "string"
|
||||
},
|
||||
"silent": {
|
||||
"description": "Silent mode disables echoing of command before Task runs it",
|
||||
"type": "boolean"
|
||||
},
|
||||
"task": {
|
||||
"description": "Task to run",
|
||||
"type": "string"
|
||||
},
|
||||
"vars": {
|
||||
"description": "Values passed to the task called",
|
||||
"$ref": "#/definitions/vars"
|
||||
},
|
||||
"platforms": {
|
||||
"description": "Specifies which platforms the command should be run on.",
|
||||
"$ref": "#/definitions/platforms"
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{"required": ["cmd"]},
|
||||
{"required": ["task"]}
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"required": ["for"]
|
||||
},
|
||||
"for_deps_call": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"for": {
|
||||
"$ref": "#/definitions/for"
|
||||
},
|
||||
"silent": {
|
||||
"description": "Silent mode disables echoing of command before Task runs it",
|
||||
"type": "boolean"
|
||||
},
|
||||
"task": {
|
||||
"description": "Task to run",
|
||||
"type": "string"
|
||||
},
|
||||
"vars": {
|
||||
"description": "Values passed to the task called",
|
||||
"$ref": "#/definitions/vars"
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{"required": ["cmd"]},
|
||||
{"required": ["task"]}
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"required": ["for"]
|
||||
},
|
||||
"for": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/for_list"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/for_attribute"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/for_var"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/for_matrix"
|
||||
}
|
||||
]
|
||||
},
|
||||
"for_list": {
|
||||
"description": "A list of values to iterate over",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"for_attribute": {
|
||||
"description": "The task attribute to iterate over",
|
||||
"type": "string",
|
||||
"enum": ["sources", "generates"]
|
||||
},
|
||||
"for_var": {
|
||||
"description": "Which variables to iterate over. The variable will be split using any whitespace character by default. This can be changed by using the `split` attribute.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"var": {
|
||||
"description": "Name of the variable to iterate over",
|
||||
"type": "string"
|
||||
},
|
||||
"split": {
|
||||
"description": "String to split the variable on",
|
||||
"type": "string"
|
||||
},
|
||||
"as": {
|
||||
"description": "What the loop variable should be named",
|
||||
"default": "ITEM",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["var"]
|
||||
},
|
||||
"for_matrix": {
|
||||
"description": "A matrix of values to iterate over",
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"required": ["matrix"]
|
||||
},
|
||||
"precondition": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/precondition_obj"
|
||||
}
|
||||
]
|
||||
},
|
||||
"precondition_obj": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sh": {
|
||||
"description": "Command to run. If that command returns 1, the condition will fail",
|
||||
"type": "string"
|
||||
},
|
||||
"msg": {
|
||||
"description": "Failure message to display when the condition fails",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"glob": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/glob_obj"
|
||||
}
|
||||
]
|
||||
},
|
||||
"glob_obj": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exclude": {
|
||||
"description": "File or glob pattern to exclude from the list",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"run": {
|
||||
"type": "string",
|
||||
"enum": ["always", "once", "when_changed"]
|
||||
},
|
||||
"outputString": {
|
||||
"type": "string",
|
||||
"enum": ["interleaved", "prefixed", "group"],
|
||||
"default": "interleaved"
|
||||
},
|
||||
"outputObject": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"group": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"begin": {
|
||||
"type": "string"
|
||||
},
|
||||
"end": {
|
||||
"type": "string"
|
||||
},
|
||||
"error_only": {
|
||||
"description": "Swallows command output on zero exit code",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"requires_obj": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"vars": {
|
||||
"description": "List of variables that must be defined for the task to run",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"enum": { "type": "array",
|
||||
"items": { "type": "string" } }
|
||||
},
|
||||
"required": ["name", "enum"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "Specify the Taskfile format that this file conforms to.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^(0|[1-9]\\d*)(?:\\.(0|[1-9]\\d*))?(?:\\.(0|[1-9]\\d*))?(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"enum": [3]
|
||||
}
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"description": "Defines how the STDOUT and STDERR are printed when running tasks in parallel. The interleaved output prints lines in real time (default). The group output will print the entire output of a command once, after it finishes, so you won't have live feedback for commands that take a long time to run. The prefix output will prefix every line printed by a command with [task-name] as the prefix, but you can customize the prefix for a command with the prefix: attribute.",
|
||||
"anyOf": [
|
||||
{ "$ref": "#/definitions/outputString" },
|
||||
{ "$ref": "#/definitions/outputObject" }
|
||||
]
|
||||
},
|
||||
"method": {
|
||||
"description": "Defines which method is used to check the task is up-to-date. (default: checksum)",
|
||||
"type": "string",
|
||||
"enum": ["none", "checksum", "timestamp"],
|
||||
"default": "checksum"
|
||||
},
|
||||
"includes": {
|
||||
"description": "Imports tasks from the specified taskfiles. The tasks described in the given Taskfiles will be available with the informed namespace.",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskfile": {
|
||||
"description": "The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile.",
|
||||
"type": "string"
|
||||
},
|
||||
"dir": {
|
||||
"description": "The working directory of the included tasks when run.",
|
||||
"type": "string"
|
||||
},
|
||||
"optional": {
|
||||
"description": "If `true`, no errors will be thrown if the specified file does not exist.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"flatten": {
|
||||
"description": "If `true`, the tasks from the included Taskfile will be available in the including Taskfile without a namespace. If a task with the same name already exists in the including Taskfile, an error will be thrown.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"internal": {
|
||||
"description": "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`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"aliases": {
|
||||
"description": "Alternative names for the namespace of the included Taskfile.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"excludes": {
|
||||
"description": "A list of tasks to be excluded from inclusion.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"vars": {
|
||||
"description": "A set of variables to apply to the included Taskfile.",
|
||||
"$ref": "#/definitions/vars"
|
||||
},
|
||||
"checksum": {
|
||||
"description": "The checksum of the file you expect to include. If the checksum does not match, the file will not be included.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"vars": {
|
||||
"description": "A set of global variables.",
|
||||
"$ref": "#/definitions/vars"
|
||||
},
|
||||
"env": {
|
||||
"description": "A set of global environment variables.",
|
||||
"$ref": "#/definitions/env"
|
||||
},
|
||||
"tasks": {
|
||||
"description": "A set of task definitions.",
|
||||
"$ref": "#/definitions/tasks"
|
||||
},
|
||||
"silent": {
|
||||
"description": "Default 'silent' options for this Taskfile. If `false`, can be overridden with `true` in a task by task basis.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"set": {
|
||||
"description": "Enables POSIX shell options for all commands in the Taskfile. See https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/set"
|
||||
}
|
||||
},
|
||||
"shopt": {
|
||||
"description": "Enables Bash shell options for all commands in the Taskfile. See https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/shopt"
|
||||
}
|
||||
},
|
||||
"dotenv": {
|
||||
"type": "array",
|
||||
"description": "A list of `.env` file paths to be parsed.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"run": {
|
||||
"description": "Default 'run' option for this Taskfile. Available options: `always`, `once` and `when_changed`.",
|
||||
"$ref": "#/definitions/run"
|
||||
},
|
||||
"interval": {
|
||||
"description": "Sets a different watch interval when using `--watch`, the default being 100 milliseconds. This string should be a valid Go duration: https://pkg.go.dev/time#ParseDuration.",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+(?:m|s|ms)$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["version"],
|
||||
"anyOf": [
|
||||
{
|
||||
"required": ["includes"]
|
||||
},
|
||||
{
|
||||
"required": ["tasks"]
|
||||
},
|
||||
{
|
||||
"required": ["includes", "tasks"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -684,6 +684,10 @@
|
||||
"vars": {
|
||||
"description": "A set of variables to apply to the included Taskfile.",
|
||||
"$ref": "#/definitions/vars"
|
||||
},
|
||||
"checksum": {
|
||||
"description": "The checksum of the file you expect to include. If the checksum does not match, the file will not be included.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,41 @@ sidebar_position: 14
|
||||
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -102,7 +102,8 @@ special variable will be overridden.
|
||||
|
||||
| Var | Description |
|
||||
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `CLI_ARGS` | Contain all extra arguments passed after `--` when calling Task through the CLI. |
|
||||
| `CLI_ARGS` | Contain all extra arguments passed after `--` when calling Task through the CLI as a string. |
|
||||
| `CLI_ARGS_LIST` | Contain all extra arguments passed after `--` when calling Task through the CLI as a shell parsed list. |
|
||||
| `CLI_FORCE` | A boolean containing whether the `--force` or `--force-all` flags were set. |
|
||||
| `CLI_SILENT` | A boolean containing whether the `--silent` flag was set. |
|
||||
| `CLI_VERBOSE` | A boolean containing whether the `--verbose` flag was set. |
|
||||
@@ -115,7 +116,7 @@ special variable will be overridden.
|
||||
| `TASKFILE` | The absolute path of the included Taskfile. |
|
||||
| `TASKFILE_DIR` | The absolute path of the included Taskfile directory. |
|
||||
| `TASK_DIR` | The absolute path of the directory where the task is executed. |
|
||||
| `USER_WORKING_DIR` | The absolute path of the directory `task` was called from, or the value of `--dir` (`-d`) if given. |
|
||||
| `USER_WORKING_DIR` | The absolute path of the directory `task` was called from. |
|
||||
| `CHECKSUM` | The checksum of the files listed in `sources`. Only available within the `status` prop and if method is set to `checksum`. |
|
||||
| `TIMESTAMP` | The date object of the greatest timestamp of the files listed in `sources`. Only available within the `status` prop and if method is set to `timestamp`. |
|
||||
| `TASK_VERSION` | The current version of task. |
|
||||
@@ -269,6 +270,10 @@ description here for completeness. For detailed usage, please refer to the
|
||||
| `b32enc` | Encodes a string into base 32. |
|
||||
| `b32dec` | Decodes a string from base 32. |
|
||||
|
||||
:::note
|
||||
YAML encoding functions are [provided directly by Task](#task-functions).
|
||||
:::
|
||||
|
||||
#### [List Functions][list-functions]
|
||||
|
||||
| Function | Description |
|
||||
@@ -336,6 +341,10 @@ description here for completeness. For detailed usage, please refer to the
|
||||
| `osExt` | Returns the file extension of a filepath. |
|
||||
| `osIsAbs` | Checks if a filepath is absolute. |
|
||||
|
||||
:::note
|
||||
More filepath encoding functions are [provided directly by Task](#task-functions).
|
||||
:::
|
||||
|
||||
#### [Flow Control Functions][flow-control-functions]
|
||||
|
||||
| Function | Description |
|
||||
@@ -375,7 +384,7 @@ Lastly, Task itself provides a few functions:
|
||||
| Function | Description |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `OS` | Returns the operating system. Possible values are `windows`, `linux`, `darwin` (macOS) and `freebsd`. |
|
||||
| `ARCH` | Returns the architecture Task was compiled to: `386`, `amd64`, `arm` or `s390x`. |
|
||||
| `ARCH` | Returns the architecture Task was compiled to: `386`, `amd64`, `arm` or `s390x`. |
|
||||
| `numCPU` | Returns the number of logical CPU's usable by the current process. |
|
||||
| `splitLines` | Splits Unix (`\n`) and Windows (`\r\n`) styled newlines. |
|
||||
| `catLines` | Replaces Unix (`\n`) and Windows (`\r\n`) styled newlines with a space. |
|
||||
@@ -388,6 +397,11 @@ Lastly, Task itself provides a few functions:
|
||||
| `relPath` | Converts an absolute path (second argument) into a relative path, based on a base path (first argument). The same as Go's [filepath.Rel](https://pkg.go.dev/path/filepath#Rel). |
|
||||
| `merge` | Creates a new map that is a copy of the first map with the keys of each subsequent map merged into it. If there is a duplicate key, the value of the last map with that key is used. |
|
||||
| `spew` | Returns the Go representation of a specific variable. Useful for debugging. Uses the [davecgh/go-spew](https://github.com/davecgh/go-spew) package. |
|
||||
| `fromYaml`\* | Decodes a YAML string into an object. |
|
||||
| `toYaml`\* | Encodes an object as a YAML string. |
|
||||
| `uuid` | Generates a new pseudo-random UUIDv4 string. |
|
||||
| `randInt` | Generates a new pseudo-random, non-negative, 32bit integer. Generated numbers are not suitable for security-sensitive work. |
|
||||
| `randIntN` | Generates a new pseudo-random, non-negative, 32bit integer in the half-open interval `[0,n)`. Generated numbers are not suitable for security-sensitive work. |
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
[text/template]: https://pkg.go.dev/text/template
|
||||
|
||||
@@ -61,12 +61,6 @@ In this example, we can run `cd <service>` and `task up` and as long as the
|
||||
`<service>` directory contains a `docker-compose.yml`, the Docker composition
|
||||
will be brought up.
|
||||
|
||||
:::info
|
||||
|
||||
`.USER_WORKING_DIR` will contain the value of the `--dir` (`-d`) flag, if given.
|
||||
|
||||
:::
|
||||
|
||||
### Running a global Taskfile
|
||||
|
||||
If you call Task with the `--global` (alias `-g`) flag, it will look for your
|
||||
@@ -787,7 +781,10 @@ tasks:
|
||||
|
||||
If you prefer these check to be made by the modification timestamp of the files,
|
||||
instead of its checksum (content), just set the `method` property to
|
||||
`timestamp`.
|
||||
`timestamp`. This can be done at two levels:
|
||||
|
||||
At the task level for a specific task:
|
||||
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
@@ -803,6 +800,24 @@ tasks:
|
||||
method: timestamp
|
||||
```
|
||||
|
||||
At the root level of the Taskfile to apply it globally to all tasks:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
method: timestamp # Will be the default for all tasks
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- go build .
|
||||
sources:
|
||||
- ./*.go
|
||||
generates:
|
||||
- app{{exeExt}}
|
||||
```
|
||||
|
||||
|
||||
In situations where you need more flexibility the `status` keyword can be used.
|
||||
You can even combine the two. See the documentation for
|
||||
[status](#using-programmatic-checks-to-indicate-a-task-is-up-to-date) for an
|
||||
@@ -1122,14 +1137,14 @@ tasks:
|
||||
MAP:
|
||||
map: {A: 1, B: 2, C: 3}
|
||||
cmds:
|
||||
- 'echo {{.STRING}}' # Hello, World!
|
||||
- 'echo {{.BOOL}}' # true
|
||||
- 'echo {{.INT}}' # 42
|
||||
- 'echo {{.FLOAT}}' # 3.14
|
||||
- 'echo {{.ARRAY}}' # [1 2 3]
|
||||
- 'echo {{.ARRAY.0}}' # 1
|
||||
- 'echo {{.MAP}}' # map[A:1 B:2 C:3]
|
||||
- 'echo {{.MAP.A}}' # 1
|
||||
- 'echo {{.STRING}}' # Hello, World!
|
||||
- 'echo {{.BOOL}}' # true
|
||||
- 'echo {{.INT}}' # 42
|
||||
- 'echo {{.FLOAT}}' # 3.14
|
||||
- 'echo {{.ARRAY}}' # [1 2 3]
|
||||
- 'echo {{index .ARRAY 0}}' # 1
|
||||
- 'echo {{.MAP}}' # map[A:1 B:2 C:3]
|
||||
- 'echo {{.MAP.A}}' # 1
|
||||
```
|
||||
|
||||
Variables can be set in many places in a Taskfile. When executing
|
||||
@@ -2366,6 +2381,21 @@ if called by another task, either directly or as a dependency.
|
||||
|
||||
:::
|
||||
|
||||
:::caution
|
||||
|
||||
The watcher can misbehave in certain scenarios, in particular for long-running
|
||||
servers.
|
||||
There is a known bug where child processes of the running might not be killed
|
||||
appropriately. It's adviced to avoid running commands as `go run` and prefer
|
||||
`go build [...] && ./binary` instead.
|
||||
|
||||
If you are having issues, you might want to try tools specifically designed for
|
||||
live-reloading, like [Air](https://github.com/air-verse/air/). Also, be sure to
|
||||
[report any issues](https://github.com/go-task/task/issues/new?template=bug_report.yml)
|
||||
to us.
|
||||
|
||||
:::
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
[gotemplate]: https://golang.org/pkg/text/template/
|
||||
[templating-reference]: ./reference/templating.mdx
|
||||
|
||||
3443
website/yarn.lock
3443
website/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user