Compare commits

..

1 Commits

Author SHA1 Message Date
Pete Davison
3c30a8066d feat: bump minor version when repo is dirty or untagged 2025-08-06 19:45:37 +00:00
263 changed files with 19940 additions and 18864 deletions

View File

@@ -8,6 +8,6 @@ charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
indent_style = tab indent_style = tab
[*.{md,mdx,yml,yaml,json,toml,htm,html,js,ts,vue,css,svg,sh,bash,fish}] [*.{md,mdx,yml,yaml,json,toml,htm,html,js,ts,css,svg,sh,bash,fish}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2

11
.github/renovate.json vendored
View File

@@ -8,17 +8,6 @@
], ],
"mode": "full", "mode": "full",
"addLabels":["area: dependencies"], "addLabels":["area: dependencies"],
"customManagers": [
{
"customType": "regex",
"fileMatch": ["^\\.github/workflows/.*\\.ya?ml$"],
"matchStrings": [
"uses:\\s*golangci/golangci-lint-action@\\S+\\s+with:\\s+version:\\s*(?<currentValue>v[\\d.]+)"
],
"datasourceTemplate": "github-releases",
"depNameTemplate": "golangci/golangci-lint"
}
],
"packageRules": [ "packageRules": [
{ {
"matchManagers": ["github-actions"], "matchManagers": ["github-actions"],

View File

@@ -8,7 +8,7 @@ jobs:
issue-awaiting-response: issue-awaiting-response:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v8 - uses: actions/github-script@v7
with: with:
github-token: ${{secrets.GH_PAT}} github-token: ${{secrets.GH_PAT}}
script: | script: |

View File

@@ -8,7 +8,7 @@ jobs:
issue-closed: issue-closed:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v8 - uses: actions/github-script@v7
with: with:
github-token: ${{secrets.GH_PAT}} github-token: ${{secrets.GH_PAT}}
script: | script: |

View File

@@ -9,7 +9,7 @@ jobs:
if: github.event.label.name == format('status{0} proposed', ':') if: github.event.label.name == format('status{0} proposed', ':')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v8 - uses: actions/github-script@v7
with: with:
github-token: ${{secrets.GH_PAT}} github-token: ${{secrets.GH_PAT}}
script: | script: |
@@ -23,7 +23,7 @@ jobs:
if: github.event.label.name == format('status{0} draft', ':') if: github.event.label.name == format('status{0} draft', ':')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v8 - uses: actions/github-script@v7
with: with:
github-token: ${{secrets.GH_PAT}} github-token: ${{secrets.GH_PAT}}
script: | script: |
@@ -37,7 +37,7 @@ jobs:
if: github.event.label.name == format('status{0} candidate', ':') if: github.event.label.name == format('status{0} candidate', ':')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v8 - uses: actions/github-script@v7
with: with:
github-token: ${{secrets.GH_PAT}} github-token: ${{secrets.GH_PAT}}
script: | script: |
@@ -51,7 +51,7 @@ jobs:
if: github.event.label.name == format('status{0} stable', ':') if: github.event.label.name == format('status{0} stable', ':')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v8 - uses: actions/github-script@v7
with: with:
github-token: ${{secrets.GH_PAT}} github-token: ${{secrets.GH_PAT}}
script: | script: |
@@ -65,7 +65,7 @@ jobs:
if: github.event.label.name == format('status{0} released', ':') if: github.event.label.name == format('status{0} released', ':')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v8 - uses: actions/github-script@v7
with: with:
github-token: ${{secrets.GH_PAT}} github-token: ${{secrets.GH_PAT}}
script: | script: |
@@ -85,7 +85,7 @@ jobs:
if: github.event.label.name == format('status{0} abandoned', ':') if: github.event.label.name == format('status{0} abandoned', ':')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v8 - uses: actions/github-script@v7
with: with:
github-token: ${{secrets.GH_PAT}} github-token: ${{secrets.GH_PAT}}
script: | script: |
@@ -105,7 +105,7 @@ jobs:
if: github.event.label.name == format('status{0} superseded', ':') if: github.event.label.name == format('status{0} superseded', ':')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v8 - uses: actions/github-script@v7
with: with:
github-token: ${{secrets.GH_PAT}} github-token: ${{secrets.GH_PAT}}
script: | script: |

View File

@@ -8,7 +8,7 @@ jobs:
issue-needs-triage: issue-needs-triage:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/github-script@v8 - uses: actions/github-script@v7
with: with:
github-token: ${{secrets.GH_PAT}} github-token: ${{secrets.GH_PAT}}
script: | script: |

View File

@@ -13,31 +13,62 @@ jobs:
name: Lint name: Lint
strategy: strategy:
matrix: matrix:
go-version: [1.24.x, 1.25.x] go-version: [1.23.x, 1.24.x]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version: ${{matrix.go-version}} go-version: ${{matrix.go-version}}
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v9 uses: golangci/golangci-lint-action@v8
with: with:
version: v2.7.2 version: v2.1.0
lint-jsonschema: lint-jsonschema:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-python@v6 - uses: actions/setup-python@v5
with: with:
python-version: 3.14 python-version: 3.12
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: install check-jsonschema - name: install check-jsonschema
run: python -m pip install 'check-jsonschema==0.27.3' run: python -m pip install 'check-jsonschema==0.27.3'
- name: check-jsonschema (metaschema) - name: check-jsonschema (metaschema)
run: check-jsonschema --check-metaschema website/src/public/schema.json run: check-jsonschema --check-metaschema website/static/schema.json
check_doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get changed files in the docs folder
id: changed-files-specific
uses: tj-actions/changed-files@v46
with:
files: website/versioned_docs/**
- uses: actions/github-script@v7
if: steps.changed-files-specific.outputs.any_changed == 'true'
with:
script: |
core.setFailed('website/versioned_docs has changed. Instead you need to update the docs in the website/docs folder.')
check_schema:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get changed files in the docs folder
id: changed-files-specific
uses: tj-actions/changed-files@v46
with:
files: |
website/static/schema.json
website/static/schema-taskrc.json
- uses: actions/github-script@v7
if: steps.changed-files-specific.outputs.any_changed == 'true'
with:
script: |
core.setFailed('schema.json or schema-taskrc.json has changed. Instead you need to update next-schema.json or next-schema-taskrc.json.')

View File

@@ -1,4 +1,4 @@
name: Release nightly name: Realease nightly
on: on:
workflow_dispatch: workflow_dispatch:
@@ -9,22 +9,21 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v5
with: with:
go-version: 1.25.x go-version: 1.24.x
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: latest version: latest
args: release --clean --nightly -f .goreleaser-nightly.yml args: release --clean --nightly
env: env:
GITHUB_TOKEN: ${{secrets.GH_PAT}} GITHUB_TOKEN: ${{secrets.GH_PAT}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}} GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}

View File

@@ -5,53 +5,26 @@ on:
tags: tags:
- 'v*' - 'v*'
permissions:
id-token: write # Required for OIDC
contents: read
jobs: jobs:
goreleaser: goreleaser:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v5
with: with:
go-version: 1.25.x go-version: 1.24.x
- uses: actions/setup-node@v6
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- name: Update npm
run: npm install -g npm@latest
- name: Install Task
uses: go-task/setup-task@v1
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
package_json_file: 'website/package.json'
run_install: 'true'
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: latest version: latest
args: release --clean --draft args: release --clean
env: env:
GITHUB_TOKEN: ${{secrets.GH_PAT}} GITHUB_TOKEN: ${{secrets.GH_PAT}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}} GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
- name: Deploy Website
shell: bash
run: |
task website:deploy:prod

View File

@@ -13,18 +13,18 @@ jobs:
name: Test name: Test
strategy: strategy:
matrix: matrix:
go-version: [1.24.x, 1.25.x] go-version: [1.23.x, 1.24.x]
platform: [ubuntu-latest, macos-latest, windows-latest] platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}} runs-on: ${{matrix.platform}}
steps: steps:
- name: Set up Go ${{matrix.go-version}} - name: Set up Go ${{matrix.go-version}}
uses: actions/setup-go@v6 uses: actions/setup-go@v5
with: with:
go-version: ${{matrix.go-version}} go-version: ${{matrix.go-version}}
id: go id: go
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: Download Go modules - name: Download Go modules
run: go mod download run: go mod download

View File

@@ -1,15 +0,0 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2
pro: true
release:
name_template: 'v{{.Version}}'
nightly:
publish_release: true
keep_single_release: true
version_template: "{{incminor .Version}}-nightly"
includes:
- from_file:
path: ./.goreleaser.yml

View File

@@ -30,8 +30,7 @@ builds:
flags: flags:
- -trimpath - -trimpath
ldflags: ldflags:
- "-s -w" - -s -w # Don't set main.version.
- "{{if .IsNightly}}-X github.com/go-task/task/v3/internal/version.version={{.Version}}{{end}}"
gomod: gomod:
proxy: true proxy: true
@@ -46,10 +45,19 @@ archives:
- goos: windows - goos: windows
formats: [zip] formats: [zip]
release:
draft: true
git: git:
ignore_tags: ignore_tags:
- "{{if not .IsNightly}}nightly{{end}}" - "{{if not .IsNightly}}nightly{{end}}"
nightly:
publish_release: true
keep_single_release: true
version_template: "{{incminor .Version}}-nightly"
snapshot: snapshot:
version_template: '{{.Version}}' version_template: '{{.Version}}'
@@ -68,8 +76,7 @@ nfpms:
formats: formats:
- deb - deb
- rpm - rpm
- apk file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}'
file_name_template: '{{.ProjectName}}_{{.Version}}_{{.Os}}_{{.Arch}}'
contents: contents:
- src: completion/bash/task.bash - src: completion/bash/task.bash
dst: /etc/bash_completion.d/task dst: /etc/bash_completion.d/task
@@ -127,7 +134,7 @@ winget:
repository: repository:
owner: go-task owner: go-task
name: winget-pkgs name: winget-pkgs
branch: 'task-{{.Version}}' branch: 'chore/task-{{.Version}}'
pull_request: pull_request:
enabled: true enabled: true
draft: false draft: false
@@ -136,39 +143,3 @@ winget:
owner: microsoft owner: microsoft
name: winget-pkgs name: winget-pkgs
branch: master branch: master
body: |
/cc @andreynering @pd93 @vmaerten
npms:
- name: "@go-task/cli"
repository: "git+https://github.com/go-task/task.git"
bugs: https://github.com/go-task/task/issues
description: A task runner / simpler Make alternative written in Go
homepage: https://taskfile.dev
license: MIT
author: "The Task authors"
access: public
keywords:
- "task"
- "taskfile"
- "build-tool"
- "task-runner"
cloudsmiths:
- organization: "task"
repository: "{{if not .IsNightly}}task{{end}}"
formats:
- deb
- rpm
- apk
distributions:
deb:
- "any-distro/any-version"
rpm:
- "any-distro/any-version"
alpine:
- "alpine/any-version"
component: main
republish: true

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
22.18.0

View File

@@ -1,12 +1,15 @@
{ {
"yaml.schemas": { "yaml.schemas": {
"./website/src/public/schema.json": [ "./website/static/schema.json": [
"Taskfile.yml", "Taskfile.yml",
"Taskfile.yaml", "tmp/**/*.yml"
"taskfile.yml",
"taskfile.yaml"
] ]
}, },
"search.exclude": {
"**/versioned_docs": true,
"**/versioned_sidesbars": true,
"**/i18n": true
},
"gopls": { "gopls": {
"formatting.local": "github.com/go-task" "formatting.local": "github.com/go-task"
}, },

View File

@@ -1,167 +1,5 @@
# Changelog # Changelog
## v3.46.2 - 2025-12-18
- Fixed a regression on previous release that affected variables passed via
command line (#2588, #2589 by @vmaerten).
## v3.46.1 - 2025-12-18
### ✨ Features
- A small behavior change was made to dependencies. Task will now wait for all
dependencies to finish running before continuing, even if any of them fail. To
opt for the previous behavior, set `failfast: true` either on your
`.taskrc.yml` or per task, or use the `--failfast` flag, which will also work
for `--parallel` (#1246, #2525 by @andreynering).
- The `--summary` flag now displays `vars:` (both global and task-level),
`env:`, and `requires:` sections. Dynamic variables show their shell command
(e.g., `sh: echo "hello"`) instead of the evaluated value (#2486 ,#2524 by
@vmaerten).
- Improved performance of fuzzy task name matching by implementing lazy
initialization. Added `--disable-fuzzy` flag and `disable-fuzzy` taskrc option
to allow disabling fuzzy matching entirely (#2521, #2523 by @vmaerten).
- Added LLM-optimized documentation via VitePress plugin, generating `llms.txt`
and `llms-full.txt` for AI-powered development tools (#2513 by @vmaerten).
- Added `--trusted-hosts` CLI flag and `remote.trusted-hosts` config option to
skip confirmation prompts for specified hosts when using Remote Taskfiles
(#2491, #2473 by @maciejlech).
- When running in GitHub Actions, Task now automatically emits error annotations
on failure, improving visibility in workflow summaries (#2568 by @vmaerten).
- The `--yes` flag is now accessible in templates via the new `CLI_ASSUME_YES`
variable (#2577, #2479 by @semihbkgr).
- Improved shell completion scripts (Zsh, Fish, PowerShell) by adding missing
flags and dynamic experimental feature detection (#2532 by @vmaerten).
- Remote Taskfiles now accept `application/octet-stream` Content-Type (#2536,
#1944 by @vmaerten).
- Shell completion now works when Task is installed or aliased under a different
binary name via TASK_EXE environment variable (#2495, #2468 by @vmaerten).
- Some small fixes and improvements were made to `task --init` and to the
default Taskfile it generates (#2433 by @andreynering).
- Added `--remote-cache-dir` flag and `remote.cache-dir` taskrc option to
customize the cache directory for Remote Taskfiles (#2572 by @vmaerten).
- Zsh completion now supports zstyle verbose option to show or hide task
descriptions (#2571 by @vmaerten).
- Task now automatically enables colored output in CI environments (GitHub
Actions, GitLab CI, etc.) without requiring FORCE_COLOR=1 (#2569 by
@vmaerten).
- Added color taskrc option to explicitly enable or disable colored output
globally (#2569 by @vmaerten).
- Improved Git Remote Taskfiles by switching to go-getter: SSH authentication
now works out of the box and `applyOf` is properly supported (#2512 by
@vmaerten).
### 🐛 Fixes
- Fix RPM upload to Cloudsmith by including the version in the filename to
ensure unique filenames (#2507 by @vmaerten).
- Fix `run: when_changed` to work properly for Taskfiles included multiple times
(#2508, #2511 by @trulede).
- Fixed Zsh and Fish completions to stop suggesting task names after `--`
separator, allowing proper CLI_ARGS completion (#1843, #1844 by
@boiledfroginthewell).
- Watch mode (`--watch`) now always runs the task, regardless of `run: once` or
`run: when_changed` settings (#2566, #1388 by @trulede).
- Fixed global variables (CLI_ARGS, CLI_FORCE, etc.) not being accessible in
root-level vars section (#2403, #2397 by @trulede, @vmaerten).
- Fixed a bug where `ignore_error` was ignored when using `task:` to call
another task (#2552, #363 by @trulede).
- Fixed Zsh completion not suggesting global tasks when using `-g`/`--global`
flag (#1574, #2574 by @vmaerten).
- Fixed Fish completion failing to parse task descriptions containing colons
(e.g., URLs or namespaced functions) (#2101, #2573 by @vmaerten).
- Fixed false positive "property 'for' is not allowed" warnings in IntelliJ when
using `for` loops in Taskfiles (#2576 by @vmaerten).
## v3.45.5 - 2025-11-11
- Fixed bug that made a generic message, instead of an useful one, appear when a
Taskfile could not be found (#2431 by @andreynering).
- Fixed a bug that caused an error when including a Remote Git Taskfile (#2438
by @twelvelabs).
- Fixed issue where `.taskrc.yml` was not returned if reading it failed, and
corrected handling of remote entrypoint Taskfiles (#2460, #2461 by @vmaerten).
- Improved performance of `--list` and `--list-all` by introducing a faster
compilation method that skips source globbing and checksum updates (#1322,
#2053 by @vmaerten).
- Fixed a concurrency bug with `output: group`. This ensures that begin/end
parts won't be mixed up from different tasks (#1208, #2349, #2350 by
@trulede).
- Do not re-evaluate variables for `defer:` (#2244, #2418 by @trulede).
- Improve error message when a Taskfile is not found (#2441, #2494 by
@vmaerten).
- Fixed generic error message `exit status 1` when a dependency task failed
(#2286 by @GrahamDennis).
- Fixed YAML library from the unmaintained `gopkg.in/yaml.v3` to the new fork
maintained by the official YAML org (#2171, #2434 by @andreynering).
- On Windows, the built-in version of the `rm` core utils contains a fix related
to the `-f` flag (#2426,
[u-root/u-root#3464](https://github.com/u-root/u-root/pull/3464),
[mvdan/sh#1199](https://github.com/mvdan/sh/pull/1199), #2506 by
@andreynering).
## v3.45.4 - 2025-09-17
- Fixed a bug where `cache-expiry` could not be defined in `.taskrc.yml` (#2423
by @vmaerten).
- Fixed a bug where `.taskrc.yml` files in parent folders were not read
correctly (#2424 by @vmaerten).
- Fixed a bug where autocomplete in subfolders did not work with zsh (#2425 by
@vmaerten).
## v3.45.3 - 2025-09-15
- Task now includes built-in core utilities to greatly improve compatibility on
Windows. This means that your commands that uses `cp`, `mv`, `mkdir` or any
other common core utility will now work by default on Windows, without extra
setup. This is something we wanted to address for many many years, and it's
finally being shipped!
[Read our blog post this the topic](https://taskfile.dev/blog/windows-core-utils).
(#197, #2360 by @andreynering).
- :sparkles: Built and deployed a [brand new website](https://taskfile.dev)
using [VitePress](https://vitepress.dev) (#2359, #2369, #2371, #2375, #2378 by
@vmaerten, @andreynering, @pd93).
- Began releasing
[nightly builds](https://github.com/go-task/task/releases/tag/nightly). This
will allow people to test our changes before they are fully released and
without having to install Go to build them (#2358 by @vmaerten).
- Added support for global config files in `$XDG_CONFIG_HOME/task/taskrc.yml` or
`$HOME/.taskrc.yml`. Check out our new
[configuration guide](https://taskfile.dev/docs/reference/config) for more
details (#2247, #2380, #2390, #2391 by @vmaerten, @pd93).
- Added experiments to the taskrc schema to clarify the expected keys and values
(#2235 by @vmaerten).
- Added support for new properties in `.taskrc.yml`: insecure, verbose,
concurrency, remote offline, remote timeout, and remote expiry. :warning:
Note: setting offline via environment variable is no longer supported. (#2389
by @vmaerten)
- Added a `--nested` flag when outputting tasks using `--list --json`. This will
output tasks in a nested structure when tasks are namespaced (#2415 by @pd93).
- Enhanced support for tasks with wildcards: they are now logged correctly, and
wildcard parameters are fully considered during fingerprinting (#1808, #1795
by @vmaerten).
- Fixed panic when a variable was declared as an empty hash (`{}`) (#2416, #2417
by @trulede).
#### Package API
- Bumped the minimum version of Go to 1.24 (#2358 by @vmaerten).
#### Other news
We recently released our
[official GitHub Action](https://github.com/go-task/setup-task). This is based
on the fantastic work by the Arduino team who created and maintained the
community version. Now that this is officially adopted, fixes/updates should be
more timely. We have already merged a couple of longstanding PRs in our
[first release](https://github.com/go-task/setup-task/releases/tag/v1.0.0) (by
@pd93, @shrink, @trim21 and all the previous contributors to
[arduino/setup-task](https://github.com/arduino/setup-task/)).
## v3.45.0-v3.45.2 - 2025-09-15
Failed due to an issue with our release process.
## v3.44.1 - 2025-07-23 ## v3.44.1 - 2025-07-23
- Internal tasks will no longer be shown as suggestions since they cannot be - Internal tasks will no longer be shown as suggestions since they cannot be

View File

@@ -1,6 +1,6 @@
<div align="center"> <div align="center">
<a href="https://taskfile.dev"> <a href="https://taskfile.dev">
<img src="website/src/public/img/logo.svg" width="200px" height="200px" /> <img src="website/static/img/logo.svg" width="200px" height="200px" />
</a> </a>
<h1>Task</h1> <h1>Task</h1>
@@ -10,7 +10,7 @@
</p> </p>
<p> <p>
<a href="https://taskfile.dev/docs/installation">Installation</a> | <a href="https://taskfile.dev/docs/getting-started">Getting Started</a> | <a href="https://taskfile.dev/docs/guide">Docs</a> | <a href="https://twitter.com/taskfiledev">Twitter</a> | <a href="https://bsky.app/profile/taskfile.dev">Bluesky</a> | <a href="https://fosstodon.org/@task">Mastodon</a> | <a href="https://discord.gg/6TY36E39UK">Discord</a> <a href="https://taskfile.dev/installation/">Installation</a> | <a href="https://taskfile.dev/usage/">Documentation</a> | <a href="https://twitter.com/taskfiledev">Twitter</a> | <a href="https://bsky.app/profile/taskfile.dev">Bluesky</a> | <a href="https://fosstodon.org/@task">Mastodon</a> | <a href="https://discord.gg/6TY36E39UK">Discord</a>
</p> </p>
<h1>Gold Sponsors</h1> <h1>Gold Sponsors</h1>
@@ -19,12 +19,7 @@
<tr> <tr>
<td align="center" valign="middle"> <td align="center" valign="middle">
<a target="_blank" href="https://devowl.io"> <a target="_blank" href="https://devowl.io">
<img src="website/src/public/img/devowl.io.svg" height="100px" width="200px" title="devowl.io" /> <img src="/website/static/img/devowl.io.svg" height="100px" title="devowl.io" />
</a>
</td>
<td align="center" valign="middle">
<a target="_blank" href="https://magic.dev/">
<img src="website/src/public/img/magic.png" height="100px" width="200px" title="Magic" />
</a> </a>
</td> </td>
</tr> </tr>

View File

@@ -8,7 +8,6 @@ includes:
vars: vars:
BIN: "{{.ROOT_DIR}}/bin" BIN: "{{.ROOT_DIR}}/bin"
GOTESTSUM_FORMAT: '{{if .CI}}github-actions{{else}}pkgname{{end}}'
env: env:
CGO_ENABLED: '0' CGO_ENABLED: '0'
@@ -132,37 +131,29 @@ tasks:
test: test:
desc: Runs test suite desc: Runs test suite
aliases: [t] aliases: [t]
deps: [gotestsum:install]
sources: sources:
- "**/*.go" - "**/*.go"
- "testdata/**/*" - "testdata/**/*"
cmds: cmds:
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./... - go test ./...
test:watch: test:watch:
desc: Runs test suite with watch tests included desc: Runs test suite with watch tests included
deps: [sleepit:build, gotestsum:install] deps: [sleepit:build]
cmds: cmds:
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' ./... -tags 'watch' - go test ./... -tags 'watch'
test:all: test:all:
desc: Runs test suite with signals and watch tests included desc: Runs test suite with signals and watch tests included
deps: [sleepit:build, gotestsum:install] deps: [sleepit:build]
cmds: cmds:
- gotestsum -f '{{.GOTESTSUM_FORMAT}}' -tags 'signals watch' ./... - go test -tags 'signals watch' ./...
goreleaser:test: goreleaser:test:
desc: Tests release process without publishing desc: Tests release process without publishing
cmds: cmds:
- goreleaser --snapshot --clean - goreleaser --snapshot --clean
gotestsum:install:
desc: Installs gotestsum
status:
- command -v gotestsum
cmds:
- go install gotest.tools/gotestsum@latest
goreleaser:install: goreleaser:install:
desc: Installs goreleaser desc: Installs goreleaser
cmds: cmds:
@@ -212,6 +203,7 @@ tasks:
Please wait for the CI to finish and then do the following: Please wait for the CI to finish and then do the following:
- Copy the changelog for v{{.VERSION}} to the GitHub release - Copy the changelog for v{{.VERSION}} to the GitHub release
- Publish the package to NPM with `task npm:publish`
- Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml - Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml
preconditions: preconditions:
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main" - sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
@@ -230,3 +222,8 @@ tasks:
- "git push origin tag v{{.VERSION}}" - "git push origin tag v{{.VERSION}}"
- cmd: printf "%s" '{{.COMPLETE_MESSAGE}}' - cmd: printf "%s" '{{.COMPLETE_MESSAGE}}'
silent: true silent: true
npm:publish:
desc: Publish release to npm
cmds:
- npm publish --access=public

View File

@@ -3,23 +3,33 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/otiai10/copy"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
) )
const ( const (
changelogSource = "CHANGELOG.md" changelogSource = "CHANGELOG.md"
changelogTarget = "website/src/docs/changelog.md" changelogTarget = "website/docs/changelog.mdx"
versionFile = "internal/version/version.txt" 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 changelogReleaseRegex = regexp.MustCompile(`## Unreleased`) var (
changelogReleaseRegex = regexp.MustCompile(`## Unreleased`)
versionRegex = regexp.MustCompile(`(?m)^ "version": "\d+\.\d+\.\d+",$`)
)
// Flags // Flags
var ( var (
@@ -43,7 +53,7 @@ func release() error {
return errors.New("error: expected version number") return errors.New("error: expected version number")
} }
version, err := getVersion(versionFile) version, err := getVersion()
if err != nil { if err != nil {
return err return err
} }
@@ -61,18 +71,36 @@ func release() error {
return err return err
} }
if err := setVersionFile(versionFile, version); err != nil { if err := setVersionFile("internal/version/version.txt", version); err != nil {
return err
}
if err := setJSONVersion("package.json", version); err != nil {
return err
}
if err := setJSONVersion("package-lock.json", version); err != nil {
return err
}
if err := docs(); err != nil {
return err
}
if err := schema(); err != nil {
return err return err
} }
return nil return nil
} }
func getVersion(filename string) (*semver.Version, error) { func getVersion() (*semver.Version, error) {
b, err := os.ReadFile(filename) cmd := exec.Command("git", "describe", "--tags", "--abbrev=0")
b, err := cmd.Output()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return semver.NewVersion(strings.TrimSpace(string(b))) return semver.NewVersion(strings.TrimSpace(string(b)))
} }
@@ -121,17 +149,47 @@ func changelog(version *semver.Version) error {
return err return err
} }
// Wrap the changelog content with v-pre directive for VitePress to prevent
// Vue from interpreting template syntax like {{.TASK_VERSION}}
changelogWithVPre := strings.Replace(changelog, "# Changelog\n\n", "# Changelog\n\n::: v-pre\n\n", 1) + "\n:::"
// Add the frontmatter to the changelog // Add the frontmatter to the changelog
changelogWithFrontmatter := fmt.Sprintf("---\n%s\n---\n\n%s", frontmatter, changelogWithVPre) changelog = fmt.Sprintf("---\n%s\n---\n\n%s", frontmatter, changelog)
// Write the changelog to the target file // Write the changelog to the target file
return os.WriteFile(changelogTarget, []byte(changelogWithFrontmatter), 0o644) return os.WriteFile(changelogTarget, []byte(changelog), 0o644)
} }
func setVersionFile(fileName string, version *semver.Version) error { func setVersionFile(fileName string, version *semver.Version) error {
return os.WriteFile(fileName, []byte(version.String()+"\n"), 0o644) return os.WriteFile(fileName, []byte(version.String()+"\n"), 0o644)
} }
func setJSONVersion(fileName string, version *semver.Version) error {
// Read the JSON file
b, err := os.ReadFile(fileName)
if err != nil {
return err
}
// Replace the version
new := versionRegex.ReplaceAllString(string(b), fmt.Sprintf(` "version": "%s",`, version.String()))
// Write the JSON file
return os.WriteFile(fileName, []byte(new), 0o644)
}
func docs() error {
if err := os.RemoveAll(docsTarget); err != nil {
return err
}
if err := copy.Copy(docsSource, docsTarget); err != nil {
return err
}
return nil
}
func schema() error {
if err := copy.Copy(schemaSource, schemaTarget); err != nil {
return err
}
if err := copy.Copy(schemaTaskrcSource, schemaTaskrcTarget); err != nil {
return err
}
return nil
}

View File

@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@@ -29,34 +28,19 @@ func main() {
Color: flags.Color, Color: flags.Color,
} }
if err, ok := err.(*errors.TaskRunError); ok && flags.ExitCode { if err, ok := err.(*errors.TaskRunError); ok && flags.ExitCode {
emitCIErrorAnnotation(err)
l.Errf(logger.Red, "%v\n", err) l.Errf(logger.Red, "%v\n", err)
os.Exit(err.TaskExitCode()) os.Exit(err.TaskExitCode())
} }
if err, ok := err.(errors.TaskError); ok { if err, ok := err.(errors.TaskError); ok {
emitCIErrorAnnotation(err)
l.Errf(logger.Red, "%v\n", err) l.Errf(logger.Red, "%v\n", err)
os.Exit(err.Code()) os.Exit(err.Code())
} }
emitCIErrorAnnotation(err)
l.Errf(logger.Red, "%v\n", err) l.Errf(logger.Red, "%v\n", err)
os.Exit(errors.CodeUnknown) os.Exit(errors.CodeUnknown)
} }
os.Exit(errors.CodeOk) os.Exit(errors.CodeOk)
} }
// emitCIErrorAnnotation emits an error annotation for supported CI providers.
func emitCIErrorAnnotation(err error) {
if isGA, _ := strconv.ParseBool(os.Getenv("GITHUB_ACTIONS")); !isGA {
return
}
if e, ok := err.(*errors.TaskRunError); ok {
fmt.Fprintf(os.Stdout, "::error title=Task '%s' failed::%v\n", e.TaskName, e.Err)
return
}
fmt.Fprintf(os.Stdout, "::error title=Task failed::%v\n", err)
}
func run() error { func run() error {
log := &logger.Logger{ log := &logger.Logger{
Stdout: os.Stdout, Stdout: os.Stdout,
@@ -144,7 +128,6 @@ func run() error {
flags.ListAll, flags.ListAll,
flags.ListJson, flags.ListJson,
flags.NoStatus, flags.NoStatus,
flags.Nested,
) )
if listOptions.ShouldListTasks() { if listOptions.ShouldListTasks() {
if flags.Silent { if flags.Silent {
@@ -172,23 +155,18 @@ func run() error {
calls = append(calls, &task.Call{Task: "default"}) calls = append(calls, &task.Call{Task: "default"})
} }
// Merge CLI variables first (e.g. FOO=bar) so they take priority over Taskfile defaults
e.Taskfile.Vars.Merge(globals, nil)
// Then ReverseMerge special variables so they're available for templating
cliArgsPostDashQuoted, err := args.ToQuotedString(cliArgsPostDash) cliArgsPostDashQuoted, err := args.ToQuotedString(cliArgsPostDash)
if err != nil { if err != nil {
return err return err
} }
specialVars := ast.NewVars() globals.Set("CLI_ARGS", ast.Var{Value: cliArgsPostDashQuoted})
specialVars.Set("CLI_ARGS", ast.Var{Value: cliArgsPostDashQuoted}) globals.Set("CLI_ARGS_LIST", ast.Var{Value: cliArgsPostDash})
specialVars.Set("CLI_ARGS_LIST", ast.Var{Value: cliArgsPostDash}) globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
specialVars.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll}) globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
specialVars.Set("CLI_SILENT", ast.Var{Value: flags.Silent}) globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
specialVars.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose}) globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
specialVars.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline}) e.Taskfile.Vars.Merge(globals, nil)
specialVars.Set("CLI_ASSUME_YES", ast.Var{Value: flags.AssumeYes})
e.Taskfile.Vars.ReverseMerge(specialVars, nil)
if !flags.Watch { if !flags.Watch {
e.InterceptInterruptSignals() e.InterceptInterruptSignals()
} }

38
cmd/tmp/main.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
defer cancel()
if err := run(ctx); err != nil {
fmt.Println(ctx.Err())
fmt.Println(err)
}
}
func run(ctx context.Context) error {
req, err := http.NewRequest("GET", "https://taskfile.dev/schema.json", nil)
if err != nil {
fmt.Println(1)
return err
}
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
if ctx.Err() != nil {
fmt.Println(2)
return err
}
fmt.Println(3)
return err
}
defer resp.Body.Close()
return nil
}

View File

@@ -61,14 +61,13 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
newVar := templater.ReplaceVar(v, cache) newVar := templater.ReplaceVar(v, cache)
// If the variable should not be evaluated, but is nil, set it to an empty string // If the variable should not be evaluated, but is nil, set it to an empty string
// This stops empty interface errors when using the templater to replace values later // This stops empty interface errors when using the templater to replace values later
// Preserve the Sh field so it can be displayed in summary
if !evaluateShVars && newVar.Value == nil { if !evaluateShVars && newVar.Value == nil {
result.Set(k, ast.Var{Value: "", Sh: newVar.Sh}) result.Set(k, ast.Var{Value: ""})
return nil return nil
} }
// If the variable should not be evaluated and it is set, we can set it and return // If the variable should not be evaluated and it is set, we can set it and return
if !evaluateShVars { if !evaluateShVars {
result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh}) result.Set(k, ast.Var{Value: newVar.Value})
return nil return nil
} }
// Now we can check for errors since we've handled all the cases when we don't want to evaluate // Now we can check for errors since we've handled all the cases when we don't want to evaluate

View File

@@ -1,7 +1,6 @@
# vim: set tabstop=2 shiftwidth=2 expandtab: # vim: set tabstop=2 shiftwidth=2 expandtab:
_GO_TASK_COMPLETION_LIST_OPTION='--list-all' _GO_TASK_COMPLETION_LIST_OPTION='--list-all'
TASK_CMD="${TASK_EXE:-task}"
function _task() function _task()
{ {
@@ -22,7 +21,7 @@ function _task()
# Handle special arguments of options. # Handle special arguments of options.
case "$prev" in case "$prev" in
-d|--dir|--remote-cache-dir) -d|--dir)
_filedir -d _filedir -d
return $? return $?
;; ;;
@@ -53,4 +52,4 @@ function _task()
__ltrim_colon_completions "$cur" __ltrim_colon_completions "$cur"
} }
complete -F _task "$TASK_CMD" complete -F _task task

View File

@@ -1,31 +1,4 @@
set -l GO_TASK_PROGNAME (if set -q GO_TASK_PROGNAME; echo $GO_TASK_PROGNAME; else if set -q TASK_EXE; echo $TASK_EXE; else; echo task; end) set -l GO_TASK_PROGNAME task
# Cache variables for experiments (global)
set -g __task_experiments_cache ""
set -g __task_experiments_cache_time 0
# Helper function to get experiments with 1-second cache
function __task_get_experiments
set -l now (date +%s)
set -l ttl 1 # Cache for 1 second only
# Return cached value if still valid
if test (math "$now - $__task_experiments_cache_time") -lt $ttl
printf '%s\n' $__task_experiments_cache
return
end
# Refresh cache
set -g __task_experiments_cache (task --experiments 2>/dev/null)
set -g __task_experiments_cache_time $now
printf '%s\n' $__task_experiments_cache
end
# Helper function to check if an experiment is enabled
function __task_is_experiment_enabled
set -l experiment $argv[1]
__task_get_experiments | string match -qr "^\* $experiment:.*on"
end
function __task_get_tasks --description "Prints all available tasks with their description" --inherit-variable GO_TASK_PROGNAME function __task_get_tasks --description "Prints all available tasks with their description" --inherit-variable GO_TASK_PROGNAME
# Check if the global task is requested # Check if the global task is requested
@@ -54,63 +27,28 @@ function __task_get_tasks --description "Prints all available tasks with their d
end end
# Grab names and descriptions (if any) of the tasks # Grab names and descriptions (if any) of the tasks
set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):\s\{2,\}\(.*\)\s\{2,\}(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):\s\{2,\}\(.*\)/\1\t\2/'| string split0) set -l output (echo $rawOutput | sed -e '1d; s/\* \(.*\):\s*\(.*\)\s*(\(aliases.*\))/\1\t\2\t\3/' -e 's/\* \(.*\):\s*\(.*\)/\1\t\2/'| string split0)
if test $output if test $output
echo $output echo $output
end end
end end
complete -c $GO_TASK_PROGNAME \ complete -c $GO_TASK_PROGNAME -d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was
-d 'Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was specified.' \ specified.' -xa "(__task_get_tasks)"
-xa "(__task_get_tasks)" \
-n "not __fish_seen_subcommand_from --"
# Standard flags complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
complete -c $GO_TASK_PROGNAME -s a -l list-all -d 'list all tasks' complete -c $GO_TASK_PROGNAME -s d -l dir -d 'sets directory of execution'
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)' complete -c $GO_TASK_PROGNAME -l dry -d 'compiles and prints tasks in the order that they would be run, without executing them'
complete -c $GO_TASK_PROGNAME -s C -l concurrency -d 'limit number of concurrent tasks' complete -c $GO_TASK_PROGNAME -s f -l force -d 'forces execution even when the task is up-to-date'
complete -c $GO_TASK_PROGNAME -l completion -d 'generate shell completion script' -xa "bash zsh fish powershell" complete -c $GO_TASK_PROGNAME -s h -l help -d 'shows Task usage'
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set directory of execution' complete -c $GO_TASK_PROGNAME -s i -l init -d 'creates a new Taskfile.yml in the current folder'
complete -c $GO_TASK_PROGNAME -l disable-fuzzy -d 'disable fuzzy matching for task names' complete -c $GO_TASK_PROGNAME -s l -l list -d 'lists tasks with description of current Taskfile'
complete -c $GO_TASK_PROGNAME -s n -l dry -d 'compile and print tasks without executing' complete -c $GO_TASK_PROGNAME -s o -l output -d 'sets output style: [interleaved|group|prefixed]' -xa "interleaved group prefixed"
complete -c $GO_TASK_PROGNAME -s x -l exit-code -d 'pass-through exit code of task command' complete -c $GO_TASK_PROGNAME -s p -l parallel -d 'executes tasks provided on command line in parallel'
complete -c $GO_TASK_PROGNAME -l experiments -d 'list available experiments' complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disables echoing'
complete -c $GO_TASK_PROGNAME -s F -l failfast -d 'when running tasks in parallel, stop all tasks if one fails' complete -c $GO_TASK_PROGNAME -l status -d 'exits with non-zero exit code if any of the given tasks is not up-to-date'
complete -c $GO_TASK_PROGNAME -s f -l force -d 'force execution even when up-to-date' complete -c $GO_TASK_PROGNAME -l summary -d 'show summary about a task'
complete -c $GO_TASK_PROGNAME -s g -l global -d 'run global Taskfile from home directory' complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose which Taskfile to run. Defaults to "Taskfile.yml"'
complete -c $GO_TASK_PROGNAME -s h -l help -d 'show help' complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'enables verbose mode'
complete -c $GO_TASK_PROGNAME -s i -l init -d 'create new Taskfile' complete -c $GO_TASK_PROGNAME -l version -d 'show Task version'
complete -c $GO_TASK_PROGNAME -l insecure -d 'allow insecure Taskfile downloads' complete -c $GO_TASK_PROGNAME -s w -l watch -d 'enables watch of the given task'
complete -c $GO_TASK_PROGNAME -s I -l interval -d 'interval to watch for changes'
complete -c $GO_TASK_PROGNAME -s j -l json -d 'format task list as JSON'
complete -c $GO_TASK_PROGNAME -s l -l list -d 'list tasks with descriptions'
complete -c $GO_TASK_PROGNAME -l nested -d 'nest namespaces when listing as JSON'
complete -c $GO_TASK_PROGNAME -l no-status -d 'ignore status when listing as JSON'
complete -c $GO_TASK_PROGNAME -s o -l output -d 'set output style' -xa "interleaved group prefixed"
complete -c $GO_TASK_PROGNAME -l output-group-begin -d 'message template before grouped output'
complete -c $GO_TASK_PROGNAME -l output-group-end -d 'message template after grouped output'
complete -c $GO_TASK_PROGNAME -l output-group-error-only -d 'hide output from successful tasks'
complete -c $GO_TASK_PROGNAME -s p -l parallel -d 'execute tasks in parallel'
complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disable echoing'
complete -c $GO_TASK_PROGNAME -l sort -d 'set task sorting order' -xa "default alphanumeric none"
complete -c $GO_TASK_PROGNAME -l status -d 'exit non-zero if tasks not up-to-date'
complete -c $GO_TASK_PROGNAME -l summary -d 'show task summary'
complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose Taskfile to run'
complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'verbose output'
complete -c $GO_TASK_PROGNAME -l version -d 'show version'
complete -c $GO_TASK_PROGNAME -s w -l watch -d 'watch mode, re-run on changes'
complete -c $GO_TASK_PROGNAME -s y -l yes -d 'assume yes to all prompts'
# Experimental flags (dynamically checked at completion time via -n condition)
# GentleForce experiment
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled GENTLE_FORCE" -l force-all -d 'force execution of task and all dependencies'
# RemoteTaskfiles experiment - Options
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l offline -d 'use only local or cached Taskfiles'
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l timeout -d 'timeout for remote Taskfile downloads'
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l expiry -d 'cache expiry duration'
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l remote-cache-dir -d 'directory to cache remote Taskfiles' -xa "(__fish_complete_directories)"
# RemoteTaskfiles experiment - Operations
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l download -d 'download remote Taskfile'
complete -c $GO_TASK_PROGNAME -n "__task_is_experiment_enabled REMOTE_TASKFILES" -l clear-cache -d 'clear remote Taskfile cache'

View File

@@ -5,82 +5,22 @@ Register-ArgumentCompleter -CommandName task -ScriptBlock {
if ($commandName.StartsWith('-')) { if ($commandName.StartsWith('-')) {
$completions = @( $completions = @(
# Standard flags (alphabetical order) [CompletionResult]::new('--list-all ', '--list-all ', [CompletionResultType]::ParameterName, 'list all tasks'),
[CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'list all tasks'), [CompletionResult]::new('--color ', '--color', [CompletionResultType]::ParameterName, '--color'),
[CompletionResult]::new('--list-all', '--list-all', [CompletionResultType]::ParameterName, 'list all tasks'), [CompletionResult]::new('--concurrency=', '--concurrency=', [CompletionResultType]::ParameterName, 'concurrency'),
[CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'colored output'), [CompletionResult]::new('--interval=', '--interval=', [CompletionResultType]::ParameterName, 'interval'),
[CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'colored output'), [CompletionResult]::new('--output=interleaved ', '--output=interleaved', [CompletionResultType]::ParameterName, '--output='),
[CompletionResult]::new('-C', '-C', [CompletionResultType]::ParameterName, 'limit concurrent tasks'), [CompletionResult]::new('--output=group ', '--output=group', [CompletionResultType]::ParameterName, '--output='),
[CompletionResult]::new('--concurrency', '--concurrency', [CompletionResultType]::ParameterName, 'limit concurrent tasks'), [CompletionResult]::new('--output=prefixed ', '--output=prefixed', [CompletionResultType]::ParameterName, '--output='),
[CompletionResult]::new('--completion', '--completion', [CompletionResultType]::ParameterName, 'generate shell completion'), [CompletionResult]::new('--dry ', '--dry', [CompletionResultType]::ParameterName, '--dry'),
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'set directory'), [CompletionResult]::new('--force ', '--force', [CompletionResultType]::ParameterName, '--force'),
[CompletionResult]::new('--dir', '--dir', [CompletionResultType]::ParameterName, 'set directory'), [CompletionResult]::new('--parallel ', '--parallel', [CompletionResultType]::ParameterName, '--parallel'),
[CompletionResult]::new('--disable-fuzzy', '--disable-fuzzy', [CompletionResultType]::ParameterName, 'disable fuzzy matching'), [CompletionResult]::new('--silent ', '--silent', [CompletionResultType]::ParameterName, '--silent'),
[CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'dry run'), [CompletionResult]::new('--status ', '--status', [CompletionResultType]::ParameterName, '--status'),
[CompletionResult]::new('--dry', '--dry', [CompletionResultType]::ParameterName, 'dry run'), [CompletionResult]::new('--verbose ', '--verbose', [CompletionResultType]::ParameterName, '--verbose'),
[CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'pass-through exit code'), [CompletionResult]::new('--watch ', '--watch', [CompletionResultType]::ParameterName, '--watch')
[CompletionResult]::new('--exit-code', '--exit-code', [CompletionResultType]::ParameterName, 'pass-through exit code'),
[CompletionResult]::new('--experiments', '--experiments', [CompletionResultType]::ParameterName, 'list experiments'),
[CompletionResult]::new('-F', '-F', [CompletionResultType]::ParameterName, 'fail fast on pallalel tasks'),
[CompletionResult]::new('--failfast', '--failfast', [CompletionResultType]::ParameterName, 'force execution'),
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'force execution'),
[CompletionResult]::new('--force', '--force', [CompletionResultType]::ParameterName, 'force execution'),
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'run global Taskfile'),
[CompletionResult]::new('--global', '--global', [CompletionResultType]::ParameterName, 'run global Taskfile'),
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'show help'),
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'show help'),
[CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'create new Taskfile'),
[CompletionResult]::new('--init', '--init', [CompletionResultType]::ParameterName, 'create new Taskfile'),
[CompletionResult]::new('--insecure', '--insecure', [CompletionResultType]::ParameterName, 'allow insecure downloads'),
[CompletionResult]::new('-I', '-I', [CompletionResultType]::ParameterName, 'watch interval'),
[CompletionResult]::new('--interval', '--interval', [CompletionResultType]::ParameterName, 'watch interval'),
[CompletionResult]::new('-j', '-j', [CompletionResultType]::ParameterName, 'format as JSON'),
[CompletionResult]::new('--json', '--json', [CompletionResultType]::ParameterName, 'format as JSON'),
[CompletionResult]::new('-l', '-l', [CompletionResultType]::ParameterName, 'list tasks'),
[CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'list tasks'),
[CompletionResult]::new('--nested', '--nested', [CompletionResultType]::ParameterName, 'nest namespaces in JSON'),
[CompletionResult]::new('--no-status', '--no-status', [CompletionResultType]::ParameterName, 'ignore status in JSON'),
[CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'set output style'),
[CompletionResult]::new('--output', '--output', [CompletionResultType]::ParameterName, 'set output style'),
[CompletionResult]::new('--output-group-begin', '--output-group-begin', [CompletionResultType]::ParameterName, 'template before group'),
[CompletionResult]::new('--output-group-end', '--output-group-end', [CompletionResultType]::ParameterName, 'template after group'),
[CompletionResult]::new('--output-group-error-only', '--output-group-error-only', [CompletionResultType]::ParameterName, 'hide successful output'),
[CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'execute in parallel'),
[CompletionResult]::new('--parallel', '--parallel', [CompletionResultType]::ParameterName, 'execute in parallel'),
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'silent mode'),
[CompletionResult]::new('--silent', '--silent', [CompletionResultType]::ParameterName, 'silent mode'),
[CompletionResult]::new('--sort', '--sort', [CompletionResultType]::ParameterName, 'task sorting order'),
[CompletionResult]::new('--status', '--status', [CompletionResultType]::ParameterName, 'check task status'),
[CompletionResult]::new('--summary', '--summary', [CompletionResultType]::ParameterName, 'show task summary'),
[CompletionResult]::new('-t', '-t', [CompletionResultType]::ParameterName, 'choose Taskfile'),
[CompletionResult]::new('--taskfile', '--taskfile', [CompletionResultType]::ParameterName, 'choose Taskfile'),
[CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'verbose output'),
[CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'verbose output'),
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'show version'),
[CompletionResult]::new('-w', '-w', [CompletionResultType]::ParameterName, 'watch mode'),
[CompletionResult]::new('--watch', '--watch', [CompletionResultType]::ParameterName, 'watch mode'),
[CompletionResult]::new('-y', '-y', [CompletionResultType]::ParameterName, 'assume yes'),
[CompletionResult]::new('--yes', '--yes', [CompletionResultType]::ParameterName, 'assume yes')
) )
# Experimental flags (dynamically added based on enabled experiments)
$experiments = & task --experiments 2>$null | Out-String
if ($experiments -match '\* GENTLE_FORCE:.*on') {
$completions += [CompletionResult]::new('--force-all', '--force-all', [CompletionResultType]::ParameterName, 'force all dependencies')
}
if ($experiments -match '\* REMOTE_TASKFILES:.*on') {
# Options
$completions += [CompletionResult]::new('--offline', '--offline', [CompletionResultType]::ParameterName, 'use cached Taskfiles')
$completions += [CompletionResult]::new('--timeout', '--timeout', [CompletionResultType]::ParameterName, 'download timeout')
$completions += [CompletionResult]::new('--expiry', '--expiry', [CompletionResultType]::ParameterName, 'cache expiry')
$completions += [CompletionResult]::new('--remote-cache-dir', '--remote-cache-dir', [CompletionResultType]::ParameterName, 'cache directory')
# Operations
$completions += [CompletionResult]::new('--download', '--download', [CompletionResultType]::ParameterName, 'download remote Taskfile')
$completions += [CompletionResult]::new('--clear-cache', '--clear-cache', [CompletionResultType]::ParameterName, 'clear cache')
}
return $completions.Where{ $_.CompletionText.StartsWith($commandName) } return $completions.Where{ $_.CompletionText.StartsWith($commandName) }
} }

View File

@@ -1,147 +1,67 @@
#compdef task #compdef task
compdef _task task
typeset -A opt_args typeset -A opt_args
TASK_CMD="${TASK_EXE:-task}"
compdef _task "$TASK_CMD"
_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}" _GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"
# Check if an experiment is enabled
function __task_is_experiment_enabled() {
local experiment=$1
task --experiments 2>/dev/null | grep -q "^\* ${experiment}:.*on"
}
# Listing commands from Taskfile.yml # Listing commands from Taskfile.yml
function __task_list() { function __task_list() {
local -a scripts cmd local -a scripts cmd
local -i enabled=0 local -i enabled=0
local taskfile item task desc local taskfile item task desc
cmd=($TASK_CMD) cmd=(task)
taskfile=${(Qv)opt_args[(i)-t|--taskfile]} taskfile=${(Qv)opt_args[(i)-t|--taskfile]}
taskfile=${taskfile//\~/$HOME} taskfile=${taskfile//\~/$HOME}
for arg in "${words[@]:0:$CURRENT}"; do
if [[ "$arg" = "--" ]]; then
# Use default completion for words after `--` as they are CLI_ARGS.
_default
return 0
fi
done
if [[ -n "$taskfile" && -f "$taskfile" ]]; then if [[ -n "$taskfile" && -f "$taskfile" ]]; then
cmd+=(--taskfile "$taskfile")
fi
# Check if global flag is set
if (( ${+opt_args[(i)-g|--global]} )); then
cmd+=(--global)
fi
if output=$("${cmd[@]}" $_GO_TASK_COMPLETION_LIST_OPTION 2>/dev/null); then
enabled=1 enabled=1
cmd+=(--taskfile "$taskfile")
else
for taskfile in {T,t}askfile{,.dist}.{yaml,yml}; do
if [[ -f "$taskfile" ]]; then
enabled=1
break
fi
done
fi fi
(( enabled )) || return 0 (( enabled )) || return 0
scripts=() scripts=()
for item in "${(@)${(f)$("${cmd[@]}" $_GO_TASK_COMPLETION_LIST_OPTION)}[2,-1]#\* }"; do
# Read zstyle verbose option (default = true via -T)
local show_desc
zstyle -T ":completion:${curcontext}:" verbose && show_desc=true || show_desc=false
for item in "${(@)${(f)output}[2,-1]#\* }"; do
task="${item%%:[[:space:]]*}" task="${item%%:[[:space:]]*}"
desc="${item##[^[:space:]]##[[:space:]]##}"
if [[ "$show_desc" == "true" ]]; then scripts+=( "${task//:/\\:}:$desc" )
local desc="${item##[^[:space:]]##[[:space:]]##}"
scripts+=( "${task//:/\\:}:$desc" )
else
scripts+=( "$task" )
fi
done done
_describe 'Task to run' scripts
if [[ "$show_desc" == "true" ]]; then
_describe 'Task to run' scripts
else
compadd -Q -a scripts
fi
} }
_task() { _task() {
local -a standard_args operation_args _arguments \
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \
standard_args=( '(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' '(-f --force)'{-f,--force}'[run even if task is up-to-date]' \
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' '(-c --color)'{-c,--color}'[colored output]' \
'(-F --failfast)'{-F,--failfast}'[when running tasks in parallel, stop all tasks if one fails]' '(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \
'(-f --force)'{-f,--force}'[run even if task is up-to-date]' '(--dry)--dry[dry-run mode, compile and print tasks only]' \
'(-c --color)'{-c,--color}'[colored output]' '(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \
'(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell)' '(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' '(--output-group-end)--output-group-end[message template after grouped output]:template text: ' \
'(--disable-fuzzy)--disable-fuzzy[disable fuzzy matching for task names]' '(-s --silent)'{-s,--silent}'[disable echoing]' \
'(-n --dry)'{-n,--dry}'[compiles and prints tasks without executing]' '(--status)--status[exit non-zero if supplied tasks not up-to-date]' \
'(--dry)--dry[dry-run mode, compile and print tasks only]' '(--summary)--summary[show summary\: field from tasks instead of running them]' \
'(-x --exit-code)'{-x,--exit-code}'[pass-through exit code of task command]' '(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files' \
'(--experiments)--experiments[list available experiments]' '(-v --verbose)'{-v,--verbose}'[verbose mode]' \
'(-g --global)'{-g,--global}'[run global Taskfile from home directory]' '(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]' \
'(--insecure)--insecure[allow insecure Taskfile downloads]' + '(operation)' \
'(-I --interval)'{-I,--interval}'[interval to watch for changes]:duration: ' {-l,--list}'[list describable tasks]' \
'(-j --json)'{-j,--json}'[format task list as JSON]' {-a,--list-all}'[list all tasks]' \
'(--nested)--nested[nest namespaces when listing as JSON]' {-i,--init}'[create new Taskfile.yml]' \
'(--no-status)--no-status[ignore status when listing as JSON]' '(-*)'{-h,--help}'[show help]' \
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' '(-*)--version[show version and exit]' \
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' '*: :__task_list'
'(--output-group-end)--output-group-end[message template after grouped output]:template text: '
'(--output-group-error-only)--output-group-error-only[hide output from successful tasks]'
'(-s --silent)'{-s,--silent}'[disable echoing]'
'(--sort)--sort[set task sorting order]:order:(default alphanumeric none)'
'(--status)--status[exit non-zero if supplied tasks not up-to-date]'
'(--summary)--summary[show summary\: field from tasks instead of running them]'
'(-t --taskfile)'{-t,--taskfile}'[specify a different taskfile]:taskfile:_files'
'(-v --verbose)'{-v,--verbose}'[verbose mode]'
'(-w --watch)'{-w,--watch}'[watch-mode for given tasks, re-run when inputs change]'
'(-y --yes)'{-y,--yes}'[assume yes to all prompts]'
)
# Experimental flags (dynamically added based on enabled experiments)
# Options (modify behavior)
if __task_is_experiment_enabled "GENTLE_FORCE"; then
standard_args+=('(--force-all)--force-all[force execution of task and all dependencies]')
fi
if __task_is_experiment_enabled "REMOTE_TASKFILES"; then
standard_args+=(
'(--offline --download)--offline[use only local or cached Taskfiles]'
'(--timeout)--timeout[timeout for remote Taskfile downloads]:duration: '
'(--expiry)--expiry[cache expiry duration]:duration: '
'(--remote-cache-dir)--remote-cache-dir[directory to cache remote Taskfiles]:cache dir:_dirs'
)
fi
operation_args=(
# Task names completion (can be specified multiple times)
'(operation)*: :__task_list'
# Operational args completion (mutually exclusive)
+ '(operation)'
'(*)'{-l,--list}'[list describable tasks]'
'(*)'{-a,--list-all}'[list all tasks]'
'(*)'{-i,--init}'[create new Taskfile.yml]'
'(- *)'{-h,--help}'[show help]'
'(- *)--version[show version and exit]'
)
# Experimental operations (dynamically added based on enabled experiments)
if __task_is_experiment_enabled "REMOTE_TASKFILES"; then
standard_args+=(
'(--offline --clear-cache)--download[download remote Taskfile]'
)
operation_args+=(
'(* --download)--clear-cache[clear remote Taskfile cache]'
)
fi
_arguments -S $standard_args $operation_args
} }
# don't run the completion function when being source-ed or eval-ed # don't run the completion function when being source-ed or eval-ed

View File

@@ -5,12 +5,15 @@ import (
"cmp" "cmp"
"errors" "errors"
"fmt" "fmt"
"regexp"
"strings" "strings"
"github.com/fatih/color" "github.com/fatih/color"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
) )
var typeErrorRegex = regexp.MustCompile(`line \d+: (.*)`)
type ( type (
TaskfileDecodeError struct { TaskfileDecodeError struct {
Message string Message string
@@ -50,10 +53,10 @@ func (err *TaskfileDecodeError) Error() string {
if len(te.Errors) > 1 { if len(te.Errors) > 1 {
fmt.Fprintln(buf, color.RedString("errs:")) fmt.Fprintln(buf, color.RedString("errs:"))
for _, message := range te.Errors { for _, message := range te.Errors {
fmt.Fprintln(buf, color.RedString("- %s", message.Err.Error())) fmt.Fprintln(buf, color.RedString("- %s", extractTypeErrorMessage(message)))
} }
} else { } else {
fmt.Fprintln(buf, color.RedString("err: %s", te.Errors[0].Err.Error())) fmt.Fprintln(buf, color.RedString("err: %s", extractTypeErrorMessage(te.Errors[0])))
} }
} else { } else {
// Otherwise print the error message normally // Otherwise print the error message normally
@@ -125,3 +128,11 @@ func (err *TaskfileDecodeError) WithFileInfo(location string, snippet string) *T
err.Snippet = snippet err.Snippet = snippet
return err return err
} }
func extractTypeErrorMessage(message string) string {
matches := typeErrorRegex.FindStringSubmatch(message)
if len(matches) == 2 {
return matches[1]
}
return message
}

View File

@@ -54,10 +54,6 @@ func (err *TaskRunError) TaskExitCode() int {
return err.Code() return err.Code()
} }
func (err *TaskRunError) Unwrap() error {
return err.Err
}
// TaskInternalError when the user attempts to invoke a task that is internal. // TaskInternalError when the user attempts to invoke a task that is internal.
type TaskInternalError struct { type TaskInternalError struct {
TaskName string TaskName string
@@ -166,7 +162,7 @@ func (v MissingVar) String() string {
} }
func (err *TaskMissingRequiredVarsError) Error() string { func (err *TaskMissingRequiredVarsError) Error() string {
vars := make([]string, 0, len(err.MissingVars)) var vars []string
for _, v := range err.MissingVars { for _, v := range err.MissingVars {
vars = append(vars, v.String()) vars = append(vars, v.String())
} }

View File

@@ -11,18 +11,14 @@ import (
// TaskfileNotFoundError is returned when no appropriate Taskfile is found when // TaskfileNotFoundError is returned when no appropriate Taskfile is found when
// searching the filesystem. // searching the filesystem.
type TaskfileNotFoundError struct { type TaskfileNotFoundError struct {
URI string URI string
Walk bool Walk bool
AskInit bool
} }
func (err TaskfileNotFoundError) Error() string { func (err TaskfileNotFoundError) Error() string {
var walkText string var walkText string
if err.Walk { if err.Walk {
walkText = " (or any of the parent directories)." walkText = " (or any of the parent directories)"
}
if err.AskInit {
walkText += " Run `task --init` to create a new Taskfile."
} }
return fmt.Sprintf(`task: No Taskfile found at %q%s`, err.URI, walkText) return fmt.Sprintf(`task: No Taskfile found at %q%s`, err.URI, walkText)
} }

View File

@@ -7,7 +7,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/puzpuzpuz/xsync/v4" "github.com/puzpuzpuz/xsync/v3"
"github.com/sajari/fuzzy" "github.com/sajari/fuzzy"
"github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/logger"
@@ -34,14 +34,11 @@ type (
Insecure bool Insecure bool
Download bool Download bool
Offline bool Offline bool
TrustedHosts []string
Timeout time.Duration Timeout time.Duration
CacheExpiryDuration time.Duration CacheExpiryDuration time.Duration
RemoteCacheDir string
Watch bool Watch bool
Verbose bool Verbose bool
Silent bool Silent bool
DisableFuzzy bool
AssumeYes bool AssumeYes bool
AssumeTerm bool // Used for testing AssumeTerm bool // Used for testing
Dry bool Dry bool
@@ -50,7 +47,6 @@ type (
Color bool Color bool
Concurrency int Concurrency int
Interval time.Duration Interval time.Duration
Failfast bool
// I/O // I/O
Stdin io.Reader Stdin io.Reader
@@ -67,15 +63,14 @@ type (
UserWorkingDir string UserWorkingDir string
EnableVersionCheck bool EnableVersionCheck bool
fuzzyModel *fuzzy.Model fuzzyModel *fuzzy.Model
fuzzyModelOnce sync.Once
concurrencySemaphore chan struct{} concurrencySemaphore chan struct{}
taskCallCount map[string]*int32 taskCallCount map[string]*int32
mkdirMutexMap map[string]*sync.Mutex mkdirMutexMap map[string]*sync.Mutex
executionHashes map[string]context.Context executionHashes map[string]context.Context
executionHashesMutex sync.Mutex executionHashesMutex sync.Mutex
watchedDirs *xsync.Map[string, bool] watchedDirs *xsync.MapOf[string, bool]
} }
TempDir struct { TempDir struct {
Remote string Remote string
@@ -230,20 +225,6 @@ func (o *offlineOption) ApplyToExecutor(e *Executor) {
e.Offline = o.offline e.Offline = o.offline
} }
// WithTrustedHosts configures the [Executor] with a list of trusted hosts for remote
// Taskfiles. Hosts in this list will not prompt for user confirmation.
func WithTrustedHosts(trustedHosts []string) ExecutorOption {
return &trustedHostsOption{trustedHosts}
}
type trustedHostsOption struct {
trustedHosts []string
}
func (o *trustedHostsOption) ApplyToExecutor(e *Executor) {
e.TrustedHosts = o.trustedHosts
}
// WithTimeout sets the [Executor]'s timeout for fetching remote taskfiles. By // WithTimeout sets the [Executor]'s timeout for fetching remote taskfiles. By
// default, the timeout is set to 10 seconds. // default, the timeout is set to 10 seconds.
func WithTimeout(timeout time.Duration) ExecutorOption { func WithTimeout(timeout time.Duration) ExecutorOption {
@@ -259,7 +240,7 @@ func (o *timeoutOption) ApplyToExecutor(e *Executor) {
} }
// WithCacheExpiryDuration sets the duration after which the cache is considered // WithCacheExpiryDuration sets the duration after which the cache is considered
// expired. By default, the cache is 0 (disabled). // expired. By default, the cache is considered expired after 24 hours.
func WithCacheExpiryDuration(duration time.Duration) ExecutorOption { func WithCacheExpiryDuration(duration time.Duration) ExecutorOption {
return &cacheExpiryDurationOption{duration: duration} return &cacheExpiryDurationOption{duration: duration}
} }
@@ -272,19 +253,6 @@ func (o *cacheExpiryDurationOption) ApplyToExecutor(r *Executor) {
r.CacheExpiryDuration = o.duration r.CacheExpiryDuration = o.duration
} }
// WithRemoteCacheDir sets the directory where remote taskfiles are cached.
func WithRemoteCacheDir(dir string) ExecutorOption {
return &remoteCacheDirOption{dir: dir}
}
type remoteCacheDirOption struct {
dir string
}
func (o *remoteCacheDirOption) ApplyToExecutor(e *Executor) {
e.RemoteCacheDir = o.dir
}
// WithWatch tells the [Executor] to keep running in the background and watch // WithWatch tells the [Executor] to keep running in the background and watch
// for changes to the fingerprint of the tasks that are run. When changes are // for changes to the fingerprint of the tasks that are run. When changes are
// detected, a new task run is triggered. // detected, a new task run is triggered.
@@ -328,19 +296,6 @@ func (o *silentOption) ApplyToExecutor(e *Executor) {
e.Silent = o.silent e.Silent = o.silent
} }
// WithDisableFuzzy tells the [Executor] to disable fuzzy matching for task names.
func WithDisableFuzzy(disableFuzzy bool) ExecutorOption {
return &disableFuzzyOption{disableFuzzy}
}
type disableFuzzyOption struct {
disableFuzzy bool
}
func (o *disableFuzzyOption) ApplyToExecutor(e *Executor) {
e.DisableFuzzy = o.disableFuzzy
}
// WithAssumeYes tells the [Executor] to assume "yes" for all prompts. // WithAssumeYes tells the [Executor] to assume "yes" for all prompts.
func WithAssumeYes(assumeYes bool) ExecutorOption { func WithAssumeYes(assumeYes bool) ExecutorOption {
return &assumeYesOption{assumeYes} return &assumeYesOption{assumeYes}
@@ -547,16 +502,3 @@ type versionCheckOption struct {
func (o *versionCheckOption) ApplyToExecutor(e *Executor) { func (o *versionCheckOption) ApplyToExecutor(e *Executor) {
e.EnableVersionCheck = o.enableVersionCheck e.EnableVersionCheck = o.enableVersionCheck
} }
// WithFailfast tells the [Executor] whether or not to check the version of
func WithFailfast(failfast bool) ExecutorOption {
return &failfastOption{failfast}
}
type failfastOption struct {
failfast bool
}
func (o *failfastOption) ApplyToExecutor(e *Executor) {
e.Failfast = o.failfast
}

View File

@@ -3,6 +3,7 @@ package task_test
import ( import (
"bytes" "bytes"
"cmp" "cmp"
"context"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@@ -143,12 +144,12 @@ func (tt *ExecutorTest) run(t *testing.T) {
t.Helper() t.Helper()
f := func(t *testing.T) { f := func(t *testing.T) {
t.Helper() t.Helper()
var buffer SyncBuffer var buf bytes.Buffer
opts := append( opts := append(
tt.executorOpts, tt.executorOpts,
task.WithStdout(&buffer), task.WithStdout(&buf),
task.WithStderr(&buffer), task.WithStderr(&buf),
) )
// If the test has input, create a reader for it and add it to the // If the test has input, create a reader for it and add it to the
@@ -171,7 +172,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
if err := e.Setup(); tt.wantSetupError { if err := e.Setup(); tt.wantSetupError {
require.Error(t, err) require.Error(t, err)
tt.writeFixtureErrSetup(t, g, err) tt.writeFixtureErrSetup(t, g, err)
tt.writeFixtureBuffer(t, g, buffer.buf) tt.writeFixtureBuffer(t, g, buf)
return return
} else { } else {
require.NoError(t, err) require.NoError(t, err)
@@ -188,11 +189,11 @@ func (tt *ExecutorTest) run(t *testing.T) {
} }
// Run the task and check for errors // Run the task and check for errors
ctx := t.Context() ctx := context.Background()
if err := e.Run(ctx, call); tt.wantRunError { if err := e.Run(ctx, call); tt.wantRunError {
require.Error(t, err) require.Error(t, err)
tt.writeFixtureErrRun(t, g, err) tt.writeFixtureErrRun(t, g, err)
tt.writeFixtureBuffer(t, g, buffer.buf) tt.writeFixtureBuffer(t, g, buf)
return return
} else { } else {
require.NoError(t, err) require.NoError(t, err)
@@ -205,7 +206,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
} }
} }
tt.writeFixtureBuffer(t, g, buffer.buf) tt.writeFixtureBuffer(t, g, buf)
} }
// Run the test (with a name if it has one) // Run the test (with a name if it has one)
@@ -263,23 +264,6 @@ func TestVars(t *testing.T) {
task.WithSilent(true), task.WithSilent(true),
), ),
) )
NewExecutorTest(t,
WithName("cli-var-priority-default"),
WithExecutorOptions(
task.WithDir("testdata/vars"),
task.WithSilent(true),
),
WithTask("cli-var-priority"),
)
NewExecutorTest(t,
WithName("cli-var-priority-override"),
WithExecutorOptions(
task.WithDir("testdata/vars"),
task.WithSilent(true),
),
WithTask("cli-var-priority"),
WithVar("CLI_VAR", "from_cli"),
)
} }
func TestRequires(t *testing.T) { func TestRequires(t *testing.T) {
@@ -638,30 +622,6 @@ func TestAlias(t *testing.T) {
) )
} }
func TestSummaryWithVarsAndRequires(t *testing.T) {
t.Parallel()
// Test basic case from prompt.md - vars and requires
NewExecutorTest(t,
WithName("vars-and-requires"),
WithExecutorOptions(
task.WithDir("testdata/summary-vars-requires"),
task.WithSummary(true),
),
WithTask("mytask"),
)
// Test with shell variables
NewExecutorTest(t,
WithName("shell-vars"),
WithExecutorOptions(
task.WithDir("testdata/summary-vars-requires"),
task.WithSummary(true),
),
WithTask("with-sh-var"),
)
}
func TestLabel(t *testing.T) { func TestLabel(t *testing.T) {
t.Parallel() t.Parallel()
@@ -706,15 +666,6 @@ func TestLabel(t *testing.T) {
), ),
WithTask("foo"), WithTask("foo"),
) )
NewExecutorTest(t,
WithName("label in error"),
WithExecutorOptions(
task.WithDir("testdata/label_error"),
),
WithTask("foo"),
WithRunError(),
)
} }
func TestPromptInSummary(t *testing.T) { func TestPromptInSummary(t *testing.T) {
@@ -1037,50 +988,3 @@ func TestIncludeChecksum(t *testing.T) {
WithFixtureTemplating(), WithFixtureTemplating(),
) )
} }
func TestFailfast(t *testing.T) {
t.Parallel()
t.Run("Default", func(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithName("default"),
WithExecutorOptions(
task.WithDir("testdata/failfast/default"),
task.WithSilent(true),
),
WithPostProcessFn(PPSortedLines),
WithRunError(),
)
})
t.Run("Option", func(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithName("default"),
WithExecutorOptions(
task.WithDir("testdata/failfast/default"),
task.WithSilent(true),
task.WithFailfast(true),
),
WithPostProcessFn(PPSortedLines),
WithRunError(),
)
})
t.Run("Task", func(t *testing.T) {
t.Parallel()
NewExecutorTest(t,
WithName("task"),
WithExecutorOptions(
task.WithDir("testdata/failfast/task"),
task.WithSilent(true),
),
WithPostProcessFn(PPSortedLines),
WithRunError(),
)
})
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/go-task/task/v3/taskrc" "github.com/go-task/task/v3/taskrc"
"github.com/go-task/task/v3/taskrc/ast"
) )
const envPrefix = "TASK_X_" const envPrefix = "TASK_X_"
@@ -32,13 +31,16 @@ var (
var xList []Experiment var xList []Experiment
func Parse(dir string) { func Parse(dir string) {
config, _ := taskrc.GetConfig(dir)
ParseWithConfig(dir, config)
}
func ParseWithConfig(dir string, config *ast.TaskRC) {
// Read any .env files // Read any .env files
readDotEnv(dir) readDotEnv(dir)
// Create a node for the Task config reader
node, _ := taskrc.NewNode("", dir)
// Read the Task config file
reader := taskrc.NewReader()
config, _ := reader.Read(node)
// Initialize the experiments // Initialize the experiments
GentleForce = New("GENTLE_FORCE", config, 1) GentleForce = New("GENTLE_FORCE", config, 1)
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1) RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)

90
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/go-task/task/v3 module github.com/go-task/task/v3
go 1.24.0 go 1.23.0
require ( require (
github.com/Ladicle/tabwriter v1.0.0 github.com/Ladicle/tabwriter v1.0.0
@@ -12,82 +12,50 @@ require (
github.com/elliotchance/orderedmap/v3 v3.1.0 github.com/elliotchance/orderedmap/v3 v3.1.0
github.com/fatih/color v1.18.0 github.com/fatih/color v1.18.0
github.com/fsnotify/fsnotify v1.9.0 github.com/fsnotify/fsnotify v1.9.0
github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.16.2
github.com/go-task/slim-sprig/v3 v3.0.0 github.com/go-task/slim-sprig/v3 v3.0.0
github.com/go-task/template v0.2.0 github.com/go-task/template v0.2.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/hashicorp/go-getter v1.8.3
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/puzpuzpuz/xsync/v4 v4.2.0 github.com/otiai10/copy v1.14.1
github.com/puzpuzpuz/xsync/v3 v3.5.1
github.com/sajari/fuzzy v1.0.0 github.com/sajari/fuzzy v1.0.0
github.com/sebdah/goldie/v2 v2.8.0 github.com/sebdah/goldie/v2 v2.7.1
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.7
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.10.0
github.com/zeebo/xxh3 v1.0.2 github.com/zeebo/xxh3 v1.0.2
go.yaml.in/yaml/v4 v4.0.0-rc.3 golang.org/x/sync v0.16.0
golang.org/x/sync v0.19.0 golang.org/x/term v0.33.0
golang.org/x/term v0.38.0 gopkg.in/yaml.v3 v3.0.1
mvdan.cc/sh/moreinterp v0.0.0-20251109230715-65adef8e2c5b
mvdan.cc/sh/v3 v3.12.0 mvdan.cc/sh/v3 v3.12.0
) )
require ( require (
cloud.google.com/go v0.110.0 // indirect dario.cat/mergo v1.0.0 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
cloud.google.com/go/iam v0.13.0 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect
cloud.google.com/go/storage v1.29.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.15 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.68 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.20 // indirect
github.com/aws/smithy-go v1.22.3 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/otiai10/mint v1.6.3 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/u-root/u-root v0.15.1-0.20251014130006-62f7144b33da // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect golang.org/x/crypto v0.37.0 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect golang.org/x/net v0.39.0 // indirect
go.opencensus.io v0.24.0 // indirect golang.org/x/sys v0.34.0 // indirect
golang.org/x/net v0.47.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.114.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

302
go.sum
View File

@@ -1,70 +1,34 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k=
cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI=
cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg= github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4= github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.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 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4=
github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2/config v1.29.15 h1:I5XjesVMpDZXZEZonVfjI12VNMrYa38LtLnw4NtY5Ss=
github.com/aws/aws-sdk-go-v2/config v1.29.15/go.mod h1:tNIp4JIPonlsgaO5hxO372a6gjhN63aSWl2GVl5QoBQ=
github.com/aws/aws-sdk-go-v2/credentials v1.17.68 h1:cFb9yjI02/sWHBSYXAtkamjzCuRymvmeFmt0TC0MbYY=
github.com/aws/aws-sdk-go-v2/credentials v1.17.68/go.mod h1:H6E+jBzyqUu8u0vGaU6POkK3P0NylYEeRZ6ynBpMqIk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2 h1:BCG7DCXEXpNCcpwCxg1oi9pkJWH2+eZzTn9MY56MbVw=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.1 h1:xYEAf/6QHiTZDccKnPMbsMwlau13GsDsTgdue3wmHGw=
github.com/aws/aws-sdk-go-v2/service/s3 v1.80.1/go.mod h1:qbn305Je/IofWBJ4bJz/Q7pDEtnnoInw/dGt71v6rHE=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.20 h1:oIaQ1e17CSKaWmUTu62MtraRWVIosn/iONMuZt0gbqc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.20/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ= 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/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -72,76 +36,48 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg= github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo= github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 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/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= 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/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
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.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviBE= github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviBE=
github.com/go-task/template v0.2.0/go.mod h1:dbdoUb6qKnHQi1y6o+IdIrs0J4o/SEhSTA6bbzZmdtc= github.com/go-task/template v0.2.0/go.mod h1:dbdoUb6qKnHQi1y6o+IdIrs0J4o/SEhSTA6bbzZmdtc=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 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/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A=
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 h1:81+kWbE1yErFBMjME0I5k3x3kojjKsWtPYHEAutoPow=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65/go.mod h1:WtMzv9T++tfWVea+qB2MXoaqxw33S8bpJslzUike2mQ=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-getter v1.8.3 h1:gIS+oTNv3kyYAvlUVgMR46MiG0bM0KuSON/KZEvRoRg=
github.com/hashicorp/go-getter v1.8.3/go.mod h1:CUTt9x2bCtJ/sV8ihgrITL3IUE+0BE1j/e4n5P/GIM4=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 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/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -149,145 +85,93 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0= github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY= github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo= github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc= github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/u-root/u-root v0.15.1-0.20251014130006-62f7144b33da h1:Vst9Tvq3G6f6pYBvxy7coi2arDsnOZ3Mkj8MkNarSK8=
github.com/u-root/u-root v0.15.1-0.20251014130006-62f7144b33da/go.mod h1:R49zft13memK20EgFAvmTbXBS0t29UvglnM0BCA1ldQ=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
mvdan.cc/sh/moreinterp v0.0.0-20251109230715-65adef8e2c5b h1:vTpx76nZDTP/BAGnnhEXYjM+8nPKe9+I86qCErBvjCw=
mvdan.cc/sh/moreinterp v0.0.0-20251109230715-65adef8e2c5b/go.mod h1:bDyKbUYKqkFunWmxxuSPrkYpln9QZcUsqu7W128qYW4=
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI= mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg= mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=

65
help.go
View File

@@ -24,17 +24,15 @@ type ListOptions struct {
ListAllTasks bool ListAllTasks bool
FormatTaskListAsJSON bool FormatTaskListAsJSON bool
NoStatus bool NoStatus bool
Nested bool
} }
// NewListOptions creates a new ListOptions instance // NewListOptions creates a new ListOptions instance
func NewListOptions(list, listAll, listAsJson, noStatus, nested bool) ListOptions { func NewListOptions(list, listAll, listAsJson, noStatus bool) ListOptions {
return ListOptions{ return ListOptions{
ListOnlyTasksWithDescriptions: list, ListOnlyTasksWithDescriptions: list,
ListAllTasks: listAll, ListAllTasks: listAll,
FormatTaskListAsJSON: listAsJson, FormatTaskListAsJSON: listAsJson,
NoStatus: noStatus, NoStatus: noStatus,
Nested: nested,
} }
} }
@@ -65,7 +63,7 @@ func (e *Executor) ListTasks(o ListOptions) (bool, error) {
return false, err return false, err
} }
if o.FormatTaskListAsJSON { if o.FormatTaskListAsJSON {
output, err := e.ToEditorOutput(tasks, o.NoStatus, o.Nested) output, err := e.ToEditorOutput(tasks, o.NoStatus)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -137,17 +135,33 @@ func (e *Executor) ListTaskNames(allTasks bool) error {
return nil return nil
} }
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool) (*editors.Namespace, error) { func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Taskfile, error) {
o := &editors.Taskfile{
Tasks: make([]editors.Task, len(tasks)),
Location: e.Taskfile.Location,
}
var g errgroup.Group var g errgroup.Group
editorTasks := make([]editors.Task, len(tasks))
// Look over each task in parallel and turn it into an editor task
for i := range tasks { for i := range tasks {
aliases := []string{}
if len(tasks[i].Aliases) > 0 {
aliases = tasks[i].Aliases
}
g.Go(func() error { g.Go(func() error {
editorTask := editors.NewTask(tasks[i]) o.Tasks[i] = editors.Task{
Name: tasks[i].Name(),
Task: tasks[i].Task,
Desc: tasks[i].Desc,
Summary: tasks[i].Summary,
Aliases: aliases,
UpToDate: false,
Location: &editors.Location{
Line: tasks[i].Location.Line,
Column: tasks[i].Location.Column,
Taskfile: tasks[i].Location.Taskfile,
},
}
if noStatus { if noStatus {
editorTasks[i] = editorTask
return nil return nil
} }
@@ -166,35 +180,10 @@ func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool, nested bool)
return err return err
} }
editorTask.UpToDate = &upToDate o.Tasks[i].UpToDate = upToDate
editorTasks[i] = editorTask
return nil return nil
}) })
} }
if err := g.Wait(); err != nil { return o, g.Wait()
return nil, err
}
// Create the root namespace
var tasksLen int
if !nested {
tasksLen = len(editorTasks)
}
rootNamespace := &editors.Namespace{
Tasks: make([]editors.Task, tasksLen),
Location: e.Taskfile.Location,
}
// Recursively add namespaces to the root namespace or if nesting is
// disabled add them all to the root namespace
for i, task := range editorTasks {
taskNamespacePath := strings.Split(task.Task, ast.NamespaceSeparator)
if nested {
rootNamespace.AddNamespace(taskNamespacePath, task)
} else {
rootNamespace.Tasks[i] = task
}
}
return rootNamespace, g.Wait()
} }

25
init.go
View File

@@ -6,10 +6,9 @@ import (
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/taskfile"
) )
const defaultFilename = "Taskfile.yml" const defaultTaskFilename = "Taskfile.yml"
//go:embed taskfile/templates/default.yml //go:embed taskfile/templates/default.yml
var DefaultTaskfile string var DefaultTaskfile string
@@ -21,30 +20,22 @@ var DefaultTaskfile string
// //
// The final file path is always returned and may be different from the input path. // The final file path is always returned and may be different from the input path.
func InitTaskfile(path string) (string, error) { func InitTaskfile(path string) (string, error) {
info, err := os.Stat(path) fi, err := os.Stat(path)
if err == nil && !info.IsDir() { if err == nil && !fi.IsDir() {
return path, errors.TaskfileAlreadyExistsError{} return path, errors.TaskfileAlreadyExistsError{}
} }
if info != nil && info.IsDir() { if fi != nil && fi.IsDir() {
// path was a directory, check if there is a Taskfile already path = filepathext.SmartJoin(path, defaultTaskFilename)
if hasDefaultTaskfile(path) { // path was a directory, so check if Taskfile.yml exists in it
if _, err := os.Stat(path); err == nil {
return path, errors.TaskfileAlreadyExistsError{} return path, errors.TaskfileAlreadyExistsError{}
} }
path = filepathext.SmartJoin(path, defaultFilename)
} }
if err := os.WriteFile(path, []byte(DefaultTaskfile), 0o644); err != nil { if err := os.WriteFile(path, []byte(DefaultTaskfile), 0o644); err != nil {
return path, err return path, err
} }
return path, nil return path, nil
} }
func hasDefaultTaskfile(dir string) bool {
for _, name := range taskfile.DefaultTaskfiles {
if _, err := os.Stat(filepathext.SmartJoin(dir, name)); err == nil {
return true
}
}
return false
}

View File

@@ -1,15 +1,10 @@
package editors package editors
import (
"github.com/go-task/task/v3/taskfile/ast"
)
type ( type (
// Namespace wraps task list output for use in editor integrations (e.g. VSCode, etc) // Taskfile wraps task list output for use in editor integrations (e.g. VSCode, etc)
Namespace struct { Taskfile struct {
Tasks []Task `json:"tasks"` Tasks []Task `json:"tasks"`
Namespaces map[string]*Namespace `json:"namespaces,omitempty"` Location string `json:"location"`
Location string `json:"location,omitempty"`
} }
// Task describes a single task // Task describes a single task
Task struct { Task struct {
@@ -18,7 +13,7 @@ type (
Desc string `json:"desc"` Desc string `json:"desc"`
Summary string `json:"summary"` Summary string `json:"summary"`
Aliases []string `json:"aliases"` Aliases []string `json:"aliases"`
UpToDate *bool `json:"up_to_date,omitempty"` UpToDate bool `json:"up_to_date"`
Location *Location `json:"location"` Location *Location `json:"location"`
} }
// Location describes a task's location in a taskfile // Location describes a task's location in a taskfile
@@ -28,59 +23,3 @@ type (
Taskfile string `json:"taskfile"` Taskfile string `json:"taskfile"`
} }
) )
func NewTask(task *ast.Task) Task {
aliases := []string{}
if len(task.Aliases) > 0 {
aliases = task.Aliases
}
return Task{
Name: task.Name(),
Task: task.Task,
Desc: task.Desc,
Summary: task.Summary,
Aliases: aliases,
Location: &Location{
Line: task.Location.Line,
Column: task.Location.Column,
Taskfile: task.Location.Taskfile,
},
}
}
func (parent *Namespace) AddNamespace(namespacePath []string, task Task) {
if len(namespacePath) == 0 {
return
}
// If there are no child namespaces, then we have found a task and we can
// simply add it to the current namespace
if len(namespacePath) == 1 {
parent.Tasks = append(parent.Tasks, task)
return
}
// Get the key of the current namespace in the path
namespaceKey := namespacePath[0]
// Add the namespace to the parent namespaces map using the namespace key
if parent.Namespaces == nil {
parent.Namespaces = make(map[string]*Namespace, 0)
}
// Search for the current namespace in the parent namespaces map
// If it doesn't exist, create it
namespace, ok := parent.Namespaces[namespaceKey]
if !ok {
namespace = &Namespace{}
parent.Namespaces[namespaceKey] = namespace
}
// Remove the current namespace key from the namespace path.
childNamespacePath := namespacePath[1:]
// If there are no child namespaces in the task name, then we have found the
// namespace of the task and we can add it to the current namespace.
// Otherwise, we need to go deeper
namespace.AddNamespace(childNamespacePath, task)
}

View File

@@ -1,20 +0,0 @@
package execext
import (
"runtime"
"strconv"
"github.com/go-task/task/v3/internal/env"
)
var useGoCoreUtils bool
func init() {
// If TASK_CORE_UTILS is set to either true or false, respect that.
// By default, enable on Windows only.
if v, err := strconv.ParseBool(env.GetTaskEnv("CORE_UTILS")); err == nil {
useGoCoreUtils = v
} else {
useGoCoreUtils = runtime.GOOS == "windows"
}
}

View File

@@ -7,8 +7,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"mvdan.cc/sh/moreinterp/coreutils"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
@@ -59,7 +59,7 @@ func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
r, err := interp.New( r, err := interp.New(
interp.Params(params...), interp.Params(params...),
interp.Env(expand.ListEnviron(environ...)), interp.Env(expand.ListEnviron(environ...)),
interp.ExecHandlers(execHandlers()...), interp.ExecHandlers(execHandler),
interp.OpenHandler(openHandler), interp.OpenHandler(openHandler),
interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr), interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
dirOption(opts.Dir), dirOption(opts.Dir),
@@ -143,11 +143,8 @@ func ExpandFields(s string) ([]string, error) {
return expand.Fields(cfg, words...) return expand.Fields(cfg, words...)
} }
func execHandlers() (handlers []func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc) { func execHandler(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
if useGoCoreUtils { return interp.DefaultExecHandler(15 * time.Second)
handlers = append(handlers, coreutils.ExecHandler)
}
return handlers
} }
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) { func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {

View File

@@ -1,6 +1,7 @@
package fingerprint package fingerprint
import ( import (
"context"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -163,7 +164,7 @@ func TestIsTaskUpToDate(t *testing.T) {
} }
result, err := IsTaskUpToDate( result, err := IsTaskUpToDate(
t.Context(), context.Background(),
tt.task, tt.task,
WithStatusChecker(mockStatusChecker), WithStatusChecker(mockStatusChecker),
WithSourcesChecker(mockSourcesChecker), WithSourcesChecker(mockSourcesChecker),

View File

@@ -8,7 +8,6 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/fatih/color"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/go-task/task/v3" "github.com/go-task/task/v3"
@@ -17,8 +16,6 @@ import (
"github.com/go-task/task/v3/internal/env" "github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/sort" "github.com/go-task/task/v3/internal/sort"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
"github.com/go-task/task/v3/taskrc"
taskrcast "github.com/go-task/task/v3/taskrc/ast"
) )
const usage = `Usage: task [flags...] [task...] const usage = `Usage: task [flags...] [task...]
@@ -54,14 +51,12 @@ var (
TaskSort string TaskSort string
Status bool Status bool
NoStatus bool NoStatus bool
Nested bool
Insecure bool Insecure bool
Force bool Force bool
ForceAll bool ForceAll bool
Watch bool Watch bool
Verbose bool Verbose bool
Silent bool Silent bool
DisableFuzzy bool
AssumeYes bool AssumeYes bool
Dry bool Dry bool
Summary bool Summary bool
@@ -73,16 +68,13 @@ var (
Output ast.Output Output ast.Output
Color bool Color bool
Interval time.Duration Interval time.Duration
Failfast bool
Global bool Global bool
Experiments bool Experiments bool
Download bool Download bool
Offline bool Offline bool
TrustedHosts []string
ClearCache bool ClearCache bool
Timeout time.Duration Timeout time.Duration
CacheExpiryDuration time.Duration CacheExpiryDuration time.Duration
RemoteCacheDir string
) )
func init() { func init() {
@@ -103,9 +95,7 @@ func init() {
// Parse the experiments // Parse the experiments
dir = cmp.Or(dir, filepath.Dir(entrypoint)) dir = cmp.Or(dir, filepath.Dir(entrypoint))
experiments.Parse(dir)
config, _ := taskrc.GetConfig(dir)
experiments.ParseWithConfig(dir, config)
// Parse the rest of the flags // Parse the rest of the flags
log.SetFlags(0) log.SetFlags(0)
@@ -114,7 +104,10 @@ func init() {
log.Print(usage) log.Print(usage)
pflag.PrintDefaults() pflag.PrintDefaults()
} }
offline, err := strconv.ParseBool(cmp.Or(env.GetTaskEnv("OFFLINE"), "false"))
if err != nil {
offline = false
}
pflag.BoolVar(&Version, "version", false, "Show Task version.") pflag.BoolVar(&Version, "version", false, "Show Task version.")
pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.") pflag.BoolVarP(&Help, "help", "h", false, "Shows Task usage.")
pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.") pflag.BoolVarP(&Init, "init", "i", false, "Creates a new Taskfile.yml in the current folder.")
@@ -125,12 +118,10 @@ func init() {
pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].") pflag.StringVar(&TaskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].")
pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.") pflag.BoolVar(&Status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.")
pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON") pflag.BoolVar(&NoStatus, "no-status", false, "Ignore status when listing tasks as JSON")
pflag.BoolVar(&Nested, "nested", false, "Nest namespaces when listing tasks as JSON") pflag.BoolVar(&Insecure, "insecure", false, "Forces Task to download Taskfiles over insecure connections.")
pflag.BoolVar(&Insecure, "insecure", getConfig(config, func() *bool { return config.Remote.Insecure }, false), "Forces Task to download Taskfiles over insecure connections.")
pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.") pflag.BoolVarP(&Watch, "watch", "w", false, "Enables watch of the given task.")
pflag.BoolVarP(&Verbose, "verbose", "v", getConfig(config, func() *bool { return config.Verbose }, false), "Enables verbose mode.") pflag.BoolVarP(&Verbose, "verbose", "v", false, "Enables verbose mode.")
pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.") pflag.BoolVarP(&Silent, "silent", "s", false, "Disables echoing.")
pflag.BoolVar(&DisableFuzzy, "disable-fuzzy", getConfig(config, func() *bool { return config.DisableFuzzy }, false), "Disables fuzzy matching for task names.")
pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.") pflag.BoolVarP(&AssumeYes, "yes", "y", false, "Assume \"yes\" as answer to all prompts.")
pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.") pflag.BoolVarP(&Parallel, "parallel", "p", false, "Executes tasks provided on command line in parallel.")
pflag.BoolVarP(&Dry, "dry", "n", false, "Compiles and prints tasks in the order that they would be run, without executing them.") pflag.BoolVarP(&Dry, "dry", "n", false, "Compiles and prints tasks in the order that they would be run, without executing them.")
@@ -142,10 +133,9 @@ func init() {
pflag.StringVar(&Output.Group.Begin, "output-group-begin", "", "Message template to print before a task's grouped output.") pflag.StringVar(&Output.Group.Begin, "output-group-begin", "", "Message template to print before a task's grouped output.")
pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.") pflag.StringVar(&Output.Group.End, "output-group-end", "", "Message template to print after a task's grouped output.")
pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.") pflag.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.")
pflag.BoolVarP(&Color, "color", "c", getConfig(config, func() *bool { return config.Color }, true), "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.") pflag.BoolVarP(&Color, "color", "c", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
pflag.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.") pflag.IntVarP(&Concurrency, "concurrency", "C", 0, "Limit number of tasks to run concurrently.")
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.") pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
pflag.BoolVarP(&Failfast, "failfast", "F", getConfig(config, func() *bool { return &config.Failfast }, false), "When running tasks in parallel, stop all tasks if one fails.")
pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.") pflag.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.") pflag.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
@@ -160,37 +150,13 @@ func init() {
// Remote Taskfiles experiment will adds the "download" and "offline" flags // Remote Taskfiles experiment will adds the "download" and "offline" flags
if experiments.RemoteTaskfiles.Enabled() { if experiments.RemoteTaskfiles.Enabled() {
pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.") pflag.BoolVar(&Download, "download", false, "Downloads a cached version of a remote Taskfile.")
pflag.BoolVar(&Offline, "offline", getConfig(config, func() *bool { return config.Remote.Offline }, false), "Forces Task to only use local or cached Taskfiles.") pflag.BoolVar(&Offline, "offline", offline, "Forces Task to only use local or cached Taskfiles.")
pflag.StringSliceVar(&TrustedHosts, "trusted-hosts", getConfig(config, func() *[]string { return &config.Remote.TrustedHosts }, nil), "List of trusted hosts for remote Taskfiles (comma-separated).") pflag.DurationVar(&Timeout, "timeout", time.Second*10, "Timeout for downloading remote Taskfiles.")
pflag.DurationVar(&Timeout, "timeout", getConfig(config, func() *time.Duration { return config.Remote.Timeout }, time.Second*10), "Timeout for downloading remote Taskfiles.")
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.") pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.") pflag.DurationVar(&CacheExpiryDuration, "expiry", 0, "Expiry duration for cached remote Taskfiles.")
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
} }
pflag.Parse() pflag.Parse()
// Auto-detect color based on environment when not explicitly configured
// Priority: CLI flag > taskrc config > NO_COLOR > FORCE_COLOR/CI > default
colorExplicitlySet := pflag.Lookup("color").Changed || (config != nil && config.Color != nil)
if !colorExplicitlySet {
if os.Getenv("NO_COLOR") != "" {
Color = false
color.NoColor = true
} else if os.Getenv("FORCE_COLOR") != "" || isCI() {
Color = true
color.NoColor = false // Force colors even without TTY
}
// Otherwise, let fatih/color auto-detect TTY
} else {
// Explicit config: sync with fatih/color
color.NoColor = !Color
}
}
// isCI returns true if running in a CI environment
func isCI() bool {
ci, _ := strconv.ParseBool(os.Getenv("CI"))
return ci
} }
func Validate() error { func Validate() error {
@@ -230,10 +196,6 @@ func Validate() error {
return errors.New("task: --no-status only applies to --json with --list or --list-all") return errors.New("task: --no-status only applies to --json with --list or --list-all")
} }
if Nested && !ListJson {
return errors.New("task: --nested only applies to --json with --list or --list-all")
}
return nil return nil
} }
@@ -272,14 +234,11 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithInsecure(Insecure), task.WithInsecure(Insecure),
task.WithDownload(Download), task.WithDownload(Download),
task.WithOffline(Offline), task.WithOffline(Offline),
task.WithTrustedHosts(TrustedHosts),
task.WithTimeout(Timeout), task.WithTimeout(Timeout),
task.WithCacheExpiryDuration(CacheExpiryDuration), task.WithCacheExpiryDuration(CacheExpiryDuration),
task.WithRemoteCacheDir(RemoteCacheDir),
task.WithWatch(Watch), task.WithWatch(Watch),
task.WithVerbose(Verbose), task.WithVerbose(Verbose),
task.WithSilent(Silent), task.WithSilent(Silent),
task.WithDisableFuzzy(DisableFuzzy),
task.WithAssumeYes(AssumeYes), task.WithAssumeYes(AssumeYes),
task.WithDry(Dry || Status), task.WithDry(Dry || Status),
task.WithSummary(Summary), task.WithSummary(Summary),
@@ -290,19 +249,5 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithOutputStyle(Output), task.WithOutputStyle(Output),
task.WithTaskSorter(sorter), task.WithTaskSorter(sorter),
task.WithVersionCheck(true), task.WithVersionCheck(true),
task.WithFailfast(Failfast),
) )
} }
// getConfig extracts a config value directly from a pointer field with a fallback default
func getConfig[T any](config *taskrcast.TaskRC, fieldFunc func() *T, fallback T) T {
if config == nil {
return fallback
}
field := fieldFunc()
if field != nil {
return *field
}
return fallback
}

View File

@@ -37,87 +37,51 @@ func DefaultDir(entrypoint, dir string) string {
return "" return ""
} }
// ResolveDir returns an absolute path to the directory that the task should be // Search will look for files with the given possible filenames using the given
// run in. If the entrypoint and dir are BOTH set, then the Taskfile will not // entrypoint and directory. If the entrypoint is set, it will check if the
// sit inside the directory specified by dir and we should ensure that the dir
// is absolute. Otherwise, the dir will always be the parent directory of the
// resolved entrypoint, so we should return that parent directory.
func ResolveDir(entrypoint, resolvedEntrypoint, dir string) (string, error) {
if entrypoint != "" && dir != "" {
return filepath.Abs(dir)
}
return filepath.Dir(resolvedEntrypoint), nil
}
// Search looks for files with the given possible filenames using the given
// entrypoint and directory. If the entrypoint is set, it checks if the
// entrypoint matches a file or if it matches a directory containing one of the // entrypoint matches a file or if it matches a directory containing one of the
// possible filenames. Otherwise, it walks up the file tree starting at the // possible filenames. Otherwise, it will walk up the file tree starting at the
// given directory and performs a search in each directory for the possible // given directory and perform a search in each directory for the possible
// filenames until it finds a match or reaches the root directory. If the // filenames until it finds a match or reaches the root directory. If the
// entrypoint and directory are both empty, it defaults the directory to the // entrypoint and directory are both empty, it will default the directory to the
// current working directory and performs a recursive search starting there. If // current working directory and perform a recursive search starting there. If a
// a match is found, the absolute path to the file is returned with its // match is found, the absolute path to the file will be returned with its
// directory. If no match is found, an error is returned. // directory. If no match is found, an error will be returned.
func Search(entrypoint, dir string, possibleFilenames []string) (string, error) { func Search(entrypoint, dir string, possibleFilenames []string) (string, string, error) {
var err error var err error
if entrypoint != "" { if entrypoint != "" {
entrypoint, err = SearchPath(entrypoint, possibleFilenames) entrypoint, err = SearchPath(entrypoint, possibleFilenames)
if err != nil { if err != nil {
return "", err return "", "", err
} }
return entrypoint, nil if dir == "" {
dir = filepath.Dir(entrypoint)
} else {
dir, err = filepath.Abs(dir)
if err != nil {
return "", "", err
}
}
return entrypoint, dir, nil
} }
if dir == "" { if dir == "" {
dir, err = os.Getwd() dir, err = os.Getwd()
if err != nil { if err != nil {
return "", err return "", "", err
} }
} }
entrypoint, err = SearchPathRecursively(dir, possibleFilenames) entrypoint, err = SearchPathRecursively(dir, possibleFilenames)
if err != nil { if err != nil {
return "", err return "", "", err
} }
return entrypoint, nil dir = filepath.Dir(entrypoint)
return entrypoint, dir, nil
} }
// SearchAll looks for files with the given possible filenames using the given // Search will check if a file at the given path exists or not. If it does, it
// entrypoint and directory. If the entrypoint is set, it checks if the // will return the path to it. If it does not, it will search for any files at
// entrypoint matches a file or if it matches a directory containing one of the // the given path with any of the given possible names. If any of these match a
// possible filenames and add it to a list of matches. It then walks up the file // file, the first matching path will be returned. If no files are found, an
// tree starting at the given directory and performs a search in each directory
// for the possible filenames until it finds a match or reaches the root
// directory. If the entrypoint and directory are both empty, it defaults the
// directory to the current working directory and performs a recursive search
// starting there. If matches are found, the absolute path to each file is added
// to the list and returned.
func SearchAll(entrypoint, dir string, possibleFilenames []string) ([]string, error) {
var err error
var entrypoints []string
if entrypoint != "" {
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
if err != nil {
return nil, err
}
entrypoints = append(entrypoints, entrypoint)
}
if dir == "" {
dir, err = os.Getwd()
if err != nil {
return nil, err
}
}
paths, err := SearchNPathRecursively(dir, possibleFilenames, -1)
if err != nil {
return nil, err
}
return append(entrypoints, paths...), nil
}
// SearchPath will check if a file at the given path exists or not. If it does,
// it will return the path to it. If it does not, it will search for any files
// at the given path with any of the given possible names. If any of these match
// a file, the first matching path will be returned. If no files are found, an
// error will be returned. // error will be returned.
func SearchPath(path string, possibleFilenames []string) (string, error) { func SearchPath(path string, possibleFilenames []string) (string, error) {
// Get file info about the path // Get file info about the path
@@ -147,56 +111,36 @@ func SearchPath(path string, possibleFilenames []string) (string, error) {
return "", os.ErrNotExist return "", os.ErrNotExist
} }
// SearchPathRecursively walks up the directory tree starting at the given // SearchRecursively will check if a file at the given path exists by calling
// path, calling the Search function in each directory until it finds a matching // the exists function. If a file is not found, it will walk up the directory
// file or reaches the root directory. On supported operating systems, it will // tree calling the Search function until it finds a file or reaches the root
// also check if the user ID of the directory changes and abort if it does. // directory. On supported operating systems, it will also check if the user ID
// of the directory changes and abort if it does.
func SearchPathRecursively(path string, possibleFilenames []string) (string, error) { func SearchPathRecursively(path string, possibleFilenames []string) (string, error) {
paths, err := SearchNPathRecursively(path, possibleFilenames, 1) owner, err := sysinfo.Owner(path)
if err != nil { if err != nil {
return "", err return "", err
} }
if len(paths) == 0 { for {
return "", os.ErrNotExist
}
return paths[0], nil
}
// SearchNPathRecursively walks up the directory tree starting at the given
// path, calling the Search function in each directory and adding each matching
// file that it finds to a list until it reaches the root directory or the
// length of the list exceeds n. On supported operating systems, it will also
// check if the user ID of the directory changes and abort if it does.
func SearchNPathRecursively(path string, possibleFilenames []string, n int) ([]string, error) {
var paths []string
owner, err := sysinfo.Owner(path)
if err != nil {
return nil, err
}
for n == -1 || len(paths) < n {
fpath, err := SearchPath(path, possibleFilenames) fpath, err := SearchPath(path, possibleFilenames)
if err == nil { if err == nil {
paths = append(paths, fpath) return fpath, nil
} }
// Get the parent path/user id // Get the parent path/user id
parentPath := filepath.Dir(path) parentPath := filepath.Dir(path)
parentOwner, err := sysinfo.Owner(parentPath) parentOwner, err := sysinfo.Owner(parentPath)
if err != nil { if err != nil {
return nil, err return "", err
} }
// Error if we reached the root directory and still haven't found a file // Error if we reached the root directory and still haven't found a file
// OR if the user id of the directory changes // OR if the user id of the directory changes
if path == parentPath || (parentOwner != owner) { if path == parentPath || (parentOwner != owner) {
return paths, nil return "", os.ErrNotExist
} }
owner = parentOwner owner = parentOwner
path = parentPath path = parentPath
} }
return paths, nil
} }

View File

@@ -71,30 +71,35 @@ func TestSearch(t *testing.T) {
dir string dir string
possibleFilenames []string possibleFilenames []string
expectedEntrypoint string expectedEntrypoint string
expectedDir string
}{ }{
{ {
name: "find foo.txt using relative entrypoint", name: "find foo.txt using relative entrypoint",
entrypoint: "./testdata/foo.txt", entrypoint: "./testdata/foo.txt",
possibleFilenames: []string{"foo.txt"}, possibleFilenames: []string{"foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
{ {
name: "find foo.txt using absolute entrypoint", name: "find foo.txt using absolute entrypoint",
entrypoint: filepath.Join(wd, "testdata", "foo.txt"), entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
possibleFilenames: []string{"foo.txt"}, possibleFilenames: []string{"foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
{ {
name: "find foo.txt using relative dir", name: "find foo.txt using relative dir",
dir: "./testdata", dir: "./testdata",
possibleFilenames: []string{"foo.txt"}, possibleFilenames: []string{"foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
{ {
name: "find foo.txt using absolute dir", name: "find foo.txt using absolute dir",
dir: filepath.Join(wd, "testdata"), dir: filepath.Join(wd, "testdata"),
possibleFilenames: []string{"foo.txt"}, possibleFilenames: []string{"foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
{ {
name: "find foo.txt using relative dir and relative entrypoint", name: "find foo.txt using relative dir and relative entrypoint",
@@ -102,6 +107,7 @@ func TestSearch(t *testing.T) {
dir: "./testdata/some/other/dir", dir: "./testdata/some/other/dir",
possibleFilenames: []string{"foo.txt"}, possibleFilenames: []string{"foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
}, },
{ {
name: "find fs.go using no entrypoint or dir", name: "find fs.go using no entrypoint or dir",
@@ -109,6 +115,7 @@ func TestSearch(t *testing.T) {
dir: "", dir: "",
possibleFilenames: []string{"fs.go"}, possibleFilenames: []string{"fs.go"},
expectedEntrypoint: filepath.Join(wd, "fs.go"), expectedEntrypoint: filepath.Join(wd, "fs.go"),
expectedDir: wd,
}, },
{ {
name: "find ../../Taskfile.yml using no entrypoint or dir by walking", name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
@@ -116,109 +123,30 @@ func TestSearch(t *testing.T) {
dir: "", dir: "",
possibleFilenames: []string{"Taskfile.yml"}, possibleFilenames: []string{"Taskfile.yml"},
expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"), expectedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
expectedDir: filepath.Join(wd, "..", ".."),
}, },
{ {
name: "find foo.txt first if listed first in possible filenames", name: "find foo.txt first if listed first in possible filenames",
entrypoint: "./testdata", entrypoint: "./testdata",
possibleFilenames: []string{"foo.txt", "bar.txt"}, possibleFilenames: []string{"foo.txt", "bar.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
{ {
name: "find bar.txt first if listed first in possible filenames", name: "find bar.txt first if listed first in possible filenames",
entrypoint: "./testdata", entrypoint: "./testdata",
possibleFilenames: []string{"bar.txt", "foo.txt"}, possibleFilenames: []string{"bar.txt", "foo.txt"},
expectedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"), expectedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
expectedDir: filepath.Join(wd, "testdata"),
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
entrypoint, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames) entrypoint, dir, err := Search(tt.entrypoint, tt.dir, tt.possibleFilenames)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, tt.expectedEntrypoint, entrypoint) require.Equal(t, tt.expectedEntrypoint, entrypoint)
require.NoError(t, err)
})
}
}
func TestResolveDir(t *testing.T) {
t.Parallel()
wd, err := os.Getwd()
require.NoError(t, err)
tests := []struct {
name string
entrypoint string
resolvedEntrypoint string
dir string
expectedDir string
}{
{
name: "find foo.txt using relative entrypoint",
entrypoint: "./testdata/foo.txt",
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
},
{
name: "find foo.txt using absolute entrypoint",
entrypoint: filepath.Join(wd, "testdata", "foo.txt"),
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
},
{
name: "find foo.txt using relative dir",
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
dir: "./testdata",
expectedDir: filepath.Join(wd, "testdata"),
},
{
name: "find foo.txt using absolute dir",
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
dir: filepath.Join(wd, "testdata"),
expectedDir: filepath.Join(wd, "testdata"),
},
{
name: "find foo.txt using relative dir and relative entrypoint",
entrypoint: "./testdata/foo.txt",
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
dir: "./testdata/some/other/dir",
expectedDir: filepath.Join(wd, "testdata", "some", "other", "dir"),
},
{
name: "find fs.go using no entrypoint or dir",
entrypoint: "",
resolvedEntrypoint: filepath.Join(wd, "fs.go"),
dir: "",
expectedDir: wd,
},
{
name: "find ../../Taskfile.yml using no entrypoint or dir by walking",
entrypoint: "",
resolvedEntrypoint: filepath.Join(wd, "..", "..", "Taskfile.yml"),
dir: "",
expectedDir: filepath.Join(wd, "..", ".."),
},
{
name: "find foo.txt first if listed first in possible filenames",
entrypoint: "./testdata",
resolvedEntrypoint: filepath.Join(wd, "testdata", "foo.txt"),
expectedDir: filepath.Join(wd, "testdata"),
},
{
name: "find bar.txt first if listed first in possible filenames",
entrypoint: "./testdata",
resolvedEntrypoint: filepath.Join(wd, "testdata", "bar.txt"),
expectedDir: filepath.Join(wd, "testdata"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
dir, err := ResolveDir(tt.entrypoint, tt.resolvedEntrypoint, tt.dir)
require.NoError(t, err)
require.Equal(t, tt.expectedDir, dir) require.Equal(t, tt.expectedDir, dir)
require.NoError(t, err)
}) })
} }
} }

View File

@@ -20,5 +20,5 @@ func Name(t *ast.Task) (string, error) {
func Hash(t *ast.Task) (string, error) { func Hash(t *ast.Task) (string, error) {
h, err := hashstructure.Hash(t, hashstructure.FormatV2, nil) h, err := hashstructure.Hash(t, hashstructure.FormatV2, nil)
return fmt.Sprintf("%s:%s:%d", t.Location.Taskfile, t.LocalName(), h), err return fmt.Sprintf("%s:%d", t.Task, h), err
} }

View File

@@ -3,6 +3,7 @@ package logger
import ( import (
"bufio" "bufio"
"io" "io"
"os"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
@@ -95,6 +96,10 @@ func BrightRed() PrintFunc {
} }
func envColor(name string, defaultColor color.Attribute) []color.Attribute { func envColor(name string, defaultColor color.Attribute) []color.Attribute {
if os.Getenv("FORCE_COLOR") != "" {
color.NoColor = false
}
// Fetch the environment variable // Fetch the environment variable
override := env.GetTaskEnv(name) override := env.GetTaskEnv(name)

View File

@@ -24,6 +24,7 @@ func (g Group) WrapWriter(stdOut, _ io.Writer, _ string, cache *templater.Cache)
if g.ErrorOnly && err == nil { if g.ErrorOnly && err == nil {
return nil return nil
} }
return gw.close() return gw.close()
} }
} }
@@ -39,22 +40,14 @@ func (gw *groupWriter) Write(p []byte) (int, error) {
} }
func (gw *groupWriter) close() error { func (gw *groupWriter) close() error {
switch { if gw.buff.Len() == 0 {
case gw.buff.Len() == 0: // don't print begin/end messages if there's no buffered entries
return nil return nil
case gw.begin == "" && gw.end == "": }
_, err := io.Copy(gw.writer, &gw.buff) if _, err := io.WriteString(gw.writer, gw.begin); err != nil {
return err
default:
_, err := io.Copy(gw.writer, gw.combinedBuff())
return err return err
} }
} gw.buff.WriteString(gw.end)
_, err := io.Copy(gw.writer, &gw.buff)
func (gw *groupWriter) combinedBuff() io.Reader { return err
var b bytes.Buffer
_, _ = b.WriteString(gw.begin)
_, _ = io.Copy(&b, &gw.buff)
_, _ = b.WriteString(gw.end)
return &b
} }

View File

@@ -1,8 +1,6 @@
package summary package summary
import ( import (
"fmt"
"os"
"strings" "strings"
"github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/logger"
@@ -31,9 +29,6 @@ func PrintSpaceBetweenSummaries(l *logger.Logger, i int) {
func PrintTask(l *logger.Logger, t *ast.Task) { func PrintTask(l *logger.Logger, t *ast.Task) {
printTaskName(l, t) printTaskName(l, t)
printTaskDescribingText(t, l) printTaskDescribingText(t, l)
printTaskVars(l, t)
printTaskEnv(l, t)
printTaskRequires(l, t)
printTaskDependencies(l, t) printTaskDependencies(l, t)
printTaskAliases(l, t) printTaskAliases(l, t)
printTaskCommands(l, t) printTaskCommands(l, t)
@@ -123,168 +118,3 @@ func printTaskCommands(l *logger.Logger, t *ast.Task) {
} }
} }
} }
func printTaskVars(l *logger.Logger, t *ast.Task) {
if t.Vars == nil || t.Vars.Len() == 0 {
return
}
osEnvVars := getEnvVarNames()
taskfileEnvVars := make(map[string]bool)
if t.Env != nil {
for key := range t.Env.All() {
taskfileEnvVars[key] = true
}
}
hasNonEnvVars := false
for key := range t.Vars.All() {
if !isEnvVar(key, osEnvVars) && !taskfileEnvVars[key] {
hasNonEnvVars = true
break
}
}
if !hasNonEnvVars {
return
}
l.Outf(logger.Default, "\n")
l.Outf(logger.Default, "vars:\n")
for key, value := range t.Vars.All() {
// Only display variables that are not from OS environment or Taskfile env
if !isEnvVar(key, osEnvVars) && !taskfileEnvVars[key] {
formattedValue := formatVarValue(value)
l.Outf(logger.Yellow, " %s: %s\n", key, formattedValue)
}
}
}
func printTaskEnv(l *logger.Logger, t *ast.Task) {
if t.Env == nil || t.Env.Len() == 0 {
return
}
envVars := getEnvVarNames()
hasNonEnvVars := false
for key := range t.Env.All() {
if !isEnvVar(key, envVars) {
hasNonEnvVars = true
break
}
}
if !hasNonEnvVars {
return
}
l.Outf(logger.Default, "\n")
l.Outf(logger.Default, "env:\n")
for key, value := range t.Env.All() {
// Only display variables that are not from OS environment
if !isEnvVar(key, envVars) {
formattedValue := formatVarValue(value)
l.Outf(logger.Yellow, " %s: %s\n", key, formattedValue)
}
}
}
// formatVarValue formats a variable value based on its type.
// Handles static values, shell commands (sh:), references (ref:), and maps.
func formatVarValue(v ast.Var) string {
// Shell command - check this first before Value
// because dynamic vars may have both Sh and an empty Value
if v.Sh != nil {
return fmt.Sprintf("sh: %s", *v.Sh)
}
// Reference
if v.Ref != "" {
return fmt.Sprintf("ref: %s", v.Ref)
}
// Static value
if v.Value != nil {
// Check if it's a map or complex type
if m, ok := v.Value.(map[string]any); ok {
return formatMap(m, 4)
}
// Simple string value
return fmt.Sprintf(`"%v"`, v.Value)
}
return `""`
}
// formatMap formats a map value with proper indentation for YAML.
func formatMap(m map[string]any, indent int) string {
if len(m) == 0 {
return "{}"
}
var result strings.Builder
result.WriteString("\n")
spaces := strings.Repeat(" ", indent)
for k, v := range m {
result.WriteString(fmt.Sprintf("%s%s: %v\n", spaces, k, v))
}
return result.String()
}
func printTaskRequires(l *logger.Logger, t *ast.Task) {
if t.Requires == nil || len(t.Requires.Vars) == 0 {
return
}
l.Outf(logger.Default, "\n")
l.Outf(logger.Default, "requires:\n")
l.Outf(logger.Default, " vars:\n")
for _, v := range t.Requires.Vars {
// If the variable has enum constraints, format accordingly
if len(v.Enum) > 0 {
l.Outf(logger.Yellow, " - %s:\n", v.Name)
l.Outf(logger.Yellow, " enum:\n")
for _, enumValue := range v.Enum {
l.Outf(logger.Yellow, " - %s\n", enumValue)
}
} else {
// Simple required variable
l.Outf(logger.Yellow, " - %s\n", v.Name)
}
}
}
func getEnvVarNames() map[string]bool {
envMap := make(map[string]bool)
for _, e := range os.Environ() {
parts := strings.SplitN(e, "=", 2)
if len(parts) > 0 {
envMap[parts[0]] = true
}
}
return envMap
}
// isEnvVar checks if a variable is from OS environment or auto-generated by Task.
func isEnvVar(key string, envVars map[string]bool) bool {
// Filter out auto-generated Task variables
if strings.HasPrefix(key, "TASK_") ||
strings.HasPrefix(key, "CLI_") ||
strings.HasPrefix(key, "ROOT_") ||
key == "TASK" ||
key == "TASKFILE" ||
key == "TASKFILE_DIR" ||
key == "USER_WORKING_DIR" ||
key == "ALIAS" ||
key == "MATCH" {
return true
}
return envVars[key]
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/google/uuid" "github.com/google/uuid"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"mvdan.cc/sh/v3/shell" "mvdan.cc/sh/v3/shell"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"

View File

@@ -4,6 +4,8 @@ import (
_ "embed" _ "embed"
"runtime/debug" "runtime/debug"
"strings" "strings"
"github.com/Masterminds/semver/v3"
) )
var ( var (
@@ -46,6 +48,10 @@ func getCommit(info *debug.BuildInfo) string {
// However, it can also be overridden at build time using: // However, it can also be overridden at build time using:
// -ldflags="-X 'github.com/go-task/task/v3/internal/version.version=vX.X.X'". // -ldflags="-X 'github.com/go-task/task/v3/internal/version.version=vX.X.X'".
func GetVersion() string { func GetVersion() string {
// If its a development version, we bump the minor version.
if commit != "" || dirty {
return semver.MustParse(version).IncMinor().String()
}
return version return version
} }
@@ -61,7 +67,7 @@ func GetVersionWithBuildInfo() string {
buildMetadata = append(buildMetadata, "dirty") buildMetadata = append(buildMetadata, "dirty")
} }
if len(buildMetadata) > 0 { if len(buildMetadata) > 0 {
return version + "+" + strings.Join(buildMetadata, ".") return GetVersion() + "+" + strings.Join(buildMetadata, ".")
} }
return version return GetVersion()
} }

View File

@@ -1 +1 @@
3.46.2 3.44.1

View File

@@ -0,0 +1,58 @@
package version
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestVersionTxt(t *testing.T) {
// Check that the version.txt is a valid semver version.
require.NotEmpty(t, GetVersion(), "version.txt is not semver compliant")
}
func TestGetVersion(t *testing.T) {
tests := []struct {
version string
commit string
dirty bool
want string
}{
{"1.2.3", "", false, "1.2.3"},
{"1.2.3", "", true, "1.3.0"},
{"1.2.3", "abcdefg", false, "1.3.0"},
{"1.2.3", "abcdefg", true, "1.3.0"},
}
for _, tt := range tests {
version = tt.version
commit = tt.commit
dirty = tt.dirty
t.Run(tt.want, func(t *testing.T) {
require.Equal(t, tt.want, GetVersion())
})
}
}
func TestGetVersionWithBuildInfo(t *testing.T) {
tests := []struct {
version string
commit string
dirty bool
want string
}{
{"1.2.3", "", false, "1.2.3"},
{"1.2.3", "", true, "1.3.0+dirty"},
{"1.2.3", "abcdefg", false, "1.3.0+abcdefg"},
{"1.2.3", "abcdefg", true, "1.3.0+abcdefg.dirty"},
}
for _, tt := range tests {
version = tt.version
commit = tt.commit
dirty = tt.dirty
t.Run(tt.want, func(t *testing.T) {
require.Equal(t, tt.want, GetVersionWithBuildInfo())
})
}
}

32
package-lock.json generated Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "@go-task/cli",
"version": "3.44.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@go-task/cli",
"version": "3.26.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@go-task/go-npm": "^0.2.0"
}
},
"node_modules/@go-task/go-npm": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.2.0.tgz",
"integrity": "sha512-vQbdtBvesHm8EUFHX8QKg4rbBodmu9VsAXH1ozpbiN5jdTMOYHTCMM31EurAYmY+rNNtxJQ4JGy6t383RPlqbw==",
"bin": {
"go-npm": "bin/index.js"
}
}
},
"dependencies": {
"@go-task/go-npm": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@go-task/go-npm/-/go-npm-0.2.0.tgz",
"integrity": "sha512-vQbdtBvesHm8EUFHX8QKg4rbBodmu9VsAXH1ozpbiN5jdTMOYHTCMM31EurAYmY+rNNtxJQ4JGy6t383RPlqbw=="
}
}
}

34
package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "@go-task/cli",
"version": "3.44.1",
"description": "A task runner / simpler Make alternative written in Go",
"scripts": {
"postinstall": "go-npm install",
"preuninstall": "go-npm uninstall"
},
"goBinary": {
"name": "task",
"path": "./bin",
"url": "https://github.com/go-task/task/releases/download/v{{version}}/task_{{platform}}_{{arch}}{{archive_ext}}"
},
"files": [],
"repository": {
"type": "git",
"url": "https://github.com/go-task/task.git"
},
"keywords": [
"task",
"taskfile",
"build-tool",
"task-runner"
],
"author": "The Task authors",
"license": "MIT",
"bugs": {
"url": "https://github.com/go-task/task/issues"
},
"homepage": "https://taskfile.dev",
"dependencies": {
"@go-task/go-npm": "^0.2.0"
}
}

View File

@@ -16,7 +16,6 @@ import (
"github.com/go-task/task/v3/internal/env" "github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/fsext"
"github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/output" "github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/version" "github.com/go-task/task/v3/internal/version"
@@ -36,6 +35,7 @@ func (e *Executor) Setup() error {
if err := e.readTaskfile(node); err != nil { if err := e.readTaskfile(node); err != nil {
return err return err
} }
e.setupFuzzyModel()
e.setupStdFiles() e.setupStdFiles()
if err := e.setupOutput(); err != nil { if err := e.setupOutput(); err != nil {
return err return err
@@ -56,13 +56,6 @@ func (e *Executor) Setup() error {
func (e *Executor) getRootNode() (taskfile.Node, error) { func (e *Executor) getRootNode() (taskfile.Node, error) {
node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout) node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout)
if os.IsNotExist(err) {
return nil, errors.TaskfileNotFoundError{
URI: fsext.DefaultDir(e.Entrypoint, e.Dir),
Walk: true,
AskInit: true,
}
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -83,7 +76,6 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
taskfile.WithInsecure(e.Insecure), taskfile.WithInsecure(e.Insecure),
taskfile.WithDownload(e.Download), taskfile.WithDownload(e.Download),
taskfile.WithOffline(e.Offline), taskfile.WithOffline(e.Offline),
taskfile.WithTrustedHosts(e.TrustedHosts),
taskfile.WithTempDir(e.TempDir.Remote), taskfile.WithTempDir(e.TempDir.Remote),
taskfile.WithCacheExpiryDuration(e.CacheExpiryDuration), taskfile.WithCacheExpiryDuration(e.CacheExpiryDuration),
taskfile.WithDebugFunc(debugFunc), taskfile.WithDebugFunc(debugFunc),
@@ -153,16 +145,16 @@ func (e *Executor) setupTempDir() error {
} }
} }
// RemoteCacheDir from taskrc/env can override the remote cache directory remoteDir := env.GetTaskEnv("REMOTE_DIR")
if e.RemoteCacheDir != "" { if remoteDir != "" {
if filepath.IsAbs(e.RemoteCacheDir) || strings.HasPrefix(e.RemoteCacheDir, "~") { if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") {
remoteCacheDir, err := execext.ExpandLiteral(e.RemoteCacheDir) remoteTempDir, err := execext.ExpandLiteral(remoteDir)
if err != nil { if err != nil {
return err return err
} }
e.TempDir.Remote = remoteCacheDir e.TempDir.Remote = remoteTempDir
} else { } else {
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, e.RemoteCacheDir) e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task")
} }
} }

59
task.go
View File

@@ -78,11 +78,9 @@ func (e *Executor) Run(ctx context.Context, calls ...*Call) error {
return err return err
} }
g := &errgroup.Group{} g, ctx := errgroup.WithContext(ctx)
if e.Failfast {
g, ctx = errgroup.WithContext(ctx)
}
for _, c := range regularCalls { for _, c := range regularCalls {
c := c
if e.Parallel { if e.Parallel {
g.Go(func() error { return e.RunTask(ctx, c) }) g.Go(func() error { return e.RunTask(ctx, c) })
} else { } else {
@@ -115,7 +113,7 @@ func (e *Executor) splitRegularAndWatchCalls(calls ...*Call) (regularCalls []*Ca
regularCalls = append(regularCalls, c) regularCalls = append(regularCalls, c)
} }
} }
return regularCalls, watchCalls, err return
} }
// RunTask runs a task by its name // RunTask runs a task by its name
@@ -152,7 +150,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
release := e.acquireConcurrencyLimit() release := e.acquireConcurrencyLimit()
defer release() defer release()
if err = e.startExecution(ctx, t, func(ctx context.Context) error { return e.startExecution(ctx, t, func(ctx context.Context) error {
e.Logger.VerboseErrf(logger.Magenta, "task: %q started\n", call.Task) e.Logger.VerboseErrf(logger.Magenta, "task: %q started\n", call.Task)
if err := e.runDeps(ctx, t); err != nil { if err := e.runDeps(ctx, t); err != nil {
return err return err
@@ -174,6 +172,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
if t.Method != "" { if t.Method != "" {
method = t.Method method = t.Method
} }
upToDate, err := fingerprint.IsTaskUpToDate(ctx, t, upToDate, err := fingerprint.IsTaskUpToDate(ctx, t,
fingerprint.WithMethod(method), fingerprint.WithMethod(method),
fingerprint.WithTempDir(e.TempDir.Fingerprint), fingerprint.WithTempDir(e.TempDir.Fingerprint),
@@ -212,7 +211,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
for i := range t.Cmds { for i := range t.Cmds {
if t.Cmds[i].Defer { if t.Cmds[i].Defer {
defer e.runDeferred(t, call, i, t.Vars, &deferredExitCode) defer e.runDeferred(t, call, i, &deferredExitCode)
continue continue
} }
@@ -230,16 +229,16 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
deferredExitCode = uint8(exitCode) deferredExitCode = uint8(exitCode)
} }
return err if call.Indirect {
return err
}
return &errors.TaskRunError{TaskName: t.Task, Err: err}
} }
} }
e.Logger.VerboseErrf(logger.Magenta, "task: %q finished\n", call.Task) e.Logger.VerboseErrf(logger.Magenta, "task: %q finished\n", call.Task)
return nil return nil
}); err != nil { })
return &errors.TaskRunError{TaskName: t.Name(), Err: err}
}
return nil
} }
func (e *Executor) mkdir(t *ast.Task) error { func (e *Executor) mkdir(t *ast.Task) error {
@@ -260,15 +259,13 @@ func (e *Executor) mkdir(t *ast.Task) error {
} }
func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error { func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
g := &errgroup.Group{} g, ctx := errgroup.WithContext(ctx)
if e.Failfast || t.Failfast {
g, ctx = errgroup.WithContext(ctx)
}
reacquire := e.releaseConcurrencyLimit() reacquire := e.releaseConcurrencyLimit()
defer reacquire() defer reacquire()
for _, d := range t.Deps { for _, d := range t.Deps {
d := d
g.Go(func() error { g.Go(func() error {
err := e.RunTask(ctx, &Call{Task: d.Task, Vars: d.Vars, Silent: d.Silent, Indirect: true}) err := e.RunTask(ctx, &Call{Task: d.Task, Vars: d.Vars, Silent: d.Silent, Indirect: true})
if err != nil { if err != nil {
@@ -281,11 +278,17 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
return g.Wait() return g.Wait()
} }
func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, deferredExitCode *uint8) { func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, deferredExitCode *uint8) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
origTask, err := e.GetTask(call)
if err != nil {
return
}
cmd := t.Cmds[i] cmd := t.Cmds[i]
vars, _ := e.Compiler.GetVariables(origTask, call)
cache := &templater.Cache{Vars: vars} cache := &templater.Cache{Vars: vars}
extra := map[string]any{} extra := map[string]any{}
@@ -311,12 +314,10 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
defer reacquire() defer reacquire()
err := e.RunTask(ctx, &Call{Task: cmd.Task, Vars: cmd.Vars, Silent: cmd.Silent, Indirect: true}) err := e.RunTask(ctx, &Call{Task: cmd.Task, Vars: cmd.Vars, Silent: cmd.Silent, Indirect: true})
var exitCode interp.ExitStatus if err != nil {
if errors.As(err, &exitCode) && cmd.IgnoreError { return err
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] task error ignored: %v\n", t.Name(), err)
return nil
} }
return err return nil
case cmd.Cmd != "": case cmd.Cmd != "":
if !shouldRunOnCurrentPlatform(cmd.Platforms) { if !shouldRunOnCurrentPlatform(cmd.Platforms) {
e.Logger.VerboseOutf(logger.Yellow, "task: [%s] %s not for current platform - ignored\n", t.Name(), cmd.Cmd) e.Logger.VerboseOutf(logger.Yellow, "task: [%s] %s not for current platform - ignored\n", t.Name(), cmd.Cmd)
@@ -372,7 +373,7 @@ func (e *Executor) startExecution(ctx context.Context, t *ast.Task, execute func
return err return err
} }
if h == "" || t.Watch { if h == "" {
return execute(ctx) return execute(ctx)
} }
@@ -458,17 +459,15 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
// If we found no tasks // If we found no tasks
if len(aliasedTasks) == 0 { if len(aliasedTasks) == 0 {
didYouMean := "" didYouMean := ""
if !e.DisableFuzzy { if e.fuzzyModel != nil {
e.fuzzyModelOnce.Do(e.setupFuzzyModel) didYouMean = e.fuzzyModel.SpellCheck(call.Task)
if e.fuzzyModel != nil {
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
}
} }
return nil, &errors.TaskNotFoundError{ return nil, &errors.TaskNotFoundError{
TaskName: call.Task, TaskName: call.Task,
DidYouMean: didYouMean, DidYouMean: didYouMean,
} }
} }
return matchingTask, nil return matchingTask, nil
} }
@@ -501,7 +500,7 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) {
// Compile the list of tasks // Compile the list of tasks
for i := range tasks { for i := range tasks {
g.Go(func() error { g.Go(func() error {
compiledTask, err := e.CompiledTaskForTaskList(&Call{Task: tasks[i].Task}) compiledTask, err := e.FastCompiledTask(&Call{Task: tasks[i].Task})
if err != nil { if err != nil {
return err return err
} }

View File

@@ -2,6 +2,7 @@ package task_test
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@@ -9,7 +10,6 @@ import (
rand "math/rand/v2" rand "math/rand/v2"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -356,7 +356,7 @@ func (fct fileContentTest) Run(t *testing.T) {
) )
require.NoError(t, e.Setup(), "e.Setup()") require.NoError(t, e.Setup(), "e.Setup()")
require.NoError(t, e.Run(t.Context(), &task.Call{Task: fct.Target}), "e.Run(target)") require.NoError(t, e.Run(context.Background(), &task.Call{Task: fct.Target}), "e.Run(target)")
for name, expectContent := range fct.Files { for name, expectContent := range fct.Files {
t.Run(fct.name(name), func(t *testing.T) { t.Run(fct.name(name), func(t *testing.T) {
path := filepathext.SmartJoin(e.Dir, name) path := filepathext.SmartJoin(e.Dir, name)
@@ -407,7 +407,7 @@ func TestGenerates(t *testing.T) {
fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask) fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
// Run task for the first time. // Run task for the first time.
require.NoError(t, e.Run(t.Context(), &task.Call{Task: theTask})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: theTask}))
if _, err := os.Stat(srcFile); err != nil { if _, err := os.Stat(srcFile); err != nil {
t.Errorf("File should exist: %v", err) t.Errorf("File should exist: %v", err)
@@ -422,7 +422,7 @@ func TestGenerates(t *testing.T) {
buff.Reset() buff.Reset()
// Re-run task to ensure it's now found to be up-to-date. // Re-run task to ensure it's now found to be up-to-date.
require.NoError(t, e.Run(t.Context(), &task.Call{Task: theTask})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: theTask}))
if buff.String() != upToDate { if buff.String() != upToDate {
t.Errorf("Wrong output message: %s", buff.String()) t.Errorf("Wrong output message: %s", buff.String())
} }
@@ -438,7 +438,6 @@ func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in
task string task string
}{ }{
{[]string{"generated.txt", ".task/checksum/build"}, "build"}, {[]string{"generated.txt", ".task/checksum/build"}, "build"},
{[]string{"generated-wildcard.txt", ".task/checksum/build-wildcard"}, "build-wildcard"},
{[]string{"generated.txt", ".task/checksum/build-with-status"}, "build-with-status"}, {[]string{"generated.txt", ".task/checksum/build-with-status"}, "build-with-status"},
} }
@@ -464,7 +463,7 @@ func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: test.task})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: test.task}))
for _, f := range test.files { for _, f := range test.files {
_, err := os.Stat(filepathext.SmartJoin(dir, f)) _, err := os.Stat(filepathext.SmartJoin(dir, f))
require.NoError(t, err) require.NoError(t, err)
@@ -477,7 +476,7 @@ func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in
time := s.ModTime() time := s.ModTime()
buff.Reset() buff.Reset()
require.NoError(t, e.Run(t.Context(), &task.Call{Task: test.task})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: test.task}))
assert.Equal(t, `task: Task "`+test.task+`" is up to date`+"\n", buff.String()) assert.Equal(t, `task: Task "`+test.task+`" is up to date`+"\n", buff.String())
s, err = os.Stat(filepathext.SmartJoin(tempDir.Fingerprint, "checksum/"+test.task)) s, err = os.Stat(filepathext.SmartJoin(tempDir.Fingerprint, "checksum/"+test.task))
@@ -508,12 +507,12 @@ func TestStatusVariables(t *testing.T) {
task.WithVerbose(true), task.WithVerbose(true),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build-checksum"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-checksum"}))
assert.Contains(t, buff.String(), "3e464c4b03f4b65d740e1e130d4d108a") assert.Contains(t, buff.String(), "3e464c4b03f4b65d740e1e130d4d108a")
buff.Reset() buff.Reset()
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build-ts"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-ts"}))
inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt")) inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt"))
require.NoError(t, err) require.NoError(t, err)
@@ -544,12 +543,12 @@ func TestCmdsVariables(t *testing.T) {
task.WithVerbose(true), task.WithVerbose(true),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build-checksum"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-checksum"}))
assert.Contains(t, buff.String(), "3e464c4b03f4b65d740e1e130d4d108a") assert.Contains(t, buff.String(), "3e464c4b03f4b65d740e1e130d4d108a")
buff.Reset() buff.Reset()
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build-ts"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-ts"}))
inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt")) inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt"))
require.NoError(t, err) require.NoError(t, err)
ts := fmt.Sprintf("%d", inf.ModTime().Unix()) ts := fmt.Sprintf("%d", inf.ModTime().Unix())
@@ -570,9 +569,7 @@ func TestCyclicDep(t *testing.T) {
task.WithStderr(io.Discard), task.WithStderr(io.Discard),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "task-1"}) assert.IsType(t, &errors.TaskCalledTooManyTimesError{}, e.Run(context.Background(), &task.Call{Task: "task-1"}))
var taskCalledTooManyTimesError *errors.TaskCalledTooManyTimesError
assert.ErrorAs(t, err, &taskCalledTooManyTimesError)
} }
func TestTaskVersion(t *testing.T) { func TestTaskVersion(t *testing.T) {
@@ -622,10 +619,10 @@ func TestTaskIgnoreErrors(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "task-should-pass"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task-should-pass"}))
require.Error(t, e.Run(t.Context(), &task.Call{Task: "task-should-fail"})) require.Error(t, e.Run(context.Background(), &task.Call{Task: "task-should-fail"}))
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "cmd-should-pass"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "cmd-should-pass"}))
require.Error(t, e.Run(t.Context(), &task.Call{Task: "cmd-should-fail"})) require.Error(t, e.Run(context.Background(), &task.Call{Task: "cmd-should-fail"}))
} }
func TestExpand(t *testing.T) { func TestExpand(t *testing.T) {
@@ -645,7 +642,7 @@ func TestExpand(t *testing.T) {
task.WithStderr(&buff), task.WithStderr(&buff),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "pwd"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "pwd"}))
assert.Equal(t, home, strings.TrimSpace(buff.String())) assert.Equal(t, home, strings.TrimSpace(buff.String()))
} }
@@ -666,7 +663,7 @@ func TestDry(t *testing.T) {
task.WithDry(true), task.WithDry(true),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build"}))
assert.Equal(t, "task: [build] touch file.txt", strings.TrimSpace(buff.String())) assert.Equal(t, "task: [build] touch file.txt", strings.TrimSpace(buff.String()))
if _, err := os.Stat(file); err == nil { if _, err := os.Stat(file); err == nil {
@@ -695,13 +692,13 @@ func TestDryChecksum(t *testing.T) {
task.WithDry(true), task.WithDry(true),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
_, err := os.Stat(checksumFile) _, err := os.Stat(checksumFile)
require.Error(t, err, "checksum file should not exist") require.Error(t, err, "checksum file should not exist")
e.Dry = false e.Dry = false
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
_, err = os.Stat(checksumFile) _, err = os.Stat(checksumFile)
require.NoError(t, err, "checksum file should exist") require.NoError(t, err, "checksum file should exist")
} }
@@ -787,11 +784,6 @@ func TestIncludesRemote(t *testing.T) {
var buff SyncBuffer var buff SyncBuffer
// Extract host from server URL for trust testing
parsedURL, err := url.Parse(srv.URL)
require.NoError(t, err)
trustedHost := parsedURL.Host
executors := []struct { executors := []struct {
name string name string
executor *task.Executor executor *task.Executor
@@ -831,23 +823,6 @@ func TestIncludesRemote(t *testing.T) {
task.WithOffline(true), task.WithOffline(true),
), ),
}, },
{
name: "with trusted hosts, no prompts",
executor: task.NewExecutor(
task.WithDir(dir),
task.WithStdout(&buff),
task.WithStderr(&buff),
task.WithTimeout(time.Minute),
task.WithInsecure(true),
task.WithStdout(&buff),
task.WithStderr(&buff),
task.WithVerbose(true),
// With trusted hosts
task.WithTrustedHosts([]string{trustedHost}),
task.WithDownload(true),
),
},
} }
for _, e := range executors { for _, e := range executors {
@@ -865,7 +840,7 @@ func TestIncludesRemote(t *testing.T) {
path := filepath.Join(dir, outputFile) path := filepath.Join(dir, outputFile)
require.NoError(t, os.RemoveAll(path)) require.NoError(t, os.RemoveAll(path))
require.NoError(t, e.executor.Run(t.Context(), taskCall)) require.NoError(t, e.executor.Run(context.Background(), taskCall))
actualContent, err := os.ReadFile(path) actualContent, err := os.ReadFile(path)
require.NoError(t, err) require.NoError(t, err)
@@ -1077,7 +1052,7 @@ func TestIncludesOptionalImplicitFalse(t *testing.T) {
const dir = "testdata/includes_optional_implicit_false" const dir = "testdata/includes_optional_implicit_false"
wd, _ := os.Getwd() wd, _ := os.Getwd()
message := "task: No Taskfile found at \"%s/%s/TaskfileOptional.yml\"" message := "stat %s/%s/TaskfileOptional.yml: no such file or directory"
expected := fmt.Sprintf(message, wd, dir) expected := fmt.Sprintf(message, wd, dir)
e := task.NewExecutor( e := task.NewExecutor(
@@ -1097,7 +1072,7 @@ func TestIncludesOptionalExplicitFalse(t *testing.T) {
const dir = "testdata/includes_optional_explicit_false" const dir = "testdata/includes_optional_explicit_false"
wd, _ := os.Getwd() wd, _ := os.Getwd()
message := "task: No Taskfile found at \"%s/%s/TaskfileOptional.yml\"" message := "stat %s/%s/TaskfileOptional.yml: no such file or directory"
expected := fmt.Sprintf(message, wd, dir) expected := fmt.Sprintf(message, wd, dir)
e := task.NewExecutor( e := task.NewExecutor(
@@ -1145,11 +1120,11 @@ func TestIncludesRelativePath(t *testing.T) {
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "common:pwd"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "common:pwd"}))
assert.Contains(t, buff.String(), "testdata/includes_rel_path/common") assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
buff.Reset() buff.Reset()
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "included:common:pwd"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "included:common:pwd"}))
assert.Contains(t, buff.String(), "testdata/includes_rel_path/common") assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
} }
@@ -1181,7 +1156,7 @@ func TestIncludesInternal(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task}) err := e.Run(context.Background(), &task.Call{Task: test.task})
if test.expectedErr { if test.expectedErr {
require.Error(t, err) require.Error(t, err)
} else { } else {
@@ -1228,7 +1203,7 @@ func TestIncludesFlatten(t *testing.T) {
assert.EqualError(t, err, test.expectedOutput) assert.EqualError(t, err, test.expectedOutput)
} else { } else {
require.NoError(t, err) require.NoError(t, err)
_ = e.Run(t.Context(), &task.Call{Task: test.task}) _ = e.Run(context.Background(), &task.Call{Task: test.task})
assert.Equal(t, test.expectedOutput, buff.String()) assert.Equal(t, test.expectedOutput, buff.String())
} }
}) })
@@ -1260,7 +1235,7 @@ func TestIncludesInterpolation(t *testing.T) { // nolint:paralleltest // cannot
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task}) err := e.Run(context.Background(), &task.Call{Task: test.task})
if test.expectedErr { if test.expectedErr {
require.Error(t, err) require.Error(t, err)
} else { } else {
@@ -1283,20 +1258,20 @@ func TestIncludesWithExclude(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "included:bar"}) err := e.Run(context.Background(), &task.Call{Task: "included:bar"})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "bar\n", buff.String()) assert.Equal(t, "bar\n", buff.String())
buff.Reset() buff.Reset()
err = e.Run(t.Context(), &task.Call{Task: "included:foo"}) err = e.Run(context.Background(), &task.Call{Task: "included:foo"})
require.Error(t, err) require.Error(t, err)
buff.Reset() buff.Reset()
err = e.Run(t.Context(), &task.Call{Task: "bar"}) err = e.Run(context.Background(), &task.Call{Task: "bar"})
require.Error(t, err) require.Error(t, err)
buff.Reset() buff.Reset()
err = e.Run(t.Context(), &task.Call{Task: "foo"}) err = e.Run(context.Background(), &task.Call{Task: "foo"})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "foo\n", buff.String()) assert.Equal(t, "foo\n", buff.String())
} }
@@ -1326,7 +1301,7 @@ func TestIncludedTaskfileVarMerging(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task}) err := e.Run(context.Background(), &task.Call{Task: test.task})
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, buff.String(), test.expectedOutput) assert.Contains(t, buff.String(), test.expectedOutput)
}) })
@@ -1361,7 +1336,7 @@ func TestInternalTask(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task}) err := e.Run(context.Background(), &task.Call{Task: test.task})
if test.expectedErr { if test.expectedErr {
require.Error(t, err) require.Error(t, err)
} else { } else {
@@ -1446,7 +1421,7 @@ func TestSummary(t *testing.T) {
task.WithSilent(true), task.WithSilent(true),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "task-with-summary"}, &task.Call{Task: "other-task-with-summary"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task-with-summary"}, &task.Call{Task: "other-task-with-summary"}))
data, err := os.ReadFile(filepathext.SmartJoin(dir, "task-with-summary.txt")) data, err := os.ReadFile(filepathext.SmartJoin(dir, "task-with-summary.txt"))
require.NoError(t, err) require.NoError(t, err)
@@ -1472,7 +1447,7 @@ func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "whereami"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "whereami"}))
// got should be the "dir" part of "testdata/dir" // got should be the "dir" part of "testdata/dir"
got := strings.TrimSuffix(filepath.Base(out.String()), "\n") got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
@@ -1492,7 +1467,7 @@ func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "whereami"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "whereami"}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n") got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory") assert.Equal(t, expected, got, "Mismatch in the working directory")
@@ -1518,7 +1493,7 @@ func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
t.Errorf("Directory should not exist: %v", err) t.Errorf("Directory should not exist: %v", err)
} }
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: target})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: target}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n") got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory") assert.Equal(t, expected, got, "Mismatch in the working directory")
@@ -1547,7 +1522,7 @@ func TestDynamicVariablesRunOnTheNewCreatedDir(t *testing.T) {
t.Errorf("Directory should not exist: %v", err) t.Errorf("Directory should not exist: %v", err)
} }
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: target})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: target}))
got := strings.TrimSuffix(filepath.Base(out.String()), "\n") got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
assert.Equal(t, expected, got, "Mismatch in the working directory") assert.Equal(t, expected, got, "Mismatch in the working directory")
@@ -1618,7 +1593,7 @@ func TestShortTaskNotation(t *testing.T) {
task.WithSilent(true), task.WithSilent(true),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String()) assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String())
} }
@@ -1816,7 +1791,7 @@ func TestExitImmediately(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.Error(t, e.Run(t.Context(), &task.Call{Task: "default"})) require.Error(t, e.Run(context.Background(), &task.Call{Task: "default"}))
assert.Contains(t, buff.String(), `"this_should_fail": executable file not found in $PATH`) assert.Contains(t, buff.String(), `"this_should_fail": executable file not found in $PATH`)
} }
@@ -1836,22 +1811,6 @@ func TestRunOnlyRunsJobsHashOnce(t *testing.T) {
}) })
} }
func TestRunOnlyRunsJobsHashOnceWithWildcard(t *testing.T) {
t.Parallel()
tt := fileContentTest{
Dir: "testdata/run",
Target: "deploy",
Files: map[string]string{
"wildcard.txt": "Deploy infra\nDeploy js\nDeploy go\n",
},
}
t.Run("", func(t *testing.T) {
t.Parallel()
tt.Run(t)
})
}
func TestRunOnceSharedDeps(t *testing.T) { func TestRunOnceSharedDeps(t *testing.T) {
t.Parallel() t.Parallel()
@@ -1865,7 +1824,7 @@ func TestRunOnceSharedDeps(t *testing.T) {
task.WithForceAll(true), task.WithForceAll(true),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build"}))
rx := regexp.MustCompile(`task: \[service-[a,b]:library:build\] echo "build library"`) rx := regexp.MustCompile(`task: \[service-[a,b]:library:build\] echo "build library"`)
matches := rx.FindAllStringSubmatch(buff.String(), -1) matches := rx.FindAllStringSubmatch(buff.String(), -1)
@@ -1874,29 +1833,6 @@ func TestRunOnceSharedDeps(t *testing.T) {
assert.Contains(t, buff.String(), `task: [service-b:build] echo "build b"`) assert.Contains(t, buff.String(), `task: [service-b:build] echo "build b"`)
} }
func TestRunWhenChanged(t *testing.T) {
t.Parallel()
const dir = "testdata/run_when_changed"
var buff bytes.Buffer
e := task.NewExecutor(
task.WithDir(dir),
task.WithStdout(&buff),
task.WithStderr(&buff),
task.WithForceAll(true),
task.WithSilent(true),
)
require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "start"}))
expectedOutputOrder := strings.TrimSpace(`
login server=fubar user=fubar
login server=foo user=foo
login server=bar user=bar
`)
assert.Contains(t, buff.String(), expectedOutputOrder)
}
func TestDeferredCmds(t *testing.T) { func TestDeferredCmds(t *testing.T) {
t.Parallel() t.Parallel()
@@ -1920,10 +1856,10 @@ task-1 ran successfully
task: [task-1] echo 'task-1 ran successfully' task: [task-1] echo 'task-1 ran successfully'
task-1 ran successfully task-1 ran successfully
`) `)
require.Error(t, e.Run(t.Context(), &task.Call{Task: "task-2"})) require.Error(t, e.Run(context.Background(), &task.Call{Task: "task-2"}))
assert.Contains(t, buff.String(), expectedOutputOrder) assert.Contains(t, buff.String(), expectedOutputOrder)
buff.Reset() buff.Reset()
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "parent"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "parent"}))
assert.Contains(t, buff.String(), "child task deferred value-from-parent") assert.Contains(t, buff.String(), "child task deferred value-from-parent")
} }
@@ -1939,7 +1875,7 @@ func TestExitCodeZero(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "exit-zero"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "exit-zero"}))
assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=", strings.TrimSpace(buff.String())) assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=", strings.TrimSpace(buff.String()))
} }
@@ -1955,7 +1891,7 @@ func TestExitCodeOne(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.Error(t, e.Run(t.Context(), &task.Call{Task: "exit-one"})) require.Error(t, e.Run(context.Background(), &task.Call{Task: "exit-one"}))
assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=1", strings.TrimSpace(buff.String())) assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=1", strings.TrimSpace(buff.String()))
} }
@@ -1984,7 +1920,7 @@ func TestIgnoreNilElements(t *testing.T) {
task.WithSilent(true), task.WithSilent(true),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
assert.Equal(t, "string-slice-1\n", buff.String()) assert.Equal(t, "string-slice-1\n", buff.String())
}) })
} }
@@ -2012,7 +1948,7 @@ task: [bye] echo 'Bye!'
Bye! Bye!
::endgroup:: ::endgroup::
`) `)
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "bye"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "bye"}))
t.Log(buff.String()) t.Log(buff.String())
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder) assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
} }
@@ -2029,7 +1965,7 @@ func TestOutputGroupErrorOnlySwallowsOutputOnSuccess(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "passing"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "passing"}))
t.Log(buff.String()) t.Log(buff.String())
assert.Empty(t, buff.String()) assert.Empty(t, buff.String())
} }
@@ -2046,7 +1982,7 @@ func TestOutputGroupErrorOnlyShowsOutputOnFailure(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.Error(t, e.Run(t.Context(), &task.Call{Task: "failing"})) require.Error(t, e.Run(context.Background(), &task.Call{Task: "failing"}))
t.Log(buff.String()) t.Log(buff.String())
assert.Contains(t, "failing-output", strings.TrimSpace(buff.String())) assert.Contains(t, "failing-output", strings.TrimSpace(buff.String()))
assert.NotContains(t, "passing", strings.TrimSpace(buff.String())) assert.NotContains(t, "passing", strings.TrimSpace(buff.String()))
@@ -2078,7 +2014,7 @@ VAR_1 is included-default-var1
task: [included3:task1] echo "VAR_2 is included-default-var2" task: [included3:task1] echo "VAR_2 is included-default-var2"
VAR_2 is included-default-var2 VAR_2 is included-default-var2
`) `)
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "task1"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task1"}))
t.Log(buff.String()) t.Log(buff.String())
assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder) assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
} }
@@ -2116,7 +2052,7 @@ Hello foo
task: [bar:lib:greet] echo 'Hello bar' task: [bar:lib:greet] echo 'Hello bar'
Hello bar Hello bar
`) `)
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
t.Log(buff.String()) t.Log(buff.String())
assert.Equal(t, expectedOutputOrder, strings.TrimSpace(buff.String())) assert.Equal(t, expectedOutputOrder, strings.TrimSpace(buff.String()))
} }
@@ -2154,7 +2090,7 @@ func TestErrorCode(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task}) err := e.Run(context.Background(), &task.Call{Task: test.task})
require.Error(t, err) require.Error(t, err)
taskRunErr, ok := err.(*errors.TaskRunError) taskRunErr, ok := err.(*errors.TaskRunError)
assert.True(t, ok, "cannot cast returned error to *task.TaskRunError") assert.True(t, ok, "cannot cast returned error to *task.TaskRunError")
@@ -2206,7 +2142,7 @@ func TestEvaluateSymlinksInPaths(t *testing.T) { // nolint:paralleltest // canno
for _, test := range tests { // nolint:paralleltest // cannot run in parallel for _, test := range tests { // nolint:paralleltest // cannot run in parallel
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: test.task}) err := e.Run(context.Background(), &task.Call{Task: test.task})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, test.expected, strings.TrimSpace(buff.String())) assert.Equal(t, test.expected, strings.TrimSpace(buff.String()))
buff.Reset() buff.Reset()
@@ -2249,7 +2185,7 @@ func TestTaskfileWalk(t *testing.T) {
task.WithStderr(&buff), task.WithStderr(&buff),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
assert.Equal(t, test.expected, buff.String()) assert.Equal(t, test.expected, buff.String())
}) })
} }
@@ -2267,7 +2203,7 @@ func TestUserWorkingDirectory(t *testing.T) {
wd, err := os.Getwd() wd, err := os.Getwd()
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "default"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String()) assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
} }
@@ -2289,7 +2225,7 @@ func TestUserWorkingDirectoryWithIncluded(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "included:echo"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "included:echo"}))
assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String()) assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
} }
@@ -2303,7 +2239,7 @@ func TestPlatforms(t *testing.T) {
task.WithStderr(&buff), task.WithStderr(&buff),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build-" + runtime.GOOS})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-" + runtime.GOOS}))
assert.Equal(t, fmt.Sprintf("task: [build-%s] echo 'Running task on %s'\nRunning task on %s\n", runtime.GOOS, runtime.GOOS, runtime.GOOS), buff.String()) assert.Equal(t, fmt.Sprintf("task: [build-%s] echo 'Running task on %s'\nRunning task on %s\n", runtime.GOOS, runtime.GOOS, runtime.GOOS), buff.String())
} }
@@ -2318,7 +2254,7 @@ func TestPOSIXShellOptsGlobalLevel(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "pipefail"}) err := e.Run(context.Background(), &task.Call{Task: "pipefail"})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "pipefail\ton\n", buff.String()) assert.Equal(t, "pipefail\ton\n", buff.String())
} }
@@ -2334,7 +2270,7 @@ func TestPOSIXShellOptsTaskLevel(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "pipefail"}) err := e.Run(context.Background(), &task.Call{Task: "pipefail"})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "pipefail\ton\n", buff.String()) assert.Equal(t, "pipefail\ton\n", buff.String())
} }
@@ -2350,7 +2286,7 @@ func TestPOSIXShellOptsCommandLevel(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "pipefail"}) err := e.Run(context.Background(), &task.Call{Task: "pipefail"})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "pipefail\ton\n", buff.String()) assert.Equal(t, "pipefail\ton\n", buff.String())
} }
@@ -2366,7 +2302,7 @@ func TestBashShellOptsGlobalLevel(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "globstar"}) err := e.Run(context.Background(), &task.Call{Task: "globstar"})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "globstar\ton\n", buff.String()) assert.Equal(t, "globstar\ton\n", buff.String())
} }
@@ -2382,7 +2318,7 @@ func TestBashShellOptsTaskLevel(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "globstar"}) err := e.Run(context.Background(), &task.Call{Task: "globstar"})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "globstar\ton\n", buff.String()) assert.Equal(t, "globstar\ton\n", buff.String())
} }
@@ -2398,7 +2334,7 @@ func TestBashShellOptsCommandLevel(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
err := e.Run(t.Context(), &task.Call{Task: "globstar"}) err := e.Run(context.Background(), &task.Call{Task: "globstar"})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "globstar\ton\n", buff.String()) assert.Equal(t, "globstar\ton\n", buff.String())
} }
@@ -2418,7 +2354,7 @@ func TestSplitArgs(t *testing.T) {
vars := ast.NewVars() vars := ast.NewVars()
vars.Set("CLI_ARGS", ast.Var{Value: "foo bar 'foo bar baz'"}) vars.Set("CLI_ARGS", ast.Var{Value: "foo bar 'foo bar baz'"})
err := e.Run(t.Context(), &task.Call{Task: "default", Vars: vars}) err := e.Run(context.Background(), &task.Call{Task: "default", Vars: vars})
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "3\n", buff.String()) assert.Equal(t, "3\n", buff.String())
} }
@@ -2459,14 +2395,14 @@ func TestSilence(t *testing.T) {
// Then test the two basic cases where the task is silent or not. // Then test the two basic cases where the task is silent or not.
// A silenced task. // A silenced task.
err = e.Run(t.Context(), &task.Call{Task: "silent"}) err = e.Run(context.Background(), &task.Call{Task: "silent"})
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, buff.String(), "siWhile running lent: Expected not see output, because the task is silent") require.Empty(t, buff.String(), "siWhile running lent: Expected not see output, because the task is silent")
buff.Reset() buff.Reset()
// A chatty (not silent) task. // A chatty (not silent) task.
err = e.Run(t.Context(), &task.Call{Task: "chatty"}) err = e.Run(context.Background(), &task.Call{Task: "chatty"})
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, buff.String(), "chWhile running atty: Expected to see output, because the task is not silent") require.NotEmpty(t, buff.String(), "chWhile running atty: Expected to see output, because the task is not silent")
@@ -2474,42 +2410,42 @@ func TestSilence(t *testing.T) {
// Then test invoking the two task from other tasks. // Then test invoking the two task from other tasks.
// A silenced task that calls a chatty task. // A silenced task that calls a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-silent-calls-chatty-non-silenced"}) err = e.Run(context.Background(), &task.Call{Task: "task-test-silent-calls-chatty-non-silenced"})
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-silent-calls-chatty-non-silenced: Expected to see output. The task is silenced, but the called task is not. Silence does not propagate to called tasks.") require.NotEmpty(t, buff.String(), "While running task-test-silent-calls-chatty-non-silenced: Expected to see output. The task is silenced, but the called task is not. Silence does not propagate to called tasks.")
buff.Reset() buff.Reset()
// A silent task that does a silent call to a chatty task. // A silent task that does a silent call to a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-silent-calls-chatty-silenced"}) err = e.Run(context.Background(), &task.Call{Task: "task-test-silent-calls-chatty-silenced"})
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-silent-calls-chatty-silenced: Expected not to see output. The task calls chatty task, but the call is silenced.") require.Empty(t, buff.String(), "While running task-test-silent-calls-chatty-silenced: Expected not to see output. The task calls chatty task, but the call is silenced.")
buff.Reset() buff.Reset()
// A chatty task that does a call to a chatty task. // A chatty task that does a call to a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-chatty-calls-chatty-non-silenced"}) err = e.Run(context.Background(), &task.Call{Task: "task-test-chatty-calls-chatty-non-silenced"})
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-chatty-calls-chatty-non-silenced: Expected to see output. Both caller and callee are chatty and not silenced.") require.NotEmpty(t, buff.String(), "While running task-test-chatty-calls-chatty-non-silenced: Expected to see output. Both caller and callee are chatty and not silenced.")
buff.Reset() buff.Reset()
// A chatty task that does a silenced call to a chatty task. // A chatty task that does a silenced call to a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-chatty-calls-chatty-silenced"}) err = e.Run(context.Background(), &task.Call{Task: "task-test-chatty-calls-chatty-silenced"})
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-chatty-calls-chatty-silenced: Expected to see output. Call to a chatty task is silenced, but the parent task is not.") require.NotEmpty(t, buff.String(), "While running task-test-chatty-calls-chatty-silenced: Expected to see output. Call to a chatty task is silenced, but the parent task is not.")
buff.Reset() buff.Reset()
// A chatty task with no cmd's of its own that does a silenced call to a chatty task. // A chatty task with no cmd's of its own that does a silenced call to a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-no-cmds-calls-chatty-silenced"}) err = e.Run(context.Background(), &task.Call{Task: "task-test-no-cmds-calls-chatty-silenced"})
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-no-cmds-calls-chatty-silenced: Expected not to see output. While the task itself is not silenced, it does not have any cmds and only does an invocation of a silenced task.") require.Empty(t, buff.String(), "While running task-test-no-cmds-calls-chatty-silenced: Expected not to see output. While the task itself is not silenced, it does not have any cmds and only does an invocation of a silenced task.")
buff.Reset() buff.Reset()
// A chatty task that does a silenced invocation of a task. // A chatty task that does a silenced invocation of a task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-chatty-calls-silenced-cmd"}) err = e.Run(context.Background(), &task.Call{Task: "task-test-chatty-calls-silenced-cmd"})
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-chatty-calls-silenced-cmd: Expected not to see output. While the task itself is not silenced, its call to the chatty task is silent.") require.Empty(t, buff.String(), "While running task-test-chatty-calls-silenced-cmd: Expected not to see output. While the task itself is not silenced, its call to the chatty task is silent.")
@@ -2517,21 +2453,21 @@ func TestSilence(t *testing.T) {
// Then test calls via dependencies. // Then test calls via dependencies.
// A silent task that depends on a chatty task. // A silent task that depends on a chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-is-silent-depends-on-chatty-non-silenced"}) err = e.Run(context.Background(), &task.Call{Task: "task-test-is-silent-depends-on-chatty-non-silenced"})
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, buff.String(), "While running task-test-is-silent-depends-on-chatty-non-silenced: Expected to see output. The task is silent and depends on a chatty task. Dependencies does not inherit silence.") require.NotEmpty(t, buff.String(), "While running task-test-is-silent-depends-on-chatty-non-silenced: Expected to see output. The task is silent and depends on a chatty task. Dependencies does not inherit silence.")
buff.Reset() buff.Reset()
// A silent task that depends on a silenced chatty task. // A silent task that depends on a silenced chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-is-silent-depends-on-chatty-silenced"}) err = e.Run(context.Background(), &task.Call{Task: "task-test-is-silent-depends-on-chatty-silenced"})
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-is-silent-depends-on-chatty-silenced: Expected not to see output. The task is silent and has a silenced dependency on a chatty task.") require.Empty(t, buff.String(), "While running task-test-is-silent-depends-on-chatty-silenced: Expected not to see output. The task is silent and has a silenced dependency on a chatty task.")
buff.Reset() buff.Reset()
// A chatty task that, depends on a silenced chatty task. // A chatty task that, depends on a silenced chatty task.
err = e.Run(t.Context(), &task.Call{Task: "task-test-is-chatty-depends-on-chatty-silenced"}) err = e.Run(context.Background(), &task.Call{Task: "task-test-is-chatty-depends-on-chatty-silenced"})
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, buff.String(), "While running task-test-is-chatty-depends-on-chatty-silenced: Expected not to see output. The task is chatty but does not have commands and has a silenced dependency on a chatty task.") require.Empty(t, buff.String(), "While running task-test-is-chatty-depends-on-chatty-silenced: Expected not to see output. The task is chatty but does not have commands and has a silenced dependency on a chatty task.")
@@ -2583,7 +2519,7 @@ func TestForce(t *testing.T) {
task.WithForceAll(tt.forceAll), task.WithForceAll(tt.forceAll),
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "task-with-dep"})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task-with-dep"}))
}) })
} }
} }
@@ -2643,10 +2579,10 @@ func TestWildcard(t *testing.T) {
) )
require.NoError(t, e.Setup()) require.NoError(t, e.Setup())
if test.wantErr { if test.wantErr {
require.Error(t, e.Run(t.Context(), &task.Call{Task: test.call})) require.Error(t, e.Run(context.Background(), &task.Call{Task: test.call}))
return return
} }
require.NoError(t, e.Run(t.Context(), &task.Call{Task: test.call})) require.NoError(t, e.Run(context.Background(), &task.Call{Task: test.call}))
assert.Equal(t, test.expectedOutput, buff.String()) assert.Equal(t, test.expectedOutput, buff.String())
}) })
} }

View File

@@ -1,7 +1,7 @@
package ast package ast
import ( import (
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy" "github.com/go-task/task/v3/internal/deepcopy"
@@ -93,7 +93,6 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
c.Vars = cmdStruct.Vars c.Vars = cmdStruct.Vars
c.For = cmdStruct.For c.For = cmdStruct.For
c.Silent = cmdStruct.Silent c.Silent = cmdStruct.Silent
c.IgnoreError = cmdStruct.IgnoreError
return nil return nil
} }

View File

@@ -1,7 +1,7 @@
package ast package ast
import ( import (
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
) )

View File

@@ -1,7 +1,7 @@
package ast package ast
import ( import (
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
) )

View File

@@ -1,7 +1,7 @@
package ast package ast
import ( import (
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy" "github.com/go-task/task/v3/internal/deepcopy"

View File

@@ -1,7 +1,7 @@
package ast package ast
import ( import (
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
) )

View File

@@ -5,7 +5,7 @@ import (
"sync" "sync"
"github.com/elliotchance/orderedmap/v3" "github.com/elliotchance/orderedmap/v3"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy" "github.com/go-task/task/v3/internal/deepcopy"

View File

@@ -4,7 +4,7 @@ import (
"iter" "iter"
"github.com/elliotchance/orderedmap/v3" "github.com/elliotchance/orderedmap/v3"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy" "github.com/go-task/task/v3/internal/deepcopy"

View File

@@ -1,7 +1,7 @@
package ast package ast
import ( import (
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
) )

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/goext" "github.com/go-task/task/v3/internal/goext"

View File

@@ -3,7 +3,7 @@ package ast
import ( import (
"fmt" "fmt"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
) )

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )

View File

@@ -1,7 +1,7 @@
package ast package ast
import ( import (
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
) )

View File

@@ -1,7 +1,7 @@
package ast package ast
import ( import (
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy" "github.com/go-task/task/v3/internal/deepcopy"

View File

@@ -5,7 +5,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy" "github.com/go-task/task/v3/internal/deepcopy"
@@ -13,7 +13,7 @@ import (
// Task represents a task // Task represents a task
type Task struct { type Task struct {
Task string `hash:"ignore"` Task string
Cmds []*Cmd Cmds []*Cmd
Deps []*Dep Deps []*Dep
Label string Label string
@@ -36,33 +36,27 @@ type Task struct {
Interactive bool Interactive bool
Internal bool Internal bool
Method string Method string
Prefix string `hash:"ignore"` Prefix string
IgnoreError bool IgnoreError bool
Run string Run string
Platforms []*Platform Platforms []*Platform
Watch bool Watch bool
Location *Location Location *Location
Failfast bool
// Populated during merging // Populated during merging
Namespace string `hash:"ignore"` Namespace string
IncludeVars *Vars IncludeVars *Vars
IncludedTaskfileVars *Vars IncludedTaskfileVars *Vars
FullName string `hash:"ignore"`
} }
func (t *Task) Name() string { func (t *Task) Name() string {
if t.Label != "" { if t.Label != "" {
return t.Label return t.Label
} }
if t.FullName != "" {
return t.FullName
}
return t.Task return t.Task
} }
func (t *Task) LocalName() string { func (t *Task) LocalName() string {
name := t.FullName name := t.Task
name = strings.TrimPrefix(name, t.Namespace) name = strings.TrimPrefix(name, t.Namespace)
name = strings.TrimPrefix(name, ":") name = strings.TrimPrefix(name, ":")
return name return name
@@ -144,7 +138,6 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
Platforms []*Platform Platforms []*Platform
Requires *Requires Requires *Requires
Watch bool Watch bool
Failfast bool
} }
if err := node.Decode(&task); err != nil { if err := node.Decode(&task); err != nil {
return errors.NewTaskfileDecodeError(err, node) return errors.NewTaskfileDecodeError(err, node)
@@ -183,7 +176,6 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
t.Platforms = task.Platforms t.Platforms = task.Platforms
t.Requires = task.Requires t.Requires = task.Requires
t.Watch = task.Watch t.Watch = task.Watch
t.Failfast = task.Failfast
return nil return nil
} }
@@ -228,8 +220,6 @@ func (t *Task) DeepCopy() *Task {
Location: t.Location.DeepCopy(), Location: t.Location.DeepCopy(),
Requires: t.Requires.DeepCopy(), Requires: t.Requires.DeepCopy(),
Namespace: t.Namespace, Namespace: t.Namespace,
FullName: t.FullName,
Failfast: t.Failfast,
} }
return c return c
} }

View File

@@ -5,7 +5,7 @@ import (
"time" "time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
) )

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )

View File

@@ -8,7 +8,7 @@ import (
"sync" "sync"
"github.com/elliotchance/orderedmap/v3" "github.com/elliotchance/orderedmap/v3"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
@@ -244,8 +244,8 @@ func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
} }
func taskNameWithNamespace(taskName string, namespace string) string { func taskNameWithNamespace(taskName string, namespace string) string {
if after, ok := strings.CutPrefix(taskName, NamespaceSeparator); ok { if strings.HasPrefix(taskName, NamespaceSeparator) {
return after return strings.TrimPrefix(taskName, NamespaceSeparator)
} }
return fmt.Sprintf("%s%s%s", namespace, NamespaceSeparator, taskName) return fmt.Sprintf("%s%s%s", namespace, NamespaceSeparator, taskName)
} }

View File

@@ -1,7 +1,7 @@
package ast package ast
import ( import (
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
) )
@@ -18,10 +18,7 @@ type Var struct {
func (v *Var) UnmarshalYAML(node *yaml.Node) error { func (v *Var) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind { switch node.Kind {
case yaml.MappingNode: case yaml.MappingNode:
key := "<none>" key := node.Content[0].Value
if len(node.Content) > 0 {
key = node.Content[0].Value
}
switch key { switch key {
case "sh", "ref", "map": case "sh", "ref", "map":
var m struct { var m struct {

View File

@@ -5,7 +5,7 @@ import (
"sync" "sync"
"github.com/elliotchance/orderedmap/v3" "github.com/elliotchance/orderedmap/v3"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/deepcopy" "github.com/go-task/task/v3/internal/deepcopy"
@@ -113,12 +113,12 @@ func (vars *Vars) ToCacheMap() (m map[string]any) {
m[k] = v.Value m[k] = v.Value
} }
} }
return m return
} }
// Merge loops over other and merges it values with the variables in vars. If // Merge loops over other and merges it values with the variables in vars. If
// the include parameter is not nil and its it is an advanced import, the // the include parameter is not nil and its it is an advanced import, the
// directory is set to the value of the include parameter. // directory is set set to the value of the include parameter.
func (vars *Vars) Merge(other *Vars, include *Include) { func (vars *Vars) Merge(other *Vars, include *Include) {
if vars == nil || vars.om == nil || other == nil { if vars == nil || vars.om == nil || other == nil {
return return
@@ -133,35 +133,6 @@ func (vars *Vars) Merge(other *Vars, include *Include) {
} }
} }
// ReverseMerge merges other variables with the existing variables in vars, but
// keeps the other variables first in order. If the include parameter is not
// nil and it is an advanced import, the directory is set to the value of the
// include parameter.
func (vars *Vars) ReverseMerge(other *Vars, include *Include) {
if vars == nil || vars.om == nil || other == nil || other.om == nil {
return
}
newOM := orderedmap.NewOrderedMap[string, Var]()
other.mutex.RLock()
for pair := other.om.Front(); pair != nil; pair = pair.Next() {
val := pair.Value
if include != nil && include.AdvancedImport {
val.Dir = include.Dir
}
newOM.Set(pair.Key, val)
}
other.mutex.RUnlock()
vars.mutex.Lock()
for pair := vars.om.Front(); pair != nil; pair = pair.Next() {
newOM.Set(pair.Key, pair.Value)
}
vars.om = newOM
vars.mutex.Unlock()
}
func (vs *Vars) DeepCopy() *Vars { func (vs *Vars) DeepCopy() *Vars {
if vs == nil { if vs == nil {
return nil return nil

View File

@@ -72,16 +72,6 @@ func NewNode(
return node, err return node, err
} }
func isRemoteEntrypoint(entrypoint string) bool {
scheme, _ := getScheme(entrypoint)
switch scheme {
case "git", "http", "https":
return true
default:
return false
}
}
func getScheme(uri string) (string, error) { func getScheme(uri string) (string, error) {
u, err := giturls.Parse(uri) u, err := giturls.Parse(uri)
if u == nil { if u == nil {

View File

@@ -4,8 +4,8 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/fsext" "github.com/go-task/task/v3/internal/fsext"
@@ -18,24 +18,15 @@ type FileNode struct {
} }
func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) { func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
// Find the entrypoint file var err error
resolvedEntrypoint, err := fsext.Search(entrypoint, dir, DefaultTaskfiles) base := NewBaseNode(dir, opts...)
if err != nil { entrypoint, base.dir, err = fsext.Search(entrypoint, base.dir, defaultTaskfiles)
if errors.Is(err, os.ErrNotExist) {
return nil, errors.TaskfileNotFoundError{URI: entrypoint, Walk: false}
}
return nil, err
}
// Resolve the directory
resolvedDir, err := fsext.ResolveDir(entrypoint, resolvedEntrypoint, dir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &FileNode{ return &FileNode{
baseNode: NewBaseNode(resolvedDir, opts...), baseNode: base,
entrypoint: resolvedEntrypoint, entrypoint: entrypoint,
}, nil }, nil
} }
@@ -54,7 +45,10 @@ func (node *FileNode) Read() ([]byte, error) {
func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) { func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) {
// If the file is remote, we don't need to resolve the path // If the file is remote, we don't need to resolve the path
if isRemoteEntrypoint(entrypoint) { if strings.Contains(entrypoint, "://") {
return entrypoint, nil
}
if strings.HasPrefix(entrypoint, "git") {
return entrypoint, nil return entrypoint, nil
} }

View File

@@ -3,14 +3,16 @@ package taskfile
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"net/url" "net/url"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
giturls "github.com/chainguard-dev/git-urls" giturls "github.com/chainguard-dev/git-urls"
"github.com/hashicorp/go-getter" "github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/execext"
@@ -26,36 +28,6 @@ type GitNode struct {
path string path string
} }
type gitRepoCache struct {
mu sync.Mutex // Protects the locks map
locks map[string]*sync.Mutex // One mutex per repo cache key
}
func (c *gitRepoCache) getLockForRepo(cacheKey string) *sync.Mutex {
c.mu.Lock()
defer c.mu.Unlock()
if _, exists := c.locks[cacheKey]; !exists {
c.locks[cacheKey] = &sync.Mutex{}
}
return c.locks[cacheKey]
}
var globalGitRepoCache = &gitRepoCache{
locks: make(map[string]*sync.Mutex),
}
func CleanGitCache() error {
// Clear the in-memory locks map to prevent memory leak
globalGitRepoCache.mu.Lock()
globalGitRepoCache.locks = make(map[string]*sync.Mutex)
globalGitRepoCache.mu.Unlock()
cacheDir := filepath.Join(os.TempDir(), "task-git-repos")
return os.RemoveAll(cacheDir)
}
func NewGitNode( func NewGitNode(
entrypoint string, entrypoint string,
dir string, dir string,
@@ -100,78 +72,24 @@ func (node *GitNode) Read() ([]byte, error) {
return node.ReadContext(context.Background()) return node.ReadContext(context.Background())
} }
func (node *GitNode) buildURL() string { func (node *GitNode) ReadContext(_ context.Context) ([]byte, error) {
// Get the base URL fs := memfs.New()
baseURL := node.url.String() storer := memory.NewStorage()
_, err := git.Clone(storer, fs, &git.CloneOptions{
ref := node.ref URL: node.url.String(),
if ref == "" { ReferenceName: plumbing.ReferenceName(node.ref),
ref = "HEAD" SingleBranch: true,
} Depth: 1,
// Always use git:: prefix for git URLs (following Terraform's pattern) })
// This forces go-getter to use git protocol
return fmt.Sprintf("git::%s?ref=%s&depth=1", baseURL, ref)
}
// getOrCloneRepo returns the path to a cached git repository.
// If the repository is not cached, it clones it first.
// This function is thread-safe: multiple goroutines cloning the same repo+ref
// will synchronize, and only one clone operation will occur.
//
// The cache directory is /tmp/task-git-repos/{cache_key}/
func (node *GitNode) getOrCloneRepo(ctx context.Context) (string, error) {
cacheKey := node.repoCacheKey()
repoMutex := globalGitRepoCache.getLockForRepo(cacheKey)
repoMutex.Lock()
defer repoMutex.Unlock()
// Check if context was cancelled while waiting for lock
if err := ctx.Err(); err != nil {
return "", fmt.Errorf("context cancelled while waiting for repository lock: %w", err)
}
cacheDir := filepath.Join(os.TempDir(), "task-git-repos", cacheKey)
// check if repo is already cached (under the lock)
gitDir := filepath.Join(cacheDir, ".git")
if _, err := os.Stat(gitDir); err == nil {
return cacheDir, nil
}
getterURL := node.buildURL()
client := &getter.Client{
Ctx: ctx,
Src: getterURL,
Dst: cacheDir,
Mode: getter.ClientModeDir,
}
if err := client.Get(); err != nil {
_ = os.RemoveAll(cacheDir)
return "", fmt.Errorf("failed to clone repository: %w", err)
}
return cacheDir, nil
}
func (node *GitNode) ReadContext(ctx context.Context) ([]byte, error) {
// Get or clone the repository into cache
repoDir, err := node.getOrCloneRepo(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
file, err := fs.Open(node.path)
// Build path to Taskfile in the cached repo if err != nil {
taskfilePath := node.path return nil, err
if taskfilePath == "" {
taskfilePath = "Taskfile.yml"
} }
filePath := filepath.Join(repoDir, taskfilePath) // Read the entire response body
b, err := io.ReadAll(file)
// Read file from cached repo
b, err := os.ReadFile(filePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -180,11 +98,6 @@ func (node *GitNode) ReadContext(ctx context.Context) ([]byte, error) {
} }
func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) { func (node *GitNode) ResolveEntrypoint(entrypoint string) (string, error) {
// If the file is remote, we don't need to resolve the path
if isRemoteEntrypoint(entrypoint) {
return entrypoint, nil
}
dir, _ := filepath.Split(node.path) 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 != "" { if node.ref != "" {
@@ -220,22 +133,6 @@ func (node *GitNode) CacheKey() string {
return fmt.Sprintf("git.%s.%s.%s", node.url.Host, prefix, checksum) return fmt.Sprintf("git.%s.%s.%s", node.url.Host, prefix, checksum)
} }
// repoCacheKey generates a unique cache key for the repository+ref combination.
// Unlike CacheKey() which includes the file path, this identifies the repository itself.
// Two GitNodes with the same repo+ref but different file paths will share the same cache.
//
// Returns a path like: github.com/user/repo.git/main
func (node *GitNode) repoCacheKey() string {
repoPath := strings.Trim(node.url.Path, "/")
ref := node.ref
if ref == "" {
ref = "HEAD"
}
return filepath.Join(node.url.Host, repoPath, ref)
}
func splitURLOnDoubleSlash(u *url.URL) (string, string) { func splitURLOnDoubleSlash(u *url.URL) (string, string) {
x := strings.Split(u.Path, "//") x := strings.Split(u.Path, "//")
switch len(x) { switch len(x) {

View File

@@ -21,17 +21,6 @@ func TestGitNode_ssh(t *testing.T) {
assert.Equal(t, "ssh://git@github.com/foo/bar.git//common.yml?ref=main", entrypoint) assert.Equal(t, "ssh://git@github.com/foo/bar.git//common.yml?ref=main", entrypoint)
} }
func TestGitNode_sshWithAltRepo(t *testing.T) {
t.Parallel()
node, err := NewGitNode("git@github.com:foo/bar.git//Taskfile.yml?ref=main", "", false)
assert.NoError(t, err)
entrypoint, err := node.ResolveEntrypoint("git@github.com:foo/other.git//Taskfile.yml?ref=dev")
assert.NoError(t, err)
assert.Equal(t, "git@github.com:foo/other.git//Taskfile.yml?ref=dev", entrypoint)
}
func TestGitNode_sshWithDir(t *testing.T) { func TestGitNode_sshWithDir(t *testing.T) {
t.Parallel() t.Parallel()
@@ -102,146 +91,3 @@ func TestGitNode_CacheKey(t *testing.T) {
assert.Equal(t, tt.expectedKey, key) assert.Equal(t, tt.expectedKey, key)
} }
} }
func TestGitNode_buildURL(t *testing.T) {
t.Parallel()
tests := []struct {
name string
entrypoint string
expectedURL string
}{
{
name: "HTTPS with ref",
entrypoint: "https://github.com/foo/bar.git//Taskfile.yml?ref=main",
expectedURL: "git::https://github.com/foo/bar.git?ref=main&depth=1",
},
{
name: "SSH with ref",
entrypoint: "git@github.com:foo/bar.git//Taskfile.yml?ref=main",
expectedURL: "git::ssh://git@github.com/foo/bar.git?ref=main&depth=1",
},
{
name: "HTTPS with tag ref",
entrypoint: "https://github.com/foo/bar.git//Taskfile.yml?ref=v1.0.0",
expectedURL: "git::https://github.com/foo/bar.git?ref=v1.0.0&depth=1",
},
{
name: "HTTPS without ref (uses remote HEAD)",
entrypoint: "https://github.com/foo/bar.git//Taskfile.yml",
expectedURL: "git::https://github.com/foo/bar.git?ref=HEAD&depth=1",
},
{
name: "SSH with directory path",
entrypoint: "git@github.com:foo/bar.git//directory/Taskfile.yml?ref=dev",
expectedURL: "git::ssh://git@github.com/foo/bar.git?ref=dev&depth=1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
node, err := NewGitNode(tt.entrypoint, "", false)
require.NoError(t, err)
gotURL := node.buildURL()
assert.Equal(t, tt.expectedURL, gotURL)
})
}
}
func TestRepoCacheKey_SameRepoSameRef(t *testing.T) {
t.Parallel()
// Same repo, same ref, different files should have SAME cache key
node1, err := NewGitNode("https://github.com/foo/bar.git//file1.yml?ref=main", "", false)
require.NoError(t, err)
node2, err := NewGitNode("https://github.com/foo/bar.git//dir/file2.yml?ref=main", "", false)
require.NoError(t, err)
key1 := node1.repoCacheKey()
key2 := node2.repoCacheKey()
assert.Equal(t, key1, key2, "Same repo+ref should generate same cache key regardless of file path")
}
func TestRepoCacheKey_SameRepoDifferentRef(t *testing.T) {
t.Parallel()
// Same repo, different ref should have DIFFERENT cache keys
node1, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=main", "", false)
require.NoError(t, err)
node2, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=dev", "", false)
require.NoError(t, err)
key1 := node1.repoCacheKey()
key2 := node2.repoCacheKey()
assert.NotEqual(t, key1, key2, "Different refs should generate different cache keys")
}
func TestRepoCacheKey_DifferentRepos(t *testing.T) {
t.Parallel()
// Different repos should have DIFFERENT cache keys
node1, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=main", "", false)
require.NoError(t, err)
node2, err := NewGitNode("https://github.com/foo/other.git//file.yml?ref=main", "", false)
require.NoError(t, err)
key1 := node1.repoCacheKey()
key2 := node2.repoCacheKey()
assert.NotEqual(t, key1, key2, "Different repos should generate different cache keys")
}
func TestRepoCacheKey_NoRefVsHead(t *testing.T) {
t.Parallel()
// No ref (defaults to HEAD) vs explicit HEAD should have SAME cache key
node1, err := NewGitNode("https://github.com/foo/bar.git//file.yml", "", false)
require.NoError(t, err)
node2, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=HEAD", "", false)
require.NoError(t, err)
key1 := node1.repoCacheKey()
key2 := node2.repoCacheKey()
assert.Equal(t, key1, key2, "No ref and explicit HEAD should generate same cache key")
}
func TestRepoCacheKey_SSHvsHTTPS(t *testing.T) {
t.Parallel()
// SSH vs HTTPS pointing to same repo should have SAME cache key
// They clone the same repo, so we want to share the cache
node1, err := NewGitNode("git@github.com:foo/bar.git//file.yml?ref=main", "", false)
require.NoError(t, err)
node2, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=main", "", false)
require.NoError(t, err)
key1 := node1.repoCacheKey()
key2 := node2.repoCacheKey()
assert.Equal(t, key1, key2, "SSH and HTTPS for same repo should share cache")
}
func TestRepoCacheKey_Consistency(t *testing.T) {
t.Parallel()
// Calling repoCacheKey multiple times on same node should return same key
node, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=main", "", false)
require.NoError(t, err)
key1 := node.repoCacheKey()
key2 := node.repoCacheKey()
key3 := node.repoCacheKey()
assert.Equal(t, key1, key2)
assert.Equal(t, key2, key3)
}

View File

@@ -53,7 +53,7 @@ func (node *HTTPNode) ReadContext(ctx context.Context) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, err := http.NewRequestWithContext(ctx, "GET", url.String(), nil) req, err := http.NewRequest("GET", url.String(), nil)
if err != nil { if err != nil {
return nil, errors.TaskfileFetchFailedError{URI: node.Location()} return nil, errors.TaskfileFetchFailedError{URI: node.Location()}
} }

View File

@@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"os" "os"
"strings"
"github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
@@ -42,7 +43,7 @@ func (node *StdinNode) Read() ([]byte, error) {
func (node *StdinNode) ResolveEntrypoint(entrypoint string) (string, error) { func (node *StdinNode) ResolveEntrypoint(entrypoint string) (string, error) {
// If the file is remote, we don't need to resolve the path // If the file is remote, we don't need to resolve the path
if isRemoteEntrypoint(entrypoint) { if strings.Contains(entrypoint, "://") {
return entrypoint, nil return entrypoint, nil
} }

View File

@@ -3,14 +3,13 @@ package taskfile
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"os" "os"
"sync" "sync"
"time" "time"
"github.com/dominikbraun/graph" "github.com/dominikbraun/graph"
"go.yaml.in/yaml/v4"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"gopkg.in/yaml.v3"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/env" "github.com/go-task/task/v3/internal/env"
@@ -44,7 +43,6 @@ type (
insecure bool insecure bool
download bool download bool
offline bool offline bool
trustedHosts []string
tempDir string tempDir string
cacheExpiryDuration time.Duration cacheExpiryDuration time.Duration
debugFunc DebugFunc debugFunc DebugFunc
@@ -61,7 +59,6 @@ func NewReader(opts ...ReaderOption) *Reader {
insecure: false, insecure: false,
download: false, download: false,
offline: false, offline: false,
trustedHosts: nil,
tempDir: os.TempDir(), tempDir: os.TempDir(),
cacheExpiryDuration: 0, cacheExpiryDuration: 0,
debugFunc: nil, debugFunc: nil,
@@ -122,20 +119,6 @@ func (o *offlineOption) ApplyToReader(r *Reader) {
r.offline = o.offline r.offline = o.offline
} }
// WithTrustedHosts configures the [Reader] with a list of trusted hosts for remote
// Taskfiles. Hosts in this list will not prompt for user confirmation.
func WithTrustedHosts(trustedHosts []string) ReaderOption {
return &trustedHostsOption{trustedHosts: trustedHosts}
}
type trustedHostsOption struct {
trustedHosts []string
}
func (o *trustedHostsOption) ApplyToReader(r *Reader) {
r.trustedHosts = o.trustedHosts
}
// WithTempDir sets the temporary directory that will be used by the [Reader]. // WithTempDir sets the temporary directory that will be used by the [Reader].
// By default, the reader uses [os.TempDir]. // By default, the reader uses [os.TempDir].
func WithTempDir(tempDir string) ReaderOption { func WithTempDir(tempDir string) ReaderOption {
@@ -204,15 +187,9 @@ func (o *promptFuncOption) ApplyToReader(r *Reader) {
// building an [ast.TaskfileGraph] as it goes. If any errors occur, they will be // building an [ast.TaskfileGraph] as it goes. If any errors occur, they will be
// returned immediately. // returned immediately.
func (r *Reader) Read(ctx context.Context, node Node) (*ast.TaskfileGraph, error) { func (r *Reader) Read(ctx context.Context, node Node) (*ast.TaskfileGraph, error) {
// Clean up git cache after reading all taskfiles
defer func() {
_ = CleanGitCache()
}()
if err := r.include(ctx, node); err != nil { if err := r.include(ctx, node); err != nil {
return nil, err return nil, err
} }
return r.graph, nil return r.graph, nil
} }
@@ -229,28 +206,6 @@ func (r *Reader) promptf(format string, a ...any) error {
return nil return nil
} }
// isTrusted checks if a URI's host matches any of the trusted hosts patterns.
func (r *Reader) isTrusted(uri string) bool {
if len(r.trustedHosts) == 0 {
return false
}
// Parse the URI to extract the host
parsedURL, err := url.Parse(uri)
if err != nil {
return false
}
host := parsedURL.Host
// Check against each trusted pattern (exact match including port if provided)
for _, pattern := range r.trustedHosts {
if host == pattern {
return true
}
}
return false
}
func (r *Reader) include(ctx context.Context, node Node) error { func (r *Reader) include(ctx context.Context, node Node) error {
// Create a new vertex for the Taskfile // Create a new vertex for the Taskfile
vertex := &ast.TaskfileVertex{ vertex := &ast.TaskfileVertex{
@@ -504,9 +459,9 @@ func (r *Reader) readRemoteNodeContent(ctx context.Context, node RemoteNode) ([]
// If there is no manual checksum pin, run the automatic checks // If there is no manual checksum pin, run the automatic checks
if node.Checksum() == "" { if node.Checksum() == "" {
// Prompt the user if required (unless host is trusted) // Prompt the user if required
prompt := cache.ChecksumPrompt(checksum) prompt := cache.ChecksumPrompt(checksum)
if prompt != "" && !r.isTrusted(node.Location()) { if prompt != "" {
if err := func() error { if err := func() error {
r.promptMutex.Lock() r.promptMutex.Lock()
defer r.promptMutex.Unlock() defer r.promptMutex.Unlock()

View File

@@ -12,8 +12,7 @@ import (
) )
var ( var (
// DefaultTaskfiles is the list of Taskfile file names supported by default. defaultTaskfiles = []string{
DefaultTaskfiles = []string{
"Taskfile.yml", "Taskfile.yml",
"taskfile.yml", "taskfile.yml",
"Taskfile.yaml", "Taskfile.yaml",
@@ -29,7 +28,6 @@ var (
"text/x-yaml", "text/x-yaml",
"application/yaml", "application/yaml",
"application/x-yaml", "application/x-yaml",
"application/octet-stream",
} }
) )
@@ -68,7 +66,7 @@ func RemoteExists(ctx context.Context, u url.URL) (*url.URL, error) {
// If the request was not successful, append the default Taskfile names to // If the request was not successful, append the default Taskfile names to
// the URL and return the URL of the first successful request // the URL and return the URL of the first successful request
for _, taskfile := range DefaultTaskfiles { for _, taskfile := range defaultTaskfiles {
// Fixes a bug with JoinPath where a leading slash is not added to the // Fixes a bug with JoinPath where a leading slash is not added to the
// path if it is empty // path if it is empty
if u.Path == "" { if u.Path == "" {

View File

@@ -1,13 +1,12 @@
# yaml-language-server: $schema=https://taskfile.dev/schema.json # https://taskfile.dev
version: '3' version: '3'
vars: vars:
GREETING: Hello, world! GREETING: Hello, World!
tasks: tasks:
default: default:
desc: Print a greeting message
cmds: cmds:
- echo "{{.GREETING}}" - echo "{{.GREETING}}"
silent: true silent: true

View File

@@ -1,63 +1,8 @@
package ast package ast
import ( import "github.com/Masterminds/semver/v3"
"cmp"
"maps"
"slices"
"time"
"github.com/Masterminds/semver/v3"
)
type TaskRC struct { type TaskRC struct {
Version *semver.Version `yaml:"version"` Version *semver.Version `yaml:"version"`
Verbose *bool `yaml:"verbose"` Experiments map[string]int `yaml:"experiments"`
Color *bool `yaml:"color"`
DisableFuzzy *bool `yaml:"disable-fuzzy"`
Concurrency *int `yaml:"concurrency"`
Remote Remote `yaml:"remote"`
Failfast bool `yaml:"failfast"`
Experiments map[string]int `yaml:"experiments"`
}
type Remote struct {
Insecure *bool `yaml:"insecure"`
Offline *bool `yaml:"offline"`
Timeout *time.Duration `yaml:"timeout"`
CacheExpiry *time.Duration `yaml:"cache-expiry"`
CacheDir *string `yaml:"cache-dir"`
TrustedHosts []string `yaml:"trusted-hosts"`
}
// Merge combines the current TaskRC with another TaskRC, prioritizing non-nil fields from the other TaskRC.
func (t *TaskRC) Merge(other *TaskRC) {
if other == nil {
return
}
t.Version = cmp.Or(other.Version, t.Version)
if t.Experiments == nil && other.Experiments != nil {
t.Experiments = other.Experiments
} else if t.Experiments != nil && other.Experiments != nil {
maps.Copy(t.Experiments, other.Experiments)
}
// Merge Remote fields
t.Remote.Insecure = cmp.Or(other.Remote.Insecure, t.Remote.Insecure)
t.Remote.Offline = cmp.Or(other.Remote.Offline, t.Remote.Offline)
t.Remote.Timeout = cmp.Or(other.Remote.Timeout, t.Remote.Timeout)
t.Remote.CacheExpiry = cmp.Or(other.Remote.CacheExpiry, t.Remote.CacheExpiry)
t.Remote.CacheDir = cmp.Or(other.Remote.CacheDir, t.Remote.CacheDir)
if len(other.Remote.TrustedHosts) > 0 {
merged := slices.Concat(other.Remote.TrustedHosts, t.Remote.TrustedHosts)
slices.Sort(merged)
t.Remote.TrustedHosts = slices.Compact(merged)
}
t.Verbose = cmp.Or(other.Verbose, t.Verbose)
t.Color = cmp.Or(other.Color, t.Color)
t.DisableFuzzy = cmp.Or(other.DisableFuzzy, t.DisableFuzzy)
t.Concurrency = cmp.Or(other.Concurrency, t.Concurrency)
t.Failfast = cmp.Or(other.Failfast, t.Failfast)
} }

View File

@@ -1,24 +1,24 @@
package taskrc package taskrc
import ( import "github.com/go-task/task/v3/internal/fsext"
"github.com/go-task/task/v3/internal/fsext"
)
type Node struct { type Node struct {
entrypoint string entrypoint string
dir string
} }
func NewNode( func NewNode(
entrypoint string, entrypoint string,
dir string, dir string,
possibleFileNames []string,
) (*Node, error) { ) (*Node, error) {
dir = fsext.DefaultDir(entrypoint, dir) dir = fsext.DefaultDir(entrypoint, dir)
resolvedEntrypoint, err := fsext.SearchPath(dir, possibleFileNames) var err error
entrypoint, dir, err = fsext.Search(entrypoint, dir, defaultTaskRCs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Node{ return &Node{
entrypoint: resolvedEntrypoint, entrypoint: entrypoint,
dir: dir,
}, nil }, nil
} }

View File

@@ -3,7 +3,7 @@ package taskrc
import ( import (
"os" "os"
"go.yaml.in/yaml/v4" "gopkg.in/yaml.v3"
"github.com/go-task/task/v3/taskrc/ast" "github.com/go-task/task/v3/taskrc/ast"
) )

View File

@@ -1,92 +1,6 @@
package taskrc package taskrc
import ( var defaultTaskRCs = []string{
"os" ".taskrc.yml",
"path/filepath" ".taskrc.yaml",
"slices"
"strings"
"github.com/go-task/task/v3/internal/fsext"
"github.com/go-task/task/v3/taskrc/ast"
)
var (
defaultXDGTaskRCs = []string{
"taskrc.yml",
"taskrc.yaml",
}
defaultTaskRCs = []string{
".taskrc.yml",
".taskrc.yaml",
}
)
// GetConfig loads and merges local and global Task configuration files
func GetConfig(dir string) (*ast.TaskRC, error) {
var config *ast.TaskRC
reader := NewReader()
// Read the XDG config file
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
xdgConfigNode, err := NewNode("", filepath.Join(xdgConfigHome, "task"), defaultXDGTaskRCs)
if err == nil && xdgConfigNode != nil {
xdgConfig, err := reader.Read(xdgConfigNode)
if err != nil {
return nil, err
}
config = xdgConfig
}
}
// If the current path does not contain $HOME
// If it does contain $HOME, then we will find this config later anyway
home, err := os.UserHomeDir()
if err == nil && !strings.Contains(home, dir) {
homeNode, err := NewNode("", home, defaultTaskRCs)
if err == nil && homeNode != nil {
homeConfig, err := reader.Read(homeNode)
if err != nil {
return nil, err
}
if config == nil {
config = homeConfig
} else {
config.Merge(homeConfig)
}
}
}
// Find all the nodes from the given directory up to the users home directory
absDir, err := filepath.Abs(dir)
if err != nil {
return config, err
}
entrypoints, err := fsext.SearchAll("", absDir, defaultTaskRCs)
if err != nil {
return config, err
}
// Reverse the entrypoints since we want the child files to override parent ones
slices.Reverse(entrypoints)
// Loop over the nodes, and merge them into the main config
for _, entrypoint := range entrypoints {
node, err := NewNode("", entrypoint, defaultTaskRCs)
if err != nil {
return nil, err
}
localConfig, err := reader.Read(node)
if err != nil {
return nil, err
}
if localConfig == nil {
continue
}
if config == nil {
config = localConfig
continue
}
config.Merge(localConfig)
}
return config, nil
} }

View File

@@ -1,309 +0,0 @@
package taskrc
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/go-task/task/v3/taskrc/ast"
)
const (
xdgConfigYAML = `
experiments:
FOO: 1
BAR: 1
BAZ: 1
`
homeConfigYAML = `
experiments:
FOO: 2
BAR: 2
`
localConfigYAML = `
experiments:
FOO: 3
`
)
func setupDirs(t *testing.T) (string, string, string) {
t.Helper()
xdgConfigDir := t.TempDir()
xdgTaskConfigDir := filepath.Join(xdgConfigDir, "task")
require.NoError(t, os.Mkdir(xdgTaskConfigDir, 0o755))
homeDir := t.TempDir()
localDir := filepath.Join(homeDir, "local")
require.NoError(t, os.Mkdir(localDir, 0o755))
t.Setenv("XDG_CONFIG_HOME", xdgConfigDir)
t.Setenv("HOME", homeDir)
return xdgTaskConfigDir, homeDir, localDir
}
func writeFile(t *testing.T, dir, filename, content string) {
t.Helper()
err := os.WriteFile(filepath.Join(dir, filename), []byte(content), 0o644)
assert.NoError(t, err)
}
func TestGetConfig_NoConfigFiles(t *testing.T) { //nolint:paralleltest // cannot run in parallel
_, _, localDir := setupDirs(t)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.Nil(t, cfg)
}
func TestGetConfig_OnlyXDG(t *testing.T) { //nolint:paralleltest // cannot run in parallel
xdgDir, _, localDir := setupDirs(t)
writeFile(t, xdgDir, "taskrc.yml", xdgConfigYAML)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.Equal(t, &ast.TaskRC{
Version: nil,
Experiments: map[string]int{
"FOO": 1,
"BAR": 1,
"BAZ": 1,
},
}, cfg)
}
func TestGetConfig_OnlyHome(t *testing.T) { //nolint:paralleltest // cannot run in parallel
_, homeDir, localDir := setupDirs(t)
writeFile(t, homeDir, ".taskrc.yml", homeConfigYAML)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.Equal(t, &ast.TaskRC{
Version: nil,
Experiments: map[string]int{
"FOO": 2,
"BAR": 2,
},
}, cfg)
}
func TestGetConfig_OnlyLocal(t *testing.T) { //nolint:paralleltest // cannot run in parallel
_, _, localDir := setupDirs(t)
writeFile(t, localDir, ".taskrc.yml", localConfigYAML)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.Equal(t, &ast.TaskRC{
Version: nil,
Experiments: map[string]int{
"FOO": 3,
},
}, cfg)
}
func TestGetConfig_All(t *testing.T) { //nolint:paralleltest // cannot run in parallel
xdgConfigDir, homeDir, localDir := setupDirs(t)
// Write local config
writeFile(t, localDir, ".taskrc.yml", localConfigYAML)
// Write home config
writeFile(t, homeDir, ".taskrc.yml", homeConfigYAML)
// Write XDG config
writeFile(t, xdgConfigDir, "taskrc.yml", xdgConfigYAML)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.NotNil(t, cfg)
assert.Equal(t, &ast.TaskRC{
Version: nil,
Experiments: map[string]int{
"FOO": 3,
"BAR": 2,
"BAZ": 1,
},
}, cfg)
}
func TestGetConfig_RemoteTrustedHosts(t *testing.T) { //nolint:paralleltest // cannot run in parallel
_, _, localDir := setupDirs(t)
// Test with single host
configYAML := `
remote:
trusted-hosts:
- github.com
`
writeFile(t, localDir, ".taskrc.yml", configYAML)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.NotNil(t, cfg)
assert.Equal(t, []string{"github.com"}, cfg.Remote.TrustedHosts)
// Test with multiple hosts
configYAML = `
remote:
trusted-hosts:
- github.com
- gitlab.com
- example.com:8080
`
writeFile(t, localDir, ".taskrc.yml", configYAML)
cfg, err = GetConfig(localDir)
assert.NoError(t, err)
assert.NotNil(t, cfg)
assert.Equal(t, []string{"github.com", "gitlab.com", "example.com:8080"}, cfg.Remote.TrustedHosts)
}
func TestGetConfig_RemoteTrustedHostsMerge(t *testing.T) { //nolint:paralleltest // cannot run in parallel
t.Run("file-based merge precedence", func(t *testing.T) { //nolint:paralleltest // parent test cannot run in parallel
xdgConfigDir, homeDir, localDir := setupDirs(t)
// XDG config has github.com and gitlab.com
xdgConfig := `
remote:
trusted-hosts:
- github.com
- gitlab.com
timeout: "30s"
`
writeFile(t, xdgConfigDir, "taskrc.yml", xdgConfig)
// Home config has example.com (should be combined with XDG)
homeConfig := `
remote:
trusted-hosts:
- example.com
`
writeFile(t, homeDir, ".taskrc.yml", homeConfig)
cfg, err := GetConfig(localDir)
assert.NoError(t, err)
assert.NotNil(t, cfg)
// Home config entries come first, then XDG
assert.Equal(t, []string{"example.com", "github.com", "gitlab.com"}, cfg.Remote.TrustedHosts)
// Test with local config too
localConfig := `
remote:
trusted-hosts:
- local.dev
`
writeFile(t, localDir, ".taskrc.yml", localConfig)
cfg, err = GetConfig(localDir)
assert.NoError(t, err)
assert.NotNil(t, cfg)
// Local config entries come first
assert.Equal(t, []string{"example.com", "github.com", "gitlab.com", "local.dev"}, cfg.Remote.TrustedHosts)
})
t.Run("merge edge cases", func(t *testing.T) { //nolint:paralleltest // parent test cannot run in parallel
tests := []struct {
name string
base *ast.TaskRC
other *ast.TaskRC
expected []string
}{
{
name: "merge hosts into empty",
base: &ast.TaskRC{},
other: &ast.TaskRC{
Remote: ast.Remote{
TrustedHosts: []string{"github.com"},
},
},
expected: []string{"github.com"},
},
{
name: "merge combines lists",
base: &ast.TaskRC{
Remote: ast.Remote{
TrustedHosts: []string{"base.com"},
},
},
other: &ast.TaskRC{
Remote: ast.Remote{
TrustedHosts: []string{"other.com"},
},
},
expected: []string{"base.com", "other.com"},
},
{
name: "merge empty list does not override",
base: &ast.TaskRC{
Remote: ast.Remote{
TrustedHosts: []string{"base.com"},
},
},
other: &ast.TaskRC{
Remote: ast.Remote{
TrustedHosts: []string{},
},
},
expected: []string{"base.com"},
},
{
name: "merge nil does not override",
base: &ast.TaskRC{
Remote: ast.Remote{
TrustedHosts: []string{"base.com"},
},
},
other: &ast.TaskRC{
Remote: ast.Remote{
TrustedHosts: nil,
},
},
expected: []string{"base.com"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { //nolint:paralleltest // parent test cannot run in parallel
tt.base.Merge(tt.other)
assert.Equal(t, tt.expected, tt.base.Remote.TrustedHosts)
})
}
})
t.Run("all remote fields merge", func(t *testing.T) { //nolint:paralleltest // parent test cannot run in parallel
insecureTrue := true
offlineTrue := true
timeout := 30 * time.Second
cacheExpiry := 1 * time.Hour
base := &ast.TaskRC{}
other := &ast.TaskRC{
Remote: ast.Remote{
Insecure: &insecureTrue,
Offline: &offlineTrue,
Timeout: &timeout,
CacheExpiry: &cacheExpiry,
TrustedHosts: []string{"github.com", "gitlab.com"},
},
}
base.Merge(other)
assert.Equal(t, &insecureTrue, base.Remote.Insecure)
assert.Equal(t, &offlineTrue, base.Remote.Offline)
assert.Equal(t, &timeout, base.Remote.Timeout)
assert.Equal(t, &cacheExpiry, base.Remote.CacheExpiry)
assert.Equal(t, []string{"github.com", "gitlab.com"}, base.Remote.TrustedHosts)
})
}

View File

@@ -12,14 +12,6 @@ tasks:
generates: generates:
- ./generated.txt - ./generated.txt
method: checksum method: checksum
build-*:
cmds:
- cp ./source.txt ./generated-{{index .MATCH 0}}.txt
sources:
- ./source.txt
generates:
- ./generated-{{index .MATCH 0}}.txt
method: checksum
build-with-status: build-with-status:
cmds: cmds:

View File

@@ -1 +0,0 @@
Hello, World!

View File

@@ -1,14 +0,0 @@
version: '3'
tasks:
default:
deps:
- dep1
- dep2
- dep3
- dep4
dep1: sleep 0.1 && echo 'dep1'
dep2: sleep 0.2 && echo 'dep2'
dep3: sleep 0.3 && echo 'dep3'
dep4: exit 1

View File

@@ -1 +0,0 @@
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1

View File

@@ -1,3 +0,0 @@
dep1
dep2
dep3

View File

@@ -1 +0,0 @@
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1

View File

@@ -1,15 +0,0 @@
version: '3'
tasks:
default:
deps:
- dep1
- dep2
- dep3
- dep4
failfast: true
dep1: sleep 0.1 && echo 'dep1'
dep2: sleep 0.2 && echo 'dep2'
dep3: sleep 0.3 && echo 'dep3'
dep4: exit 1

View File

@@ -1 +0,0 @@
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1

View File

@@ -1,7 +0,0 @@
version: '3'
tasks:
foo:
label: "foobar"
cmds:
- "false"

View File

@@ -1 +0,0 @@
task: Failed to run task "foobar": exit status 1

Some files were not shown because too many files have changed in this diff Show More