mirror of
https://github.com/go-task/task.git
synced 2026-05-18 13:15:41 +02:00
Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc378cfb92 | ||
|
|
9488a2a744 | ||
|
|
6ece2445ae | ||
|
|
9b95e758f4 | ||
|
|
28fee2c356 | ||
|
|
763e77467b | ||
|
|
f2385e625d | ||
|
|
e929cccd73 | ||
|
|
cb183349b7 | ||
|
|
2ebbb99f58 | ||
|
|
6660afc8d2 | ||
|
|
b710259bfa | ||
|
|
4ec6c453bd | ||
|
|
28408ef3f4 | ||
|
|
a2d34ffc4c | ||
|
|
1a190a118f | ||
|
|
18efa3982f | ||
|
|
655e83454e | ||
|
|
3ad4604c36 | ||
|
|
5a27d04655 | ||
|
|
ea933bcc55 | ||
|
|
e0d6b71971 | ||
|
|
d7ee855e49 | ||
|
|
511f35a456 | ||
|
|
5889ff6b65 | ||
|
|
85a98b5f90 | ||
|
|
89b6140166 | ||
|
|
8cd51af3b0 | ||
|
|
a40ddd4949 | ||
|
|
b1814277c2 | ||
|
|
500ab8b941 | ||
|
|
745633dc0e | ||
|
|
9b99866224 | ||
|
|
54e4905432 | ||
|
|
c95805e0e0 | ||
|
|
4560589652 | ||
|
|
084d6444b4 | ||
|
|
3fb7919577 | ||
|
|
69b345efc9 | ||
|
|
4af5278d73 | ||
|
|
12fbdd3ec7 | ||
|
|
72a349b0e9 | ||
|
|
896d65b21f | ||
|
|
2161f33b5c | ||
|
|
b93638b97a | ||
|
|
47b78ca879 | ||
|
|
f0b15d397b | ||
|
|
eb285fa3d2 | ||
|
|
02b13a687a | ||
|
|
a085d62727 | ||
|
|
4ab1958df1 | ||
|
|
54ca217b92 | ||
|
|
a6c0c1daba | ||
|
|
9cc1c7b40b | ||
|
|
7901cce831 | ||
|
|
c7b4f26900 | ||
|
|
3ed403b839 | ||
|
|
386dcbc1a0 | ||
|
|
799bc85498 | ||
|
|
0d9e8dd71b | ||
|
|
a927ffb31e | ||
|
|
42ad618205 | ||
|
|
2b713f564f | ||
|
|
cb8e94aa33 | ||
|
|
6bc339d714 | ||
|
|
5712c463f5 | ||
|
|
78cc6e5fd3 | ||
|
|
38e07ea812 | ||
|
|
72e25a25fd | ||
|
|
a496ee5fcb | ||
|
|
ef4292c42f | ||
|
|
dc315efc7f | ||
|
|
a3a3e7fb0b | ||
|
|
ee99849b1d | ||
|
|
bf9dc3f662 | ||
|
|
94f82cbc5a | ||
|
|
b14318ed3f | ||
|
|
17757c0c15 | ||
|
|
19f72b7eb0 | ||
|
|
0052ad2309 | ||
|
|
af1e755196 | ||
|
|
43074c20f2 | ||
|
|
39c86992bd | ||
|
|
c71241bcbd | ||
|
|
7c2bb78540 | ||
|
|
32e675895a | ||
|
|
786813d95d | ||
|
|
f7287c503a | ||
|
|
413574e3ee | ||
|
|
4b39becf65 | ||
|
|
15b7e3c69a | ||
|
|
7c93ea8b44 | ||
|
|
6a7cfa58f9 | ||
|
|
74b93f6eef | ||
|
|
88101613c8 | ||
|
|
599591ad3c | ||
|
|
348158a5f6 | ||
|
|
c3e410e95a | ||
|
|
42bcd5406a | ||
|
|
ba23aca631 | ||
|
|
5ef245a4bd | ||
|
|
036a60f517 | ||
|
|
9c969541a5 | ||
|
|
a52b483dd0 | ||
|
|
4e84c6bb76 | ||
|
|
0f9baf62a1 | ||
|
|
979ad523ef | ||
|
|
975c07688e | ||
|
|
67a02255b5 | ||
|
|
028ae1a660 | ||
|
|
68b1d2783d | ||
|
|
12793c350d | ||
|
|
8716ab81be | ||
|
|
c2a4e4470b | ||
|
|
f5a8ec8a0c | ||
|
|
048d92709a | ||
|
|
8dc9637e7a | ||
|
|
700bf00107 | ||
|
|
4836d42828 | ||
|
|
5762d5ef8e | ||
|
|
9f2fe0da61 | ||
|
|
d1a5771839 | ||
|
|
7663abdcde | ||
|
|
1e42e1f817 | ||
|
|
5f7ae5d32e | ||
|
|
17db402e4b | ||
|
|
f2242958a6 | ||
|
|
ea4b695b5a |
11
.github/renovate.json
vendored
11
.github/renovate.json
vendored
@@ -8,6 +8,17 @@
|
||||
],
|
||||
"mode": "full",
|
||||
"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": [
|
||||
{
|
||||
"matchManagers": ["github-actions"],
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
issue-awaiting-response:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
|
||||
2
.github/workflows/issue-closed.yml
vendored
2
.github/workflows/issue-closed.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
issue-closed:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
|
||||
14
.github/workflows/issue-experiment.yml
vendored
14
.github/workflows/issue-experiment.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
if: github.event.label.name == format('status{0} proposed', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
if: github.event.label.name == format('status{0} draft', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
if: github.event.label.name == format('status{0} candidate', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
if: github.event.label.name == format('status{0} stable', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
if: github.event.label.name == format('status{0} released', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
if: github.event.label.name == format('status{0} abandoned', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
if: github.event.label.name == format('status{0} superseded', ':')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
|
||||
2
.github/workflows/issue-needs-triage.yml
vendored
2
.github/workflows/issue-needs-triage.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
issue-needs-triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
script: |
|
||||
|
||||
14
.github/workflows/lint.yml
vendored
14
.github/workflows/lint.yml
vendored
@@ -16,25 +16,25 @@ jobs:
|
||||
go-version: [1.24.x, 1.25.x]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.1.0
|
||||
version: v2.7.2
|
||||
|
||||
lint-jsonschema:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.14
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: install check-jsonschema
|
||||
run: python -m pip install 'check-jsonschema==0.27.3'
|
||||
|
||||
4
.github/workflows/release-nightly.yml
vendored
4
.github/workflows/release-nightly.yml
vendored
@@ -9,12 +9,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
|
||||
|
||||
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -5,36 +5,40 @@ on:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
|
||||
- name: npm-login
|
||||
run: |
|
||||
npm config set '//registry.npmjs.org/:_authToken'=${{ secrets.NPM_TOKEN }}
|
||||
- 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:
|
||||
version: latest
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.x'
|
||||
cache: 'pnpm'
|
||||
package_json_file: 'website/package.json'
|
||||
run_install: 'true'
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
|
||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -18,13 +18,13 @@ jobs:
|
||||
runs-on: ${{matrix.platform}}
|
||||
steps:
|
||||
- name: Set up Go ${{matrix.go-version}}
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download Go modules
|
||||
run: go mod download
|
||||
|
||||
@@ -69,7 +69,7 @@ nfpms:
|
||||
- deb
|
||||
- rpm
|
||||
- apk
|
||||
file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}'
|
||||
file_name_template: '{{.ProjectName}}_{{.Version}}_{{.Os}}_{{.Arch}}'
|
||||
contents:
|
||||
- src: completion/bash/task.bash
|
||||
dst: /etc/bash_completion.d/task
|
||||
@@ -127,7 +127,7 @@ winget:
|
||||
repository:
|
||||
owner: go-task
|
||||
name: winget-pkgs
|
||||
branch: 'chore/task-{{.Version}}'
|
||||
branch: 'task-{{.Version}}'
|
||||
pull_request:
|
||||
enabled: true
|
||||
draft: false
|
||||
@@ -136,6 +136,8 @@ winget:
|
||||
owner: microsoft
|
||||
name: winget-pkgs
|
||||
branch: master
|
||||
body: |
|
||||
/cc @andreynering @pd93 @vmaerten
|
||||
|
||||
|
||||
npms:
|
||||
|
||||
13
.vscode/settings-sample.json
vendored
13
.vscode/settings-sample.json
vendored
@@ -1,15 +1,12 @@
|
||||
{
|
||||
"yaml.schemas": {
|
||||
"./website/static/schema.json": [
|
||||
"Taskfile.yml",
|
||||
"tmp/**/*.yml"
|
||||
"./website/src/public/schema.json": [
|
||||
"Taskfile.yml",
|
||||
"Taskfile.yaml",
|
||||
"taskfile.yml",
|
||||
"taskfile.yaml"
|
||||
]
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/versioned_docs": true,
|
||||
"**/versioned_sidesbars": true,
|
||||
"**/i18n": true
|
||||
},
|
||||
"gopls": {
|
||||
"formatting.local": "github.com/go-task"
|
||||
},
|
||||
|
||||
113
CHANGELOG.md
113
CHANGELOG.md
@@ -1,6 +1,115 @@
|
||||
# Changelog
|
||||
|
||||
## v3.45.2 - 2025-09-15
|
||||
## 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
|
||||
@@ -49,7 +158,7 @@ more timely. We have already merged a couple of longstanding PRs in our
|
||||
@pd93, @shrink, @trim21 and all the previous contributors to
|
||||
[arduino/setup-task](https://github.com/arduino/setup-task/)).
|
||||
|
||||
## v3.45.0-v3.45.1 - 2025-09-15
|
||||
## v3.45.0-v3.45.2 - 2025-09-15
|
||||
|
||||
Failed due to an issue with our release process.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<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>
|
||||
<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>
|
||||
</p>
|
||||
|
||||
<h1>Gold Sponsors</h1>
|
||||
@@ -19,7 +19,12 @@
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a target="_blank" href="https://devowl.io">
|
||||
<img src="https://devowl.io/wp-content/uploads/meta/favicon.webp" height="100px" title="devowl.io" />
|
||||
<img src="website/src/public/img/devowl.io.svg" height="100px" width="200px" 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>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -121,11 +121,15 @@ func changelog(version *semver.Version) error {
|
||||
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
|
||||
changelog = fmt.Sprintf("---\n%s\n---\n\n%s", frontmatter, changelog)
|
||||
changelogWithFrontmatter := fmt.Sprintf("---\n%s\n---\n\n%s", frontmatter, changelogWithVPre)
|
||||
|
||||
// Write the changelog to the target file
|
||||
return os.WriteFile(changelogTarget, []byte(changelog), 0o644)
|
||||
return os.WriteFile(changelogTarget, []byte(changelogWithFrontmatter), 0o644)
|
||||
}
|
||||
|
||||
func setVersionFile(fileName string, version *semver.Version) error {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
@@ -28,19 +29,34 @@ func main() {
|
||||
Color: flags.Color,
|
||||
}
|
||||
if err, ok := err.(*errors.TaskRunError); ok && flags.ExitCode {
|
||||
emitCIErrorAnnotation(err)
|
||||
l.Errf(logger.Red, "%v\n", err)
|
||||
os.Exit(err.TaskExitCode())
|
||||
}
|
||||
if err, ok := err.(errors.TaskError); ok {
|
||||
emitCIErrorAnnotation(err)
|
||||
l.Errf(logger.Red, "%v\n", err)
|
||||
os.Exit(err.Code())
|
||||
}
|
||||
emitCIErrorAnnotation(err)
|
||||
l.Errf(logger.Red, "%v\n", err)
|
||||
os.Exit(errors.CodeUnknown)
|
||||
}
|
||||
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 {
|
||||
log := &logger.Logger{
|
||||
Stdout: os.Stdout,
|
||||
@@ -156,18 +172,23 @@ func run() error {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
globals.Set("CLI_ARGS", ast.Var{Value: cliArgsPostDashQuoted})
|
||||
globals.Set("CLI_ARGS_LIST", ast.Var{Value: cliArgsPostDash})
|
||||
globals.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
||||
e.Taskfile.Vars.Merge(globals, nil)
|
||||
|
||||
specialVars := ast.NewVars()
|
||||
specialVars.Set("CLI_ARGS", ast.Var{Value: cliArgsPostDashQuoted})
|
||||
specialVars.Set("CLI_ARGS_LIST", ast.Var{Value: cliArgsPostDash})
|
||||
specialVars.Set("CLI_FORCE", ast.Var{Value: flags.Force || flags.ForceAll})
|
||||
specialVars.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
|
||||
specialVars.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
|
||||
specialVars.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
|
||||
specialVars.Set("CLI_ASSUME_YES", ast.Var{Value: flags.AssumeYes})
|
||||
e.Taskfile.Vars.ReverseMerge(specialVars, nil)
|
||||
if !flags.Watch {
|
||||
e.InterceptInterruptSignals()
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
|
||||
defer cancel()
|
||||
if err := run(ctx); err != nil {
|
||||
fmt.Println(ctx.Err())
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context) error {
|
||||
req, err := http.NewRequest("GET", "https://taskfile.dev/schema.json", nil)
|
||||
if err != nil {
|
||||
fmt.Println(1)
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
fmt.Println(2)
|
||||
return err
|
||||
}
|
||||
fmt.Println(3)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -61,13 +61,14 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
|
||||
newVar := templater.ReplaceVar(v, cache)
|
||||
// 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
|
||||
// Preserve the Sh field so it can be displayed in summary
|
||||
if !evaluateShVars && newVar.Value == nil {
|
||||
result.Set(k, ast.Var{Value: ""})
|
||||
result.Set(k, ast.Var{Value: "", Sh: newVar.Sh})
|
||||
return nil
|
||||
}
|
||||
// If the variable should not be evaluated and it is set, we can set it and return
|
||||
if !evaluateShVars {
|
||||
result.Set(k, ast.Var{Value: newVar.Value})
|
||||
result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh})
|
||||
return nil
|
||||
}
|
||||
// Now we can check for errors since we've handled all the cases when we don't want to evaluate
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# vim: set tabstop=2 shiftwidth=2 expandtab:
|
||||
|
||||
_GO_TASK_COMPLETION_LIST_OPTION='--list-all'
|
||||
TASK_CMD="${TASK_EXE:-task}"
|
||||
|
||||
function _task()
|
||||
{
|
||||
@@ -21,7 +22,7 @@ function _task()
|
||||
|
||||
# Handle special arguments of options.
|
||||
case "$prev" in
|
||||
-d|--dir)
|
||||
-d|--dir|--remote-cache-dir)
|
||||
_filedir -d
|
||||
return $?
|
||||
;;
|
||||
@@ -52,4 +53,4 @@ function _task()
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
complete -F _task task
|
||||
complete -F _task "$TASK_CMD"
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
set -l GO_TASK_PROGNAME task
|
||||
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)
|
||||
|
||||
# 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
|
||||
# Check if the global task is requested
|
||||
@@ -27,28 +54,63 @@ function __task_get_tasks --description "Prints all available tasks with their d
|
||||
end
|
||||
|
||||
# Grab names and descriptions (if any) of the tasks
|
||||
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)
|
||||
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)
|
||||
if test $output
|
||||
echo $output
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
specified.' -xa "(__task_get_tasks)"
|
||||
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 specified.' \
|
||||
-xa "(__task_get_tasks)" \
|
||||
-n "not __fish_seen_subcommand_from --"
|
||||
|
||||
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
|
||||
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'sets directory of execution'
|
||||
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 f -l force -d 'forces execution even when the task is up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -s h -l help -d 'shows Task usage'
|
||||
complete -c $GO_TASK_PROGNAME -s i -l init -d 'creates a new Taskfile.yml in the current folder'
|
||||
complete -c $GO_TASK_PROGNAME -s l -l list -d 'lists tasks with description of current Taskfile'
|
||||
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 p -l parallel -d 'executes tasks provided on command line in parallel'
|
||||
complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disables echoing'
|
||||
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 -l summary -d 'show summary about a task'
|
||||
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 v -l verbose -d 'enables verbose mode'
|
||||
complete -c $GO_TASK_PROGNAME -l version -d 'show Task version'
|
||||
complete -c $GO_TASK_PROGNAME -s w -l watch -d 'enables watch of the given task'
|
||||
# Standard flags
|
||||
complete -c $GO_TASK_PROGNAME -s a -l list-all -d 'list all tasks'
|
||||
complete -c $GO_TASK_PROGNAME -s c -l color -d 'colored output (default true)'
|
||||
complete -c $GO_TASK_PROGNAME -s C -l concurrency -d 'limit number of concurrent tasks'
|
||||
complete -c $GO_TASK_PROGNAME -l completion -d 'generate shell completion script' -xa "bash zsh fish powershell"
|
||||
complete -c $GO_TASK_PROGNAME -s d -l dir -d 'set directory of execution'
|
||||
complete -c $GO_TASK_PROGNAME -l disable-fuzzy -d 'disable fuzzy matching for task names'
|
||||
complete -c $GO_TASK_PROGNAME -s n -l dry -d 'compile and print tasks without executing'
|
||||
complete -c $GO_TASK_PROGNAME -s x -l exit-code -d 'pass-through exit code of task command'
|
||||
complete -c $GO_TASK_PROGNAME -l experiments -d 'list available experiments'
|
||||
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 -s f -l force -d 'force execution even when up-to-date'
|
||||
complete -c $GO_TASK_PROGNAME -s g -l global -d 'run global Taskfile from home directory'
|
||||
complete -c $GO_TASK_PROGNAME -s h -l help -d 'show help'
|
||||
complete -c $GO_TASK_PROGNAME -s i -l init -d 'create new Taskfile'
|
||||
complete -c $GO_TASK_PROGNAME -l insecure -d 'allow insecure Taskfile downloads'
|
||||
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'
|
||||
|
||||
@@ -5,22 +5,82 @@ Register-ArgumentCompleter -CommandName task -ScriptBlock {
|
||||
|
||||
if ($commandName.StartsWith('-')) {
|
||||
$completions = @(
|
||||
[CompletionResult]::new('--list-all ', '--list-all ', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('--color ', '--color', [CompletionResultType]::ParameterName, '--color'),
|
||||
[CompletionResult]::new('--concurrency=', '--concurrency=', [CompletionResultType]::ParameterName, 'concurrency'),
|
||||
[CompletionResult]::new('--interval=', '--interval=', [CompletionResultType]::ParameterName, 'interval'),
|
||||
[CompletionResult]::new('--output=interleaved ', '--output=interleaved', [CompletionResultType]::ParameterName, '--output='),
|
||||
[CompletionResult]::new('--output=group ', '--output=group', [CompletionResultType]::ParameterName, '--output='),
|
||||
[CompletionResult]::new('--output=prefixed ', '--output=prefixed', [CompletionResultType]::ParameterName, '--output='),
|
||||
[CompletionResult]::new('--dry ', '--dry', [CompletionResultType]::ParameterName, '--dry'),
|
||||
[CompletionResult]::new('--force ', '--force', [CompletionResultType]::ParameterName, '--force'),
|
||||
[CompletionResult]::new('--parallel ', '--parallel', [CompletionResultType]::ParameterName, '--parallel'),
|
||||
[CompletionResult]::new('--silent ', '--silent', [CompletionResultType]::ParameterName, '--silent'),
|
||||
[CompletionResult]::new('--status ', '--status', [CompletionResultType]::ParameterName, '--status'),
|
||||
[CompletionResult]::new('--verbose ', '--verbose', [CompletionResultType]::ParameterName, '--verbose'),
|
||||
[CompletionResult]::new('--watch ', '--watch', [CompletionResultType]::ParameterName, '--watch')
|
||||
# Standard flags (alphabetical order)
|
||||
[CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('--list-all', '--list-all', [CompletionResultType]::ParameterName, 'list all tasks'),
|
||||
[CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'colored output'),
|
||||
[CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'colored output'),
|
||||
[CompletionResult]::new('-C', '-C', [CompletionResultType]::ParameterName, 'limit concurrent tasks'),
|
||||
[CompletionResult]::new('--concurrency', '--concurrency', [CompletionResultType]::ParameterName, 'limit concurrent tasks'),
|
||||
[CompletionResult]::new('--completion', '--completion', [CompletionResultType]::ParameterName, 'generate shell completion'),
|
||||
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'set directory'),
|
||||
[CompletionResult]::new('--dir', '--dir', [CompletionResultType]::ParameterName, 'set directory'),
|
||||
[CompletionResult]::new('--disable-fuzzy', '--disable-fuzzy', [CompletionResultType]::ParameterName, 'disable fuzzy matching'),
|
||||
[CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'dry run'),
|
||||
[CompletionResult]::new('--dry', '--dry', [CompletionResultType]::ParameterName, 'dry run'),
|
||||
[CompletionResult]::new('-x', '-x', [CompletionResultType]::ParameterName, 'pass-through exit code'),
|
||||
[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) }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +1,147 @@
|
||||
#compdef task
|
||||
compdef _task task
|
||||
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}"
|
||||
|
||||
# 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
|
||||
function __task_list() {
|
||||
local -a scripts cmd
|
||||
local -i enabled=0
|
||||
local taskfile item task desc
|
||||
|
||||
cmd=(task)
|
||||
cmd=($TASK_CMD)
|
||||
taskfile=${(Qv)opt_args[(i)-t|--taskfile]}
|
||||
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
|
||||
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
|
||||
|
||||
# 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
|
||||
fi
|
||||
|
||||
(( enabled )) || return 0
|
||||
|
||||
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:]]*}"
|
||||
desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
|
||||
if [[ "$show_desc" == "true" ]]; then
|
||||
local desc="${item##[^[:space:]]##[[:space:]]##}"
|
||||
scripts+=( "${task//:/\\:}:$desc" )
|
||||
else
|
||||
scripts+=( "$task" )
|
||||
fi
|
||||
done
|
||||
_describe 'Task to run' scripts
|
||||
|
||||
if [[ "$show_desc" == "true" ]]; then
|
||||
_describe 'Task to run' scripts
|
||||
else
|
||||
compadd -Q -a scripts
|
||||
fi
|
||||
}
|
||||
|
||||
_task() {
|
||||
_arguments \
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: ' \
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]' \
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]' \
|
||||
'(-c --color)'{-c,--color}'[colored output]' \
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs' \
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]' \
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)' \
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: ' \
|
||||
'(--output-group-end)--output-group-end[message template after grouped output]:template text: ' \
|
||||
'(-s --silent)'{-s,--silent}'[disable echoing]' \
|
||||
'(--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]' \
|
||||
+ '(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]' \
|
||||
'*: :__task_list'
|
||||
local -a standard_args operation_args
|
||||
|
||||
standard_args=(
|
||||
'(-C --concurrency)'{-C,--concurrency}'[limit number of concurrent tasks]: '
|
||||
'(-p --parallel)'{-p,--parallel}'[run command-line tasks in parallel]'
|
||||
'(-F --failfast)'{-F,--failfast}'[when running tasks in parallel, stop all tasks if one fails]'
|
||||
'(-f --force)'{-f,--force}'[run even if task is up-to-date]'
|
||||
'(-c --color)'{-c,--color}'[colored output]'
|
||||
'(--completion)--completion[generate shell completion script]:shell:(bash zsh fish powershell)'
|
||||
'(-d --dir)'{-d,--dir}'[dir to run in]:execution dir:_dirs'
|
||||
'(--disable-fuzzy)--disable-fuzzy[disable fuzzy matching for task names]'
|
||||
'(-n --dry)'{-n,--dry}'[compiles and prints tasks without executing]'
|
||||
'(--dry)--dry[dry-run mode, compile and print tasks only]'
|
||||
'(-x --exit-code)'{-x,--exit-code}'[pass-through exit code of task command]'
|
||||
'(--experiments)--experiments[list available experiments]'
|
||||
'(-g --global)'{-g,--global}'[run global Taskfile from home directory]'
|
||||
'(--insecure)--insecure[allow insecure Taskfile downloads]'
|
||||
'(-I --interval)'{-I,--interval}'[interval to watch for changes]:duration: '
|
||||
'(-j --json)'{-j,--json}'[format task list as JSON]'
|
||||
'(--nested)--nested[nest namespaces when listing as JSON]'
|
||||
'(--no-status)--no-status[ignore status when listing as JSON]'
|
||||
'(-o --output)'{-o,--output}'[set output style]:style:(interleaved group prefixed)'
|
||||
'(--output-group-begin)--output-group-begin[message template before grouped output]:template text: '
|
||||
'(--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
|
||||
|
||||
@@ -5,15 +5,12 @@ import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
var typeErrorRegex = regexp.MustCompile(`line \d+: (.*)`)
|
||||
|
||||
type (
|
||||
TaskfileDecodeError struct {
|
||||
Message string
|
||||
@@ -53,10 +50,10 @@ func (err *TaskfileDecodeError) Error() string {
|
||||
if len(te.Errors) > 1 {
|
||||
fmt.Fprintln(buf, color.RedString("errs:"))
|
||||
for _, message := range te.Errors {
|
||||
fmt.Fprintln(buf, color.RedString("- %s", extractTypeErrorMessage(message)))
|
||||
fmt.Fprintln(buf, color.RedString("- %s", message.Err.Error()))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", extractTypeErrorMessage(te.Errors[0])))
|
||||
fmt.Fprintln(buf, color.RedString("err: %s", te.Errors[0].Err.Error()))
|
||||
}
|
||||
} else {
|
||||
// Otherwise print the error message normally
|
||||
@@ -128,11 +125,3 @@ func (err *TaskfileDecodeError) WithFileInfo(location string, snippet string) *T
|
||||
err.Snippet = snippet
|
||||
return err
|
||||
}
|
||||
|
||||
func extractTypeErrorMessage(message string) string {
|
||||
matches := typeErrorRegex.FindStringSubmatch(message)
|
||||
if len(matches) == 2 {
|
||||
return matches[1]
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ func (err *TaskRunError) TaskExitCode() int {
|
||||
return err.Code()
|
||||
}
|
||||
|
||||
func (err *TaskRunError) Unwrap() error {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
// TaskInternalError when the user attempts to invoke a task that is internal.
|
||||
type TaskInternalError struct {
|
||||
TaskName string
|
||||
|
||||
@@ -11,14 +11,18 @@ import (
|
||||
// TaskfileNotFoundError is returned when no appropriate Taskfile is found when
|
||||
// searching the filesystem.
|
||||
type TaskfileNotFoundError struct {
|
||||
URI string
|
||||
Walk bool
|
||||
URI string
|
||||
Walk bool
|
||||
AskInit bool
|
||||
}
|
||||
|
||||
func (err TaskfileNotFoundError) Error() string {
|
||||
var walkText string
|
||||
if err.Walk {
|
||||
walkText = " (or any of the parent directories)"
|
||||
walkText = " (or any of the parent directories)."
|
||||
}
|
||||
if err.AskInit {
|
||||
walkText += " Run `task --init` to create a new Taskfile."
|
||||
}
|
||||
return fmt.Sprintf(`task: No Taskfile found at %q%s`, err.URI, walkText)
|
||||
}
|
||||
|
||||
66
executor.go
66
executor.go
@@ -7,7 +7,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/sajari/fuzzy"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
@@ -34,11 +34,14 @@ type (
|
||||
Insecure bool
|
||||
Download bool
|
||||
Offline bool
|
||||
TrustedHosts []string
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
RemoteCacheDir string
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
DisableFuzzy bool
|
||||
AssumeYes bool
|
||||
AssumeTerm bool // Used for testing
|
||||
Dry bool
|
||||
@@ -47,6 +50,7 @@ type (
|
||||
Color bool
|
||||
Concurrency int
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
|
||||
// I/O
|
||||
Stdin io.Reader
|
||||
@@ -63,14 +67,15 @@ type (
|
||||
UserWorkingDir string
|
||||
EnableVersionCheck bool
|
||||
|
||||
fuzzyModel *fuzzy.Model
|
||||
fuzzyModel *fuzzy.Model
|
||||
fuzzyModelOnce sync.Once
|
||||
|
||||
concurrencySemaphore chan struct{}
|
||||
taskCallCount map[string]*int32
|
||||
mkdirMutexMap map[string]*sync.Mutex
|
||||
executionHashes map[string]context.Context
|
||||
executionHashesMutex sync.Mutex
|
||||
watchedDirs *xsync.MapOf[string, bool]
|
||||
watchedDirs *xsync.Map[string, bool]
|
||||
}
|
||||
TempDir struct {
|
||||
Remote string
|
||||
@@ -225,6 +230,20 @@ func (o *offlineOption) ApplyToExecutor(e *Executor) {
|
||||
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
|
||||
// default, the timeout is set to 10 seconds.
|
||||
func WithTimeout(timeout time.Duration) ExecutorOption {
|
||||
@@ -240,7 +259,7 @@ func (o *timeoutOption) ApplyToExecutor(e *Executor) {
|
||||
}
|
||||
|
||||
// WithCacheExpiryDuration sets the duration after which the cache is considered
|
||||
// expired. By default, the cache is considered expired after 24 hours.
|
||||
// expired. By default, the cache is 0 (disabled).
|
||||
func WithCacheExpiryDuration(duration time.Duration) ExecutorOption {
|
||||
return &cacheExpiryDurationOption{duration: duration}
|
||||
}
|
||||
@@ -253,6 +272,19 @@ func (o *cacheExpiryDurationOption) ApplyToExecutor(r *Executor) {
|
||||
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
|
||||
// for changes to the fingerprint of the tasks that are run. When changes are
|
||||
// detected, a new task run is triggered.
|
||||
@@ -296,6 +328,19 @@ func (o *silentOption) ApplyToExecutor(e *Executor) {
|
||||
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.
|
||||
func WithAssumeYes(assumeYes bool) ExecutorOption {
|
||||
return &assumeYesOption{assumeYes}
|
||||
@@ -502,3 +547,16 @@ type versionCheckOption struct {
|
||||
func (o *versionCheckOption) ApplyToExecutor(e *Executor) {
|
||||
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
|
||||
}
|
||||
|
||||
109
executor_test.go
109
executor_test.go
@@ -143,12 +143,12 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
t.Helper()
|
||||
f := func(t *testing.T) {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
var buffer SyncBuffer
|
||||
|
||||
opts := append(
|
||||
tt.executorOpts,
|
||||
task.WithStdout(&buf),
|
||||
task.WithStderr(&buf),
|
||||
task.WithStdout(&buffer),
|
||||
task.WithStderr(&buffer),
|
||||
)
|
||||
|
||||
// If the test has input, create a reader for it and add it to the
|
||||
@@ -171,7 +171,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
if err := e.Setup(); tt.wantSetupError {
|
||||
require.Error(t, err)
|
||||
tt.writeFixtureErrSetup(t, g, err)
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
tt.writeFixtureBuffer(t, g, buffer.buf)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
@@ -192,7 +192,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
if err := e.Run(ctx, call); tt.wantRunError {
|
||||
require.Error(t, err)
|
||||
tt.writeFixtureErrRun(t, g, err)
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
tt.writeFixtureBuffer(t, g, buffer.buf)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
@@ -205,7 +205,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
tt.writeFixtureBuffer(t, g, buf)
|
||||
tt.writeFixtureBuffer(t, g, buffer.buf)
|
||||
}
|
||||
|
||||
// Run the test (with a name if it has one)
|
||||
@@ -263,6 +263,23 @@ func TestVars(t *testing.T) {
|
||||
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) {
|
||||
@@ -621,6 +638,30 @@ 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) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -665,6 +706,15 @@ func TestLabel(t *testing.T) {
|
||||
),
|
||||
WithTask("foo"),
|
||||
)
|
||||
|
||||
NewExecutorTest(t,
|
||||
WithName("label in error"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/label_error"),
|
||||
),
|
||||
WithTask("foo"),
|
||||
WithRunError(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestPromptInSummary(t *testing.T) {
|
||||
@@ -987,3 +1037,50 @@ func TestIncludeChecksum(t *testing.T) {
|
||||
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(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,14 +33,12 @@ var xList []Experiment
|
||||
|
||||
func Parse(dir string) {
|
||||
config, _ := taskrc.GetConfig(dir)
|
||||
|
||||
ParseWithConfig(dir, config)
|
||||
}
|
||||
|
||||
func ParseWithConfig(dir string, config *ast.TaskRC) {
|
||||
// Read any .env files
|
||||
readDotEnv(dir)
|
||||
|
||||
// Initialize the experiments
|
||||
GentleForce = New("GENTLE_FORCE", config, 1)
|
||||
RemoteTaskfiles = New("REMOTE_TASKFILES", config, 1)
|
||||
|
||||
81
go.mod
81
go.mod
@@ -12,55 +12,82 @@ require (
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/go-git/go-billy/v5 v5.6.2
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0
|
||||
github.com/go-task/template v0.2.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/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0
|
||||
github.com/sajari/fuzzy v1.0.0
|
||||
github.com/sebdah/goldie/v2 v2.7.1
|
||||
github.com/sebdah/goldie/v2 v2.8.0
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/term v0.35.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20250807215248-5a1a658912aa
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/term v0.38.0
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20251109230715-65adef8e2c5b
|
||||
mvdan.cc/sh/v3 v3.12.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
cloud.google.com/go v0.110.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/iam v0.13.0 // indirect
|
||||
cloud.google.com/go/storage v1.29.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 // 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/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/go-cmp v0.7.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/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/mitchellh/go-homedir v1.1.0 // 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/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/u-root/u-root v0.14.1-0.20250807200646-5e7721023dc7 // indirect
|
||||
github.com/u-root/u-root v0.15.1-0.20251014130006-62f7144b33da // indirect
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/net v0.47.0 // 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
|
||||
)
|
||||
|
||||
292
go.sum
292
go.sum
@@ -1,32 +1,70 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
|
||||
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/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/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/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
||||
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
|
||||
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/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -36,48 +74,70 @@ github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucV
|
||||
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
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/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
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/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviBE=
|
||||
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/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/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/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/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/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
@@ -89,107 +149,145 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
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/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/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0=
|
||||
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/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY=
|
||||
github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo=
|
||||
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
|
||||
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc=
|
||||
github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.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/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/u-root/u-root v0.14.1-0.20250807200646-5e7721023dc7 h1:ax+jBy7xFhh+Ka0IGLmH5mft+YDuqvzEjSgWuAP0nsM=
|
||||
github.com/u-root/u-root v0.14.1-0.20250807200646-5e7721023dc7/go.mod h1:/0Qr7qJeDwWxoKku2xKQ4Szc+SwBE3g9VE8jNiamsmc=
|
||||
github.com/u-root/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/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/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/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
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-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 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-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.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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20250807215248-5a1a658912aa h1:sRmA9AmA5+9CbK6a7N52q9W9jAeoBy1EJ7cncm+SLxw=
|
||||
mvdan.cc/sh/moreinterp v0.0.0-20250807215248-5a1a658912aa/go.mod h1:Of9PCedbLDYT8b3EyiYG64rNnx5nOp27OLCVdDrjJyo=
|
||||
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/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
|
||||
|
||||
25
init.go
25
init.go
@@ -6,9 +6,10 @@ import (
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
)
|
||||
|
||||
const defaultTaskFilename = "Taskfile.yml"
|
||||
const defaultFilename = "Taskfile.yml"
|
||||
|
||||
//go:embed taskfile/templates/default.yml
|
||||
var DefaultTaskfile string
|
||||
@@ -20,22 +21,30 @@ var DefaultTaskfile string
|
||||
//
|
||||
// The final file path is always returned and may be different from the input path.
|
||||
func InitTaskfile(path string) (string, error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err == nil && !fi.IsDir() {
|
||||
info, err := os.Stat(path)
|
||||
if err == nil && !info.IsDir() {
|
||||
return path, errors.TaskfileAlreadyExistsError{}
|
||||
}
|
||||
|
||||
if fi != nil && fi.IsDir() {
|
||||
path = filepathext.SmartJoin(path, defaultTaskFilename)
|
||||
// path was a directory, so check if Taskfile.yml exists in it
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
if info != nil && info.IsDir() {
|
||||
// path was a directory, check if there is a Taskfile already
|
||||
if hasDefaultTaskfile(path) {
|
||||
return path, errors.TaskfileAlreadyExistsError{}
|
||||
}
|
||||
path = filepathext.SmartJoin(path, defaultFilename)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, []byte(DefaultTaskfile), 0o644); err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func execHandlers() (handlers []func(next interp.ExecHandlerFunc) interp.ExecHan
|
||||
if useGoCoreUtils {
|
||||
handlers = append(handlers, coreutils.ExecHandler)
|
||||
}
|
||||
return
|
||||
return handlers
|
||||
}
|
||||
|
||||
func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||
|
||||
@@ -5,13 +5,16 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/go-task/task/v3"
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/experiments"
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/sort"
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
"github.com/go-task/task/v3/taskrc"
|
||||
@@ -58,6 +61,7 @@ var (
|
||||
Watch bool
|
||||
Verbose bool
|
||||
Silent bool
|
||||
DisableFuzzy bool
|
||||
AssumeYes bool
|
||||
Dry bool
|
||||
Summary bool
|
||||
@@ -69,13 +73,16 @@ var (
|
||||
Output ast.Output
|
||||
Color bool
|
||||
Interval time.Duration
|
||||
Failfast bool
|
||||
Global bool
|
||||
Experiments bool
|
||||
Download bool
|
||||
Offline bool
|
||||
TrustedHosts []string
|
||||
ClearCache bool
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
RemoteCacheDir string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -123,6 +130,7 @@ func init() {
|
||||
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(&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(&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.")
|
||||
@@ -134,9 +142,10 @@ func init() {
|
||||
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.BoolVar(&Output.Group.ErrorOnly, "output-group-error-only", false, "Swallow output from successful tasks.")
|
||||
pflag.BoolVarP(&Color, "color", "c", true, "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable.")
|
||||
pflag.BoolVarP(&Color, "color", "c", 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.IntVarP(&Concurrency, "concurrency", "C", getConfig(config, func() *int { return config.Concurrency }, 0), "Limit number of tasks to run concurrently.")
|
||||
pflag.DurationVarP(&Interval, "interval", "I", 0, "Interval to watch for changes.")
|
||||
pflag.BoolVarP(&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.BoolVar(&Experiments, "experiments", false, "Lists all the available experiments and whether or not they are enabled.")
|
||||
|
||||
@@ -152,11 +161,36 @@ func init() {
|
||||
if experiments.RemoteTaskfiles.Enabled() {
|
||||
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.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", getConfig(config, func() *time.Duration { return config.Remote.Timeout }, time.Second*10), "Timeout for downloading remote Taskfiles.")
|
||||
pflag.BoolVar(&ClearCache, "clear-cache", false, "Clear the remote cache.")
|
||||
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, func() *time.Duration { return config.Remote.Timeout }, 0), "Expiry duration for cached remote Taskfiles.")
|
||||
pflag.DurationVar(&CacheExpiryDuration, "expiry", getConfig(config, func() *time.Duration { return config.Remote.CacheExpiry }, 0), "Expiry duration for cached remote Taskfiles.")
|
||||
pflag.StringVar(&RemoteCacheDir, "remote-cache-dir", getConfig(config, func() *string { return config.Remote.CacheDir }, env.GetTaskEnv("REMOTE_DIR")), "Directory to cache remote Taskfiles.")
|
||||
}
|
||||
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 {
|
||||
@@ -238,11 +272,14 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
task.WithInsecure(Insecure),
|
||||
task.WithDownload(Download),
|
||||
task.WithOffline(Offline),
|
||||
task.WithTrustedHosts(TrustedHosts),
|
||||
task.WithTimeout(Timeout),
|
||||
task.WithCacheExpiryDuration(CacheExpiryDuration),
|
||||
task.WithRemoteCacheDir(RemoteCacheDir),
|
||||
task.WithWatch(Watch),
|
||||
task.WithVerbose(Verbose),
|
||||
task.WithSilent(Silent),
|
||||
task.WithDisableFuzzy(DisableFuzzy),
|
||||
task.WithAssumeYes(AssumeYes),
|
||||
task.WithDry(Dry || Status),
|
||||
task.WithSummary(Summary),
|
||||
@@ -253,6 +290,7 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
task.WithOutputStyle(Output),
|
||||
task.WithTaskSorter(sorter),
|
||||
task.WithVersionCheck(true),
|
||||
task.WithFailfast(Failfast),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,5 +20,5 @@ func Name(t *ast.Task) (string, error) {
|
||||
|
||||
func Hash(t *ast.Task) (string, error) {
|
||||
h, err := hashstructure.Hash(t, hashstructure.FormatV2, nil)
|
||||
return fmt.Sprintf("%s:%d", t.Task, h), err
|
||||
return fmt.Sprintf("%s:%s:%d", t.Location.Taskfile, t.LocalName(), h), err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package logger
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -96,10 +95,6 @@ func BrightRed() PrintFunc {
|
||||
}
|
||||
|
||||
func envColor(name string, defaultColor color.Attribute) []color.Attribute {
|
||||
if os.Getenv("FORCE_COLOR") != "" {
|
||||
color.NoColor = false
|
||||
}
|
||||
|
||||
// Fetch the environment variable
|
||||
override := env.GetTaskEnv(name)
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ func (g Group) WrapWriter(stdOut, _ io.Writer, _ string, cache *templater.Cache)
|
||||
if g.ErrorOnly && err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gw.close()
|
||||
}
|
||||
}
|
||||
@@ -40,14 +39,22 @@ func (gw *groupWriter) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (gw *groupWriter) close() error {
|
||||
if gw.buff.Len() == 0 {
|
||||
// don't print begin/end messages if there's no buffered entries
|
||||
switch {
|
||||
case gw.buff.Len() == 0:
|
||||
return nil
|
||||
}
|
||||
if _, err := io.WriteString(gw.writer, gw.begin); err != nil {
|
||||
case gw.begin == "" && gw.end == "":
|
||||
_, err := io.Copy(gw.writer, &gw.buff)
|
||||
return err
|
||||
default:
|
||||
_, err := io.Copy(gw.writer, gw.combinedBuff())
|
||||
return err
|
||||
}
|
||||
gw.buff.WriteString(gw.end)
|
||||
_, err := io.Copy(gw.writer, &gw.buff)
|
||||
return err
|
||||
}
|
||||
|
||||
func (gw *groupWriter) combinedBuff() io.Reader {
|
||||
var b bytes.Buffer
|
||||
_, _ = b.WriteString(gw.begin)
|
||||
_, _ = io.Copy(&b, &gw.buff)
|
||||
_, _ = b.WriteString(gw.end)
|
||||
return &b
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package summary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
@@ -29,6 +31,9 @@ func PrintSpaceBetweenSummaries(l *logger.Logger, i int) {
|
||||
func PrintTask(l *logger.Logger, t *ast.Task) {
|
||||
printTaskName(l, t)
|
||||
printTaskDescribingText(t, l)
|
||||
printTaskVars(l, t)
|
||||
printTaskEnv(l, t)
|
||||
printTaskRequires(l, t)
|
||||
printTaskDependencies(l, t)
|
||||
printTaskAliases(l, t)
|
||||
printTaskCommands(l, t)
|
||||
@@ -118,3 +123,168 @@ 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]
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/google/uuid"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.45.2
|
||||
3.46.2
|
||||
|
||||
22
setup.go
22
setup.go
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
"github.com/go-task/task/v3/internal/fsext"
|
||||
"github.com/go-task/task/v3/internal/logger"
|
||||
"github.com/go-task/task/v3/internal/output"
|
||||
"github.com/go-task/task/v3/internal/version"
|
||||
@@ -35,7 +36,6 @@ func (e *Executor) Setup() error {
|
||||
if err := e.readTaskfile(node); err != nil {
|
||||
return err
|
||||
}
|
||||
e.setupFuzzyModel()
|
||||
e.setupStdFiles()
|
||||
if err := e.setupOutput(); err != nil {
|
||||
return err
|
||||
@@ -56,6 +56,13 @@ func (e *Executor) Setup() error {
|
||||
|
||||
func (e *Executor) getRootNode() (taskfile.Node, error) {
|
||||
node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.TaskfileNotFoundError{
|
||||
URI: fsext.DefaultDir(e.Entrypoint, e.Dir),
|
||||
Walk: true,
|
||||
AskInit: true,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -76,6 +83,7 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
|
||||
taskfile.WithInsecure(e.Insecure),
|
||||
taskfile.WithDownload(e.Download),
|
||||
taskfile.WithOffline(e.Offline),
|
||||
taskfile.WithTrustedHosts(e.TrustedHosts),
|
||||
taskfile.WithTempDir(e.TempDir.Remote),
|
||||
taskfile.WithCacheExpiryDuration(e.CacheExpiryDuration),
|
||||
taskfile.WithDebugFunc(debugFunc),
|
||||
@@ -145,16 +153,16 @@ func (e *Executor) setupTempDir() error {
|
||||
}
|
||||
}
|
||||
|
||||
remoteDir := env.GetTaskEnv("REMOTE_DIR")
|
||||
if remoteDir != "" {
|
||||
if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") {
|
||||
remoteTempDir, err := execext.ExpandLiteral(remoteDir)
|
||||
// RemoteCacheDir from taskrc/env can override the remote cache directory
|
||||
if e.RemoteCacheDir != "" {
|
||||
if filepath.IsAbs(e.RemoteCacheDir) || strings.HasPrefix(e.RemoteCacheDir, "~") {
|
||||
remoteCacheDir, err := execext.ExpandLiteral(e.RemoteCacheDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.TempDir.Remote = remoteTempDir
|
||||
e.TempDir.Remote = remoteCacheDir
|
||||
} else {
|
||||
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task")
|
||||
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, e.RemoteCacheDir)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
57
task.go
57
task.go
@@ -78,9 +78,11 @@ func (e *Executor) Run(ctx context.Context, calls ...*Call) error {
|
||||
return err
|
||||
}
|
||||
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
g := &errgroup.Group{}
|
||||
if e.Failfast {
|
||||
g, ctx = errgroup.WithContext(ctx)
|
||||
}
|
||||
for _, c := range regularCalls {
|
||||
c := c
|
||||
if e.Parallel {
|
||||
g.Go(func() error { return e.RunTask(ctx, c) })
|
||||
} else {
|
||||
@@ -113,7 +115,7 @@ func (e *Executor) splitRegularAndWatchCalls(calls ...*Call) (regularCalls []*Ca
|
||||
regularCalls = append(regularCalls, c)
|
||||
}
|
||||
}
|
||||
return
|
||||
return regularCalls, watchCalls, err
|
||||
}
|
||||
|
||||
// RunTask runs a task by its name
|
||||
@@ -150,7 +152,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
release := e.acquireConcurrencyLimit()
|
||||
defer release()
|
||||
|
||||
return e.startExecution(ctx, t, func(ctx context.Context) error {
|
||||
if err = e.startExecution(ctx, t, func(ctx context.Context) error {
|
||||
e.Logger.VerboseErrf(logger.Magenta, "task: %q started\n", call.Task)
|
||||
if err := e.runDeps(ctx, t); err != nil {
|
||||
return err
|
||||
@@ -210,7 +212,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
|
||||
for i := range t.Cmds {
|
||||
if t.Cmds[i].Defer {
|
||||
defer e.runDeferred(t, call, i, &deferredExitCode)
|
||||
defer e.runDeferred(t, call, i, t.Vars, &deferredExitCode)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -228,16 +230,16 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
deferredExitCode = uint8(exitCode)
|
||||
}
|
||||
|
||||
if call.Indirect {
|
||||
return err
|
||||
}
|
||||
|
||||
return &errors.TaskRunError{TaskName: t.Task, Err: err}
|
||||
return err
|
||||
}
|
||||
}
|
||||
e.Logger.VerboseErrf(logger.Magenta, "task: %q finished\n", call.Task)
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return &errors.TaskRunError{TaskName: t.Name(), Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Executor) mkdir(t *ast.Task) error {
|
||||
@@ -258,13 +260,15 @@ func (e *Executor) mkdir(t *ast.Task) error {
|
||||
}
|
||||
|
||||
func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
g := &errgroup.Group{}
|
||||
if e.Failfast || t.Failfast {
|
||||
g, ctx = errgroup.WithContext(ctx)
|
||||
}
|
||||
|
||||
reacquire := e.releaseConcurrencyLimit()
|
||||
defer reacquire()
|
||||
|
||||
for _, d := range t.Deps {
|
||||
d := d
|
||||
g.Go(func() error {
|
||||
err := e.RunTask(ctx, &Call{Task: d.Task, Vars: d.Vars, Silent: d.Silent, Indirect: true})
|
||||
if err != nil {
|
||||
@@ -277,17 +281,11 @@ func (e *Executor) runDeps(ctx context.Context, t *ast.Task) error {
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, deferredExitCode *uint8) {
|
||||
func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, deferredExitCode *uint8) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
origTask, err := e.GetTask(call)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := t.Cmds[i]
|
||||
vars, _ := e.Compiler.GetVariables(origTask, call)
|
||||
cache := &templater.Cache{Vars: vars}
|
||||
extra := map[string]any{}
|
||||
|
||||
@@ -313,10 +311,12 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in
|
||||
defer reacquire()
|
||||
|
||||
err := e.RunTask(ctx, &Call{Task: cmd.Task, Vars: cmd.Vars, Silent: cmd.Silent, Indirect: true})
|
||||
if err != nil {
|
||||
return err
|
||||
var exitCode interp.ExitStatus
|
||||
if errors.As(err, &exitCode) && cmd.IgnoreError {
|
||||
e.Logger.VerboseErrf(logger.Yellow, "task: [%s] task error ignored: %v\n", t.Name(), err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
case cmd.Cmd != "":
|
||||
if !shouldRunOnCurrentPlatform(cmd.Platforms) {
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: [%s] %s not for current platform - ignored\n", t.Name(), cmd.Cmd)
|
||||
@@ -372,7 +372,7 @@ func (e *Executor) startExecution(ctx context.Context, t *ast.Task, execute func
|
||||
return err
|
||||
}
|
||||
|
||||
if h == "" {
|
||||
if h == "" || t.Watch {
|
||||
return execute(ctx)
|
||||
}
|
||||
|
||||
@@ -458,8 +458,11 @@ func (e *Executor) GetTask(call *Call) (*ast.Task, error) {
|
||||
// If we found no tasks
|
||||
if len(aliasedTasks) == 0 {
|
||||
didYouMean := ""
|
||||
if e.fuzzyModel != nil {
|
||||
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
||||
if !e.DisableFuzzy {
|
||||
e.fuzzyModelOnce.Do(e.setupFuzzyModel)
|
||||
if e.fuzzyModel != nil {
|
||||
didYouMean = e.fuzzyModel.SpellCheck(call.Task)
|
||||
}
|
||||
}
|
||||
return nil, &errors.TaskNotFoundError{
|
||||
TaskName: call.Task,
|
||||
@@ -498,7 +501,7 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) ([]*ast.Task, error) {
|
||||
// Compile the list of tasks
|
||||
for i := range tasks {
|
||||
g.Go(func() error {
|
||||
compiledTask, err := e.FastCompiledTask(&Call{Task: tasks[i].Task})
|
||||
compiledTask, err := e.CompiledTaskForTaskList(&Call{Task: tasks[i].Task})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
54
task_test.go
54
task_test.go
@@ -9,6 +9,7 @@ import (
|
||||
rand "math/rand/v2"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -569,7 +570,9 @@ func TestCyclicDep(t *testing.T) {
|
||||
task.WithStderr(io.Discard),
|
||||
)
|
||||
require.NoError(t, e.Setup())
|
||||
assert.IsType(t, &errors.TaskCalledTooManyTimesError{}, e.Run(t.Context(), &task.Call{Task: "task-1"}))
|
||||
err := e.Run(t.Context(), &task.Call{Task: "task-1"})
|
||||
var taskCalledTooManyTimesError *errors.TaskCalledTooManyTimesError
|
||||
assert.ErrorAs(t, err, &taskCalledTooManyTimesError)
|
||||
}
|
||||
|
||||
func TestTaskVersion(t *testing.T) {
|
||||
@@ -784,6 +787,11 @@ func TestIncludesRemote(t *testing.T) {
|
||||
|
||||
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 {
|
||||
name string
|
||||
executor *task.Executor
|
||||
@@ -823,6 +831,23 @@ func TestIncludesRemote(t *testing.T) {
|
||||
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 {
|
||||
@@ -1052,7 +1077,7 @@ func TestIncludesOptionalImplicitFalse(t *testing.T) {
|
||||
const dir = "testdata/includes_optional_implicit_false"
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
message := "stat %s/%s/TaskfileOptional.yml: no such file or directory"
|
||||
message := "task: No Taskfile found at \"%s/%s/TaskfileOptional.yml\""
|
||||
expected := fmt.Sprintf(message, wd, dir)
|
||||
|
||||
e := task.NewExecutor(
|
||||
@@ -1072,7 +1097,7 @@ func TestIncludesOptionalExplicitFalse(t *testing.T) {
|
||||
const dir = "testdata/includes_optional_explicit_false"
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
message := "stat %s/%s/TaskfileOptional.yml: no such file or directory"
|
||||
message := "task: No Taskfile found at \"%s/%s/TaskfileOptional.yml\""
|
||||
expected := fmt.Sprintf(message, wd, dir)
|
||||
|
||||
e := task.NewExecutor(
|
||||
@@ -1849,6 +1874,29 @@ func TestRunOnceSharedDeps(t *testing.T) {
|
||||
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) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
@@ -93,6 +93,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
c.Vars = cmdStruct.Vars
|
||||
c.For = cmdStruct.For
|
||||
c.Silent = cmdStruct.Silent
|
||||
c.IgnoreError = cmdStruct.IgnoreError
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"iter"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/goext"
|
||||
|
||||
@@ -3,7 +3,7 @@ package ast
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// Task represents a task
|
||||
type Task struct {
|
||||
Task string
|
||||
Task string `hash:"ignore"`
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Label string
|
||||
@@ -36,18 +36,19 @@ type Task struct {
|
||||
Interactive bool
|
||||
Internal bool
|
||||
Method string
|
||||
Prefix string
|
||||
Prefix string `hash:"ignore"`
|
||||
IgnoreError bool
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
Watch bool
|
||||
Location *Location
|
||||
Failfast bool
|
||||
// Populated during merging
|
||||
Namespace string
|
||||
Namespace string `hash:"ignore"`
|
||||
IncludeVars *Vars
|
||||
IncludedTaskfileVars *Vars
|
||||
|
||||
FullName string
|
||||
FullName string `hash:"ignore"`
|
||||
}
|
||||
|
||||
func (t *Task) Name() string {
|
||||
@@ -143,6 +144,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
Platforms []*Platform
|
||||
Requires *Requires
|
||||
Watch bool
|
||||
Failfast bool
|
||||
}
|
||||
if err := node.Decode(&task); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
@@ -181,6 +183,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
t.Platforms = task.Platforms
|
||||
t.Requires = task.Requires
|
||||
t.Watch = task.Watch
|
||||
t.Failfast = task.Failfast
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -226,6 +229,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
Requires: t.Requires.DeepCopy(),
|
||||
Namespace: t.Namespace,
|
||||
FullName: t.FullName,
|
||||
Failfast: t.Failfast,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"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 {
|
||||
if strings.HasPrefix(taskName, NamespaceSeparator) {
|
||||
return strings.TrimPrefix(taskName, NamespaceSeparator)
|
||||
if after, ok := strings.CutPrefix(taskName, NamespaceSeparator); ok {
|
||||
return after
|
||||
}
|
||||
return fmt.Sprintf("%s%s%s", namespace, NamespaceSeparator, taskName)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/elliotchance/orderedmap/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/deepcopy"
|
||||
@@ -113,12 +113,12 @@ func (vars *Vars) ToCacheMap() (m map[string]any) {
|
||||
m[k] = v.Value
|
||||
}
|
||||
}
|
||||
return
|
||||
return m
|
||||
}
|
||||
|
||||
// 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
|
||||
// directory is set set to the value of the include parameter.
|
||||
// directory is set to the value of the include parameter.
|
||||
func (vars *Vars) Merge(other *Vars, include *Include) {
|
||||
if vars == nil || vars.om == nil || other == nil {
|
||||
return
|
||||
@@ -133,6 +133,35 @@ 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 {
|
||||
if vs == nil {
|
||||
return nil
|
||||
|
||||
@@ -72,6 +72,16 @@ func NewNode(
|
||||
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) {
|
||||
u, err := giturls.Parse(uri)
|
||||
if u == nil {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"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/filepathext"
|
||||
"github.com/go-task/task/v3/internal/fsext"
|
||||
@@ -19,8 +19,11 @@ type FileNode struct {
|
||||
|
||||
func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
|
||||
// Find the entrypoint file
|
||||
resolvedEntrypoint, err := fsext.Search(entrypoint, dir, defaultTaskfiles)
|
||||
resolvedEntrypoint, err := fsext.Search(entrypoint, dir, DefaultTaskfiles)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, errors.TaskfileNotFoundError{URI: entrypoint, Walk: false}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -51,10 +54,7 @@ func (node *FileNode) Read() ([]byte, error) {
|
||||
|
||||
func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) {
|
||||
// If the file is remote, we don't need to resolve the path
|
||||
if strings.Contains(entrypoint, "://") {
|
||||
return entrypoint, nil
|
||||
}
|
||||
if strings.HasPrefix(entrypoint, "git") {
|
||||
if isRemoteEntrypoint(entrypoint) {
|
||||
return entrypoint, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,14 @@ package taskfile
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
giturls "github.com/chainguard-dev/git-urls"
|
||||
"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/hashicorp/go-getter"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
@@ -28,6 +26,36 @@ type GitNode struct {
|
||||
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(
|
||||
entrypoint string,
|
||||
dir string,
|
||||
@@ -72,24 +100,78 @@ func (node *GitNode) Read() ([]byte, error) {
|
||||
return node.ReadContext(context.Background())
|
||||
}
|
||||
|
||||
func (node *GitNode) ReadContext(_ context.Context) ([]byte, error) {
|
||||
fs := memfs.New()
|
||||
storer := memory.NewStorage()
|
||||
_, err := git.Clone(storer, fs, &git.CloneOptions{
|
||||
URL: node.url.String(),
|
||||
ReferenceName: plumbing.ReferenceName(node.ref),
|
||||
SingleBranch: true,
|
||||
Depth: 1,
|
||||
})
|
||||
func (node *GitNode) buildURL() string {
|
||||
// Get the base URL
|
||||
baseURL := node.url.String()
|
||||
|
||||
ref := node.ref
|
||||
if ref == "" {
|
||||
ref = "HEAD"
|
||||
}
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
file, err := fs.Open(node.path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
// Build path to Taskfile in the cached repo
|
||||
taskfilePath := node.path
|
||||
if taskfilePath == "" {
|
||||
taskfilePath = "Taskfile.yml"
|
||||
}
|
||||
// Read the entire response body
|
||||
b, err := io.ReadAll(file)
|
||||
filePath := filepath.Join(repoDir, taskfilePath)
|
||||
|
||||
// Read file from cached repo
|
||||
b, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -98,6 +180,11 @@ func (node *GitNode) ReadContext(_ context.Context) ([]byte, 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)
|
||||
resolvedEntrypoint := fmt.Sprintf("%s//%s", node.url, filepath.Join(dir, entrypoint))
|
||||
if node.ref != "" {
|
||||
@@ -133,6 +220,22 @@ func (node *GitNode) CacheKey() string {
|
||||
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) {
|
||||
x := strings.Split(u.Path, "//")
|
||||
switch len(x) {
|
||||
|
||||
@@ -21,6 +21,17 @@ func TestGitNode_ssh(t *testing.T) {
|
||||
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) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -91,3 +102,146 @@ func TestGitNode_CacheKey(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func (node *HTTPNode) ReadContext(ctx context.Context) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("GET", url.String(), nil)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url.String(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.TaskfileFetchFailedError{URI: node.Location()}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
@@ -43,7 +42,7 @@ func (node *StdinNode) Read() ([]byte, error) {
|
||||
|
||||
func (node *StdinNode) ResolveEntrypoint(entrypoint string) (string, error) {
|
||||
// If the file is remote, we don't need to resolve the path
|
||||
if strings.Contains(entrypoint, "://") {
|
||||
if isRemoteEntrypoint(entrypoint) {
|
||||
return entrypoint, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ package taskfile
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dominikbraun/graph"
|
||||
"go.yaml.in/yaml/v4"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-task/task/v3/errors"
|
||||
"github.com/go-task/task/v3/internal/env"
|
||||
@@ -43,6 +44,7 @@ type (
|
||||
insecure bool
|
||||
download bool
|
||||
offline bool
|
||||
trustedHosts []string
|
||||
tempDir string
|
||||
cacheExpiryDuration time.Duration
|
||||
debugFunc DebugFunc
|
||||
@@ -59,6 +61,7 @@ func NewReader(opts ...ReaderOption) *Reader {
|
||||
insecure: false,
|
||||
download: false,
|
||||
offline: false,
|
||||
trustedHosts: nil,
|
||||
tempDir: os.TempDir(),
|
||||
cacheExpiryDuration: 0,
|
||||
debugFunc: nil,
|
||||
@@ -119,6 +122,20 @@ func (o *offlineOption) ApplyToReader(r *Reader) {
|
||||
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].
|
||||
// By default, the reader uses [os.TempDir].
|
||||
func WithTempDir(tempDir string) ReaderOption {
|
||||
@@ -187,9 +204,15 @@ func (o *promptFuncOption) ApplyToReader(r *Reader) {
|
||||
// building an [ast.TaskfileGraph] as it goes. If any errors occur, they will be
|
||||
// returned immediately.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.graph, nil
|
||||
}
|
||||
|
||||
@@ -206,6 +229,28 @@ func (r *Reader) promptf(format string, a ...any) error {
|
||||
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 {
|
||||
// Create a new vertex for the Taskfile
|
||||
vertex := &ast.TaskfileVertex{
|
||||
@@ -459,9 +504,9 @@ func (r *Reader) readRemoteNodeContent(ctx context.Context, node RemoteNode) ([]
|
||||
|
||||
// If there is no manual checksum pin, run the automatic checks
|
||||
if node.Checksum() == "" {
|
||||
// Prompt the user if required
|
||||
// Prompt the user if required (unless host is trusted)
|
||||
prompt := cache.ChecksumPrompt(checksum)
|
||||
if prompt != "" {
|
||||
if prompt != "" && !r.isTrusted(node.Location()) {
|
||||
if err := func() error {
|
||||
r.promptMutex.Lock()
|
||||
defer r.promptMutex.Unlock()
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
defaultTaskfiles = []string{
|
||||
// DefaultTaskfiles is the list of Taskfile file names supported by default.
|
||||
DefaultTaskfiles = []string{
|
||||
"Taskfile.yml",
|
||||
"taskfile.yml",
|
||||
"Taskfile.yaml",
|
||||
@@ -28,6 +29,7 @@ var (
|
||||
"text/x-yaml",
|
||||
"application/yaml",
|
||||
"application/x-yaml",
|
||||
"application/octet-stream",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -66,7 +68,7 @@ func RemoteExists(ctx context.Context, u url.URL) (*url.URL, error) {
|
||||
|
||||
// If the request was not successful, append the default Taskfile names to
|
||||
// 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
|
||||
// path if it is empty
|
||||
if u.Path == "" {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# https://taskfile.dev
|
||||
# yaml-language-server: $schema=https://taskfile.dev/schema.json
|
||||
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
GREETING: Hello, World!
|
||||
GREETING: Hello, world!
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: Print a greeting message
|
||||
cmds:
|
||||
- echo "{{.GREETING}}"
|
||||
silent: true
|
||||
|
||||
@@ -3,24 +3,30 @@ package ast
|
||||
import (
|
||||
"cmp"
|
||||
"maps"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
)
|
||||
|
||||
type TaskRC struct {
|
||||
Version *semver.Version `yaml:"version"`
|
||||
Verbose *bool `yaml:"verbose"`
|
||||
Concurrency *int `yaml:"concurrency"`
|
||||
Remote Remote `yaml:"remote"`
|
||||
Experiments map[string]int `yaml:"experiments"`
|
||||
Version *semver.Version `yaml:"version"`
|
||||
Verbose *bool `yaml:"verbose"`
|
||||
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"`
|
||||
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.
|
||||
@@ -42,7 +48,16 @@ func (t *TaskRC) Merge(other *TaskRC) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package taskrc
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/go-task/task/v3/taskrc/ast"
|
||||
)
|
||||
|
||||
@@ -57,9 +57,13 @@ func GetConfig(dir string) (*ast.TaskRC, error) {
|
||||
}
|
||||
|
||||
// Find all the nodes from the given directory up to the users home directory
|
||||
entrypoints, err := fsext.SearchAll("", dir, defaultTaskRCs)
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
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
|
||||
@@ -84,6 +88,5 @@ func GetConfig(dir string) (*ast.TaskRC, error) {
|
||||
}
|
||||
config.Merge(localConfig)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -135,3 +136,174 @@ func TestGetConfig_All(t *testing.T) { //nolint:paralleltest // cannot run in pa
|
||||
},
|
||||
}, 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)
|
||||
})
|
||||
}
|
||||
|
||||
14
testdata/failfast/default/Taskfile.yaml
vendored
Normal file
14
testdata/failfast/default/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
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
|
||||
1
testdata/failfast/default/testdata/TestFailfast-Default-default-err-run.golden
vendored
Normal file
1
testdata/failfast/default/testdata/TestFailfast-Default-default-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1
|
||||
3
testdata/failfast/default/testdata/TestFailfast-Default-default.golden
vendored
Normal file
3
testdata/failfast/default/testdata/TestFailfast-Default-default.golden
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dep1
|
||||
dep2
|
||||
dep3
|
||||
1
testdata/failfast/default/testdata/TestFailfast-Option-default-err-run.golden
vendored
Normal file
1
testdata/failfast/default/testdata/TestFailfast-Option-default-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1
|
||||
1
testdata/failfast/default/testdata/TestFailfast-Option-default.golden
vendored
Normal file
1
testdata/failfast/default/testdata/TestFailfast-Option-default.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
15
testdata/failfast/task/Taskfile.yaml
vendored
Normal file
15
testdata/failfast/task/Taskfile.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
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
|
||||
1
testdata/failfast/task/testdata/TestFailfast-Task-task-err-run.golden
vendored
Normal file
1
testdata/failfast/task/testdata/TestFailfast-Task-task-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Failed to run task "default": task: Failed to run task "dep4": exit status 1
|
||||
1
testdata/failfast/task/testdata/TestFailfast-Task-task.golden
vendored
Normal file
1
testdata/failfast/task/testdata/TestFailfast-Task-task.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
7
testdata/label_error/Taskfile.yml
vendored
Normal file
7
testdata/label_error/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
label: "foobar"
|
||||
cmds:
|
||||
- "false"
|
||||
1
testdata/label_error/testdata/TestLabel-label_in_error-err-run.golden
vendored
Normal file
1
testdata/label_error/testdata/TestLabel-label_in_error-err-run.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: Failed to run task "foobar": exit status 1
|
||||
1
testdata/label_error/testdata/TestLabel-label_in_error.golden
vendored
Normal file
1
testdata/label_error/testdata/TestLabel-label_in_error.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: [foobar] false
|
||||
@@ -1 +1 @@
|
||||
task: precondition not met
|
||||
task: Failed to run task "impossible": task: precondition not met
|
||||
@@ -1 +1 @@
|
||||
task: Failed to run task "executes_failing_task_as_cmd": task: precondition not met
|
||||
task: Failed to run task "executes_failing_task_as_cmd": task: Failed to run task "impossible": task: precondition not met
|
||||
@@ -1 +1 @@
|
||||
task: precondition not met
|
||||
task: Failed to run task "depends_on_impossible": task: Failed to run task "impossible": task: precondition not met
|
||||
@@ -1 +1 @@
|
||||
task: Task "foo" cancelled by user
|
||||
task: Failed to run task "foo": task: Task "foo" cancelled by user
|
||||
@@ -1 +1 @@
|
||||
task: Task "foo" cancelled by user
|
||||
task: Failed to run task "foo": task: Task "foo" cancelled by user
|
||||
@@ -1 +1 @@
|
||||
task: Task "foo" cancelled by user
|
||||
task: Failed to run task "foo": task: Task "foo" cancelled by user
|
||||
@@ -1 +1 @@
|
||||
task: Task "foo" cancelled by user
|
||||
task: Failed to run task "foo": task: Task "foo" cancelled by user
|
||||
11
testdata/run_when_changed/Taskfile.yml
vendored
Normal file
11
testdata/run_when_changed/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
service-a: ./service-a
|
||||
service-b: ./service-b
|
||||
|
||||
tasks:
|
||||
start:
|
||||
cmds:
|
||||
- task: service-a:start
|
||||
- task: service-b:start
|
||||
7
testdata/run_when_changed/library/Taskfile.yml
vendored
Normal file
7
testdata/run_when_changed/library/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
login:
|
||||
run: when_changed
|
||||
cmds:
|
||||
- echo "login server={{.SERVER}} user={{.USER}}"
|
||||
18
testdata/run_when_changed/service-a/Taskfile.yml
vendored
Normal file
18
testdata/run_when_changed/service-a/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
library:
|
||||
taskfile: ../library/Taskfile.yml
|
||||
dir: ../library
|
||||
|
||||
tasks:
|
||||
start:
|
||||
cmds:
|
||||
- task: library:login
|
||||
vars:
|
||||
SERVER: fubar
|
||||
USER: fubar
|
||||
- task: library:login
|
||||
vars:
|
||||
SERVER: foo
|
||||
USER: foo
|
||||
18
testdata/run_when_changed/service-b/Taskfile.yml
vendored
Normal file
18
testdata/run_when_changed/service-b/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
library:
|
||||
taskfile: ../library/Taskfile.yml
|
||||
dir: ../library
|
||||
|
||||
tasks:
|
||||
start:
|
||||
cmds:
|
||||
- task: library:login
|
||||
vars:
|
||||
SERVER: fubar
|
||||
USER: fubar
|
||||
- task: library:login
|
||||
vars:
|
||||
SERVER: bar
|
||||
USER: bar
|
||||
21
testdata/summary-vars-requires/Taskfile-with-env.yml
vendored
Normal file
21
testdata/summary-vars-requires/Taskfile-with-env.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
version: 3
|
||||
|
||||
vars:
|
||||
GLOBAL_VAR: "I am a global var"
|
||||
|
||||
env:
|
||||
GLOBAL_ENV: "I am a global env"
|
||||
|
||||
tasks:
|
||||
test-env:
|
||||
desc: Task with vars and env
|
||||
vars:
|
||||
LOCAL_VAR: "I am a local var"
|
||||
env:
|
||||
LOCAL_ENV: "I am a local env"
|
||||
DATABASE_URL: "postgres://localhost/mydb"
|
||||
requires:
|
||||
vars:
|
||||
- API_KEY
|
||||
cmds:
|
||||
- echo "Testing env vars"
|
||||
16
testdata/summary-vars-requires/Taskfile-with-globals.yml
vendored
Normal file
16
testdata/summary-vars-requires/Taskfile-with-globals.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
version: 3
|
||||
|
||||
vars:
|
||||
GLOBAL_VAR: "I am global"
|
||||
ANOTHER_GLOBAL: "Also global"
|
||||
|
||||
tasks:
|
||||
test-globals:
|
||||
desc: Task with global and local vars
|
||||
vars:
|
||||
LOCAL_VAR: "I am local"
|
||||
requires:
|
||||
vars:
|
||||
- REQUIRED_VAR
|
||||
cmds:
|
||||
- echo {{ .GLOBAL_VAR }} {{ .LOCAL_VAR }}
|
||||
36
testdata/summary-vars-requires/Taskfile.yml
vendored
Normal file
36
testdata/summary-vars-requires/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
version: 3
|
||||
|
||||
tasks:
|
||||
mytask:
|
||||
desc: It does things
|
||||
summary: |
|
||||
It does things and has optional and required variables.
|
||||
vars:
|
||||
OPTIONAL_VAR: "hello"
|
||||
requires:
|
||||
vars:
|
||||
- REQUIRED_VAR
|
||||
cmds:
|
||||
- cmd: echo {{ .OPTIONAL_VAR }} {{ .REQUIRED_VAR }}
|
||||
|
||||
with-sh-var:
|
||||
desc: Task with shell variable
|
||||
vars:
|
||||
DYNAMIC_VAR:
|
||||
sh: echo "world"
|
||||
STATIC_VAR: "hello"
|
||||
cmds:
|
||||
- echo {{ .DYNAMIC_VAR }}
|
||||
|
||||
no-vars:
|
||||
desc: Task without variables
|
||||
cmds:
|
||||
- echo "no vars here"
|
||||
|
||||
only-requires:
|
||||
desc: Task with only requires
|
||||
requires:
|
||||
vars:
|
||||
- NEEDED_VAR
|
||||
cmds:
|
||||
- echo {{ .NEEDED_VAR }}
|
||||
10
testdata/summary-vars-requires/testdata/TestSummaryWithVarsAndRequires-shell-vars.golden
vendored
Normal file
10
testdata/summary-vars-requires/testdata/TestSummaryWithVarsAndRequires-shell-vars.golden
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
task: with-sh-var
|
||||
|
||||
Task with shell variable
|
||||
|
||||
vars:
|
||||
DYNAMIC_VAR: sh: echo "world"
|
||||
STATIC_VAR: "hello"
|
||||
|
||||
commands:
|
||||
- echo
|
||||
13
testdata/summary-vars-requires/testdata/TestSummaryWithVarsAndRequires-vars-and-requires.golden
vendored
Normal file
13
testdata/summary-vars-requires/testdata/TestSummaryWithVarsAndRequires-vars-and-requires.golden
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
task: mytask
|
||||
|
||||
It does things and has optional and required variables.
|
||||
|
||||
vars:
|
||||
OPTIONAL_VAR: "hello"
|
||||
|
||||
requires:
|
||||
vars:
|
||||
- REQUIRED_VAR
|
||||
|
||||
commands:
|
||||
- echo hello
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user