Compare commits

..

102 Commits

Author SHA1 Message Date
45af67d22d chore: make deps 2025-11-11 14:18:57 +01:00
db7c4042d0 chore: go mod vendor 2025-11-09 11:52:00 +01:00
ed1a66dc5f chore: publish next tag 0.12.0-beta
Some checks failed
continuous-integration/drone/tag Build is failing
continuous-integration/drone/push Build is passing
2025-11-09 11:46:38 +01:00
bb93e4266a fix: show domain with https (clickable)
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
See #643
2025-11-09 10:56:21 +01:00
a2cc70b2f5 test: reinstate debug
Some checks failed
continuous-integration/drone/push Build is failing
2025-11-09 10:43:09 +01:00
ce1aa3d870 test: ensure recipe sync is robust
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-09 10:41:32 +01:00
d75700c8a9 test: ensure env vars updated
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
See #723
2025-11-09 09:32:52 +01:00
0ccc4aae72 chore: remove --debug 2025-11-09 09:32:42 +01:00
ec22d5d51d test: remove old tests
All checks were successful
continuous-integration/drone/push Build is passing
See #716
2025-11-05 09:52:28 +01:00
ab42584d05 docs: update var name 2025-11-05 09:52:28 +01:00
f
40eb6e9a18 fix: shorter hyphen
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-11-04 11:49:44 -03:00
35eb9d4a89 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-11-04 14:34:39 +00:00
08cc63d523 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-04 15:34:27 +01:00
797b8d899b chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-11-04 13:53:26 +00:00
fb786306b5 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-04 14:53:15 +01:00
c3a2048eba fix: throw away unknown version
Some checks failed
continuous-integration/drone/push Build is failing
See #715
2025-11-04 13:52:49 +00:00
1bdc11ba62 fix no-input app deployment when no tty is present
Some checks failed
continuous-integration/drone/push Build is failing
2025-11-04 13:52:28 +00:00
cc8703310c chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-11-04 07:35:14 +00:00
fcd5bd863d chore: make i18n
Some checks failed
continuous-integration/drone/push Build is failing
2025-11-04 08:35:04 +01:00
e6af2da9dd refactor: named note, merge if clause 2025-11-04 08:34:49 +01:00
4b688825e0 feat: create docker context when server folder does exist
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2025-11-03 17:29:04 +01:00
b0cf2a1f8e chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-02 10:44:34 +00:00
6b7020d457 test: env version to .config.env 2025-11-02 10:44:34 +00:00
efdac610bd fix: skip local server on it's own 2025-11-02 10:44:34 +00:00
cd6021f116 fix: expose new version
See #713
2025-11-02 10:44:34 +00:00
ee8de8ef5c chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-31 20:38:18 +00:00
e5a653c002 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-31 21:38:01 +01:00
2cca04de90 fix(move): does not error when secret already exists on new server
See #709
2025-10-31 21:37:55 +01:00
f2f79e2df8 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-31 20:33:36 +00:00
dd83741a9f chore: i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-31 21:31:49 +01:00
dc2cd85d91 feat!: abra app env pull
`abra app env` -> `abra app env list`.

See #497
2025-10-31 21:31:43 +01:00
96e59cf196 test: adjust to match new reality [ci skip] 2025-10-31 14:35:57 +01:00
11656c009d test: don't wat to converge [ci skip] 2025-10-26 11:49:57 +01:00
e4e1b58501 test: update matches in old tests [ci skip] 2025-10-26 11:40:25 +01:00
3b8f12643c test: use new target
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-25 20:01:23 +02:00
e5f5154197 test: kadabra is gone
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-23 20:58:08 +02:00
6c1c0a8a8a refactor: use xgettext-go from makefile variable
All checks were successful
continuous-integration/drone/push Build is passing
Easier to hack when customising xgettext-go.
2025-10-23 09:26:51 +02:00
662f45008c chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-23 07:14:28 +00:00
708c5f5223 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
See #688
2025-10-23 09:14:15 +02:00
d58552b748 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-23 07:10:59 +00:00
51fe809851 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
See #688
2025-10-23 09:10:36 +02:00
3f6a22747f chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-23 07:09:11 +00:00
4e75b96914 chore: make i18n
All checks were successful
continuous-integration/drone/push Build is passing
See #688
2025-10-23 09:08:50 +02:00
fd4ee75ab7 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-19 13:47:17 +00:00
964ed834ee refactor!: remove autoupdate (kadabra)
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-19 15:46:18 +02:00
fcb3167394 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-19 13:36:39 +00:00
3845b40aa3 refactor!: archive kadabra
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-19 15:32:38 +02:00
3wc
0dc5c307af chore: update i18n
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-18 16:03:11 -04:00
3wc
fc5855ff28 feat: Add hexadecimal secret generation
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
Closes #695
2025-10-18 15:03:02 -04:00
5b504a1550 Revert "feat: cctuip lands in main"
All checks were successful
continuous-integration/drone/push Build is passing
See #691 (comment)
2025-10-17 19:27:23 +02:00
fc16a21f1c chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-03 18:39:54 +00:00
7b4d2d7230 chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-03 20:35:47 +02:00
d0ccb805c6 refactor: isolate expensive IsDirty() call
Some checks failed
continuous-integration/drone/push Build is failing
See #689
2025-10-03 20:35:09 +02:00
2460dd9438 fix: pagination with multiline(true)
See #689
2025-10-03 20:13:35 +02:00
9c648a2566 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-02 09:05:45 +00:00
22ecfb9c4c test: remove old non-tui tests
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-02 10:58:53 +02:00
9f3cf718be chore: make i18n 2025-10-02 10:54:07 +02:00
b737ce2107 feat: cctuip lands in main
See toolshed/organising#657
2025-10-02 10:53:44 +02:00
a3d0ece7cb refactor: single missing value 2025-10-02 10:53:31 +02:00
d63a1c28ea chore: go mod tidy / vendor / make deps 2025-10-02 10:35:46 +02:00
1c10e64c58 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-01 19:16:08 +00:00
21826ec555 chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-01 21:13:41 +02:00
4b4c56d406 fix: skip borked tags on app list
See #656
2025-10-01 21:13:18 +02:00
4314195dd7 test: dont run xgettext-go on release
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
See #663
2025-10-01 12:20:16 +02:00
df4447b038 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-01 10:12:54 +00:00
3fa660e579 chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-01 12:12:07 +02:00
a430b1e4fd fix: dont show unchanged images/tags
See #677
2025-10-01 12:11:29 +02:00
896c434f38 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-01 09:50:52 +00:00
847b7238c5 chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-01 11:49:19 +02:00
89d5fc91b0 fix: gracefully explode of missing context
See #675
2025-10-01 11:48:51 +02:00
5af3c5f56e chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-01 09:20:19 +00:00
beb3864b2d chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-10-01 11:18:51 +02:00
581e6ef538 chore: lowercase 2025-10-01 11:18:42 +02:00
fd642ddb84 fix: fail if release conflicts
See toolshed/organising#638
2025-10-01 11:18:30 +02:00
1ad8c127d9 fix: point to the catalogue
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
See #669
2025-10-01 09:09:40 +02:00
40aab6a6c1 chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-01 07:01:05 +00:00
4d33a24a07 refactor: less EN specific value
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-01 08:54:09 +02:00
ee59eb350b chore: make i18n 2025-10-01 08:54:08 +02:00
5da13ff15a test: adjust integration suite 2025-10-01 08:54:05 +02:00
491c594ad3 fix: better message for redeploying chaos version
See #668
2025-10-01 08:19:47 +02:00
c794d533be fix: avoid hanging when tasks randomly surge
See #557
2025-10-01 08:19:46 +02:00
a6daf7030e fix: show chaos version on upgrade 2025-10-01 08:19:45 +02:00
fe3b7ffa9c fix: write correct undeploy version 2025-10-01 08:19:44 +02:00
4c066a92d8 fix: show chaos version on rollback overview 2025-10-01 08:19:43 +02:00
7899b57781 fix: show chaos version on deploy overview 2025-10-01 08:19:42 +02:00
6e0a901887 chore: spacing for readability 2025-10-01 08:19:41 +02:00
713fdebc90 fix: show chaos version on undeploy 2025-10-01 08:19:40 +02:00
6944d138c6 refactor: chaos-y handling
See #659
2025-10-01 08:19:39 +02:00
fbb1f16470 fix: dont overwrite label when chaos
See #668
2025-10-01 08:19:38 +02:00
2473cafdf5 test: use new CI option for xgettext-go
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-30 19:23:23 +02:00
0ccfbd253e chore: update translation files
Some checks failed
continuous-integration/drone/push Build is failing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-09-30 17:20:47 +00:00
6c4bee0ac7 chore: make i18n
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-30 19:20:35 +02:00
4fa9f536eb chore: update translation files
Some checks failed
continuous-integration/drone/push Build is failing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-09-30 17:10:26 +00:00
033c9bfc13 feat: msgctxt support
Some checks failed
continuous-integration/drone/push Build is failing
See #647
See toolshed/xgettext-go#1
2025-09-30 19:08:52 +02:00
0db1ee87fc chore: update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-09-30 17:03:14 +00:00
d180bb924f chore: make i18n
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-09-29 10:33:56 +02:00
d50d68d95a test: further generate=false secre tests 2025-09-29 10:33:40 +02:00
f468bc7443 fix: collect local name also 2025-09-29 10:33:26 +02:00
dee2d9d104 fix: nuance of generate=false for app deploy 2025-09-29 10:32:29 +02:00
5c892b1d6a fix: nuance of generate=false for app new 2025-09-29 10:32:04 +02:00
81b96fc7b1 docs: better wording 2025-09-29 10:31:46 +02:00
c92a0d0703 feat: Add cloud-init file
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-09-14 13:02:21 -04:00
1018 changed files with 51565 additions and 51348 deletions

View File

@ -4,5 +4,4 @@
Dockerfile
abra
dist
kadabra
tags

View File

@ -11,10 +11,15 @@ steps:
image: git.coopcloud.tech/toolshed/drone-xgettext-go:latest
settings:
keyword: i18n.G
keyword_ctx: i18n.GC
out: pkg/i18n/locales/abra.pot
comments_tag: translators
depends_on:
- make check
when:
event:
exclude:
- tag
- name: xgettext-go status
image: golang:1.24-alpine3.22
@ -27,6 +32,10 @@ steps:
- git diff-files --exit-code
depends_on:
- xgettext-go
when:
event:
exclude:
- tag
- name: make test
image: golang:1.24

2
.gitignore vendored
View File

@ -4,6 +4,6 @@
.envrc
.vscode/
/abra
/kadabra
/bin
dist/
tests/integration/.bats

View File

@ -32,31 +32,6 @@ builds:
- "-s"
- "-w"
- id: kadabra
binary: kadabra
dir: cmd/kadabra
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
goarch:
- 386
- amd64
- arm
- arm64
goarm:
- 5
- 6
- 7
gcflags:
- "all=-l -B"
ldflags:
- "-X 'main.Commit={{ .Commit }}'"
- "-X 'main.Version={{ .Version }}'"
- "-s"
- "-w"
checksum:
name_template: "checksums.txt"

View File

@ -1,5 +1,5 @@
ABRA := ./cmd/abra
KADABRA := ./cmd/kadabra
XGETTEXT := ./bin/xgettext-go
COMMIT := $(shell git rev-list -1 HEAD)
GOPATH := $(shell go env GOPATH)
GOVERSION := 1.24
@ -13,40 +13,23 @@ LINGUAS := $(basename $(POFILES))
export GOPRIVATE=coopcloud.tech
# NOTE(d1): default `make` optimised for Abra hacking
all: format check build-abra test
all: format check build
run-abra:
run:
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
run-kadabra:
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(KADABRA)
install-abra:
install:
@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
install-kadabra:
@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(KADABRA)
install: install-abra install-kadabra
build-abra:
build:
@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(ABRA)
build-kadabra:
@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(KADABRA)
build: build-abra build-kadabra
build-docker-abra:
build-docker:
@docker run -it -v $(PWD):/abra golang:$(GOVERSION) \
bash -c 'cd /abra; ./scripts/docker/build.sh'
build-docker: build-docker-abra
clean:
@rm '$(GOPATH)/bin/abra'
@rm '$(GOPATH)/bin/kadabra'
format:
@gofmt -s -w $$(find . -type f -name '*.go' | grep -v "/vendor/")
@ -78,14 +61,20 @@ update-po:
done
.PHONY: update-pot
update-pot:
@xgettext-go \
update-pot: $(XGETTEXT)
@${XGETTEXT} \
-o pkg/i18n/locales/$(DOMAIN).pot \
--keyword=i18n.G \
--keyword-ctx=i18n.GC \
--sort-output \
--add-comments-tag="translators" \
$$(find . -name "*.go" -not -path "*vendor*" | sort)
${XGETTEXT}:
@mkdir -p ./bin && \
wget -O ./bin/xgettext-go https://git.coopcloud.tech/toolshed/xgettext-go/raw/branch/main/xgettext-go && \
chmod +x ./bin/xgettext-go
.PHONY: update-pot-po-metadata
update-pot-po-metadata:
@sed -i "s/charset=CHARSET/charset=UTF-8/g" pkg/i18n/locales/*.po pkg/i18n/locales/*.pot

View File

@ -9,7 +9,7 @@ import (
// translators: `abra app` aliases. use a comma separated list of aliases with
// no spaces in between
var appAliases = i18n.G("a")
var appAliases = i18n.GC("a", "abra app")
var AppCommand = &cobra.Command{
// translators: `app` command group

View File

@ -268,7 +268,7 @@ func init() {
AppBackupListCommand.Flags().BoolVarP(
&showAllPaths,
i18n.G("all"),
i18n.G("a"),
i18n.GC("a", "app backup list"),
false,
i18n.G("show all paths"),
)

View File

@ -3,6 +3,7 @@ package app
import (
"context"
"errors"
"fmt"
"strings"
"coopcloud.tech/abra/cli/internal"
@ -103,18 +104,17 @@ checkout as-is. Recipe commit hashes are also supported as values for
toDeployVersion, err = getDeployVersion(args, deployMeta, app)
if err != nil {
log.Fatal(i18n.G("get deploy version: %s", err))
log.Fatal(err)
}
versionIsChaos := false
if !internal.Chaos {
var err error
versionIsChaos, err = app.Recipe.EnsureVersion(toDeployVersion)
isChaosCommit, err := app.Recipe.EnsureVersion(toDeployVersion)
if err != nil {
log.Fatal(i18n.G("ensure recipe: %s", err))
}
if versionIsChaos {
if isChaosCommit {
log.Warnf(i18n.G("version '%s' appears to be a chaos commit, but --chaos/-C was not provided", toDeployVersion))
internal.Chaos = true
}
}
@ -152,14 +152,26 @@ checkout as-is. Recipe commit hashes are also supported as values for
log.Fatal(err)
}
appPkg.ExposeAllEnv(stackName, compose, app.Env)
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos || versionIsChaos)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
if internal.Chaos {
appPkg.SetChaosVersionLabel(compose, stackName, toDeployVersion)
}
appPkg.SetUpdateLabel(compose, stackName, app.Env)
appPkg.SetVersionLabel(compose, stackName, toDeployVersion)
versionLabel := toDeployVersion
if internal.Chaos {
for _, service := range compose.Services {
if service.Name == "app" {
labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName)
// NOTE(d1): keep non-chaos version labbeling when doing chaos ops
versionLabel = service.Deploy.Labels[labelKey]
}
}
}
appPkg.SetVersionLabel(compose, stackName, versionLabel)
newRecipeWithDeployVersion := fmt.Sprintf("%s:%s", app.Recipe.Name, toDeployVersion)
appPkg.ExposeAllEnv(stackName, compose, app.Env, newRecipeWithDeployVersion)
envVars, err := appPkg.CheckEnv(app)
if err != nil {
@ -186,9 +198,12 @@ checkout as-is. Recipe commit hashes are also supported as values for
log.Debug(i18n.G("skipping domain checks"))
}
deployedVersion := config.NO_VERSION_DEFAULT
deployedVersion := config.MISSING_DEFAULT
if deployMeta.IsDeployed {
deployedVersion = deployMeta.Version
if deployMeta.IsChaos {
deployedVersion = deployMeta.ChaosVersion
}
}
// Gather secrets
@ -245,6 +260,7 @@ checkout as-is. Recipe commit hashes are also supported as values for
app.Name,
app.Server,
internal.DontWaitConverge,
internal.NoInput,
f,
); err != nil {
log.Fatal(err)
@ -300,6 +316,16 @@ func validateArgsAndFlags(args []string) error {
}
func validateSecrets(cl *dockerClient.Client, app appPkg.App) error {
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
if err != nil {
return err
}
secretsConfig, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName())
if err != nil {
return err
}
secStats, err := secret.PollSecretsStatus(cl, app)
if err != nil {
return err
@ -307,6 +333,10 @@ func validateSecrets(cl *dockerClient.Client, app appPkg.App) error {
for _, secStat := range secStats {
if !secStat.CreatedOnRemote {
secretConfig := secretsConfig[secStat.LocalName]
if secretConfig.SkipGenerate {
return errors.New(i18n.G("secret not inserted (#generate=false): %s", secStat.LocalName))
}
return errors.New(i18n.G("secret not generated: %s", secStat.LocalName))
}
}
@ -334,7 +364,12 @@ func getDeployVersion(cliArgs []string, deployMeta stack.DeployMeta, app appPkg.
// Check if the recipe has a version in the .env file
if app.Recipe.EnvVersion != "" && !internal.DeployLatest {
if strings.HasSuffix(app.Recipe.EnvVersionRaw, "+U") {
return "", errors.New(i18n.G("version: can not redeploy chaos version %s", app.Recipe.EnvVersionRaw))
// NOTE(d1): use double-line 5 spaces ("FATA ") trick to make a more
// informative error message. it's ugly but that's our logging situation
// atm
return "", errors.New(i18n.G(`cannot redeploy previous chaos version (%s), did you mean to use "--chaos"?
to return to a regular release, specify a release tag, commit SHA or use "--latest"`,
formatter.BoldDirtyDefault(app.Recipe.EnvVersionRaw)))
}
log.Debug(i18n.G("version: taking version from .env file: %s", app.Recipe.EnvVersion))
return app.Recipe.EnvVersion, nil

View File

@ -1,28 +1,50 @@
package app
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strings"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/app"
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config"
containerPkg "coopcloud.tech/abra/pkg/container"
contextPkg "coopcloud.tech/abra/pkg/context"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack"
"github.com/docker/docker/api/types/filters"
"github.com/spf13/cobra"
)
// translators: `abra app env` aliases. use a comma separated list of aliases with
// no spaces in between
// translators: `abra app env` aliases. use a comma separated list of aliases
// with no spaces in between
var appEnvAliases = i18n.G("e")
var AppEnvCommand = &cobra.Command{
// translators: `app env` command
Use: i18n.G("env <domain> [flags]"),
Aliases: strings.Split(appEnvAliases, ","),
// translators: Short description for `app env` command
Short: i18n.G("Show app .env values"),
Example: i18n.G(" abra app env 1312.net"),
// translators: `abra app env list` aliases. use a comma separated list of
// aliases with no spaces in between
var appEnvListAliases = i18n.G("l,ls")
// translators: `abra app env pull` aliases. use a comma separated list of
// aliases with no spaces in between
var appEnvPullAliases = i18n.G("pl,p")
var AppEnvListCommand = &cobra.Command{
// translators: `app env list` command
Use: i18n.G("list <domain> [flags]"),
Aliases: strings.Split(appEnvListAliases, ","),
// translators: Short description for `app env list` command
Short: i18n.G("List all app environment values"),
Example: i18n.G(" abra app env list 1312.net"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -49,3 +71,274 @@ var AppEnvCommand = &cobra.Command{
fmt.Println(overview)
},
}
var AppEnvPullCommand = &cobra.Command{
// translators: `app pull` command
Use: i18n.G("pull <domain> [flags]"),
Aliases: strings.Split(appEnvPullAliases, ","),
// translators: Short description for `app env pull` command
Short: i18n.G("Pull app environment values from a deployed app"),
Long: i18n.G(`Pull app environment values from a deploymed app.
A convenient command for when you've lost your app environment file or want to
synchronize your local app environment values with what is deployed live.`),
Example: i18n.G(` # pull existing .env file and overwrite local values
abra app env pull 1312.net --force
# pull lost app .env file
abra app env pull my.gitea.net --server 1312.net`),
Args: cobra.MaximumNArgs(2),
ValidArgsFunction: func(
cmd *cobra.Command,
args []string,
toComplete string) ([]string, cobra.ShellCompDirective) {
return autocomplete.AppNameComplete()
},
Run: func(cmd *cobra.Command, args []string) {
appName := args[0]
appEnvPath := path.Join(config.ABRA_DIR, "servers", server, fmt.Sprintf("%s.env", appName))
if _, err := os.Stat(appEnvPath); !os.IsNotExist(err) {
log.Fatal(i18n.G("%s already exists?", appEnvPath))
}
if server == "" {
log.Fatal(i18n.G("unable to determine server of app %s, please pass --server/-s", appName))
}
serverDir := filepath.Join(config.SERVERS_DIR, server)
if _, err := os.Stat(serverDir); os.IsNotExist(err) {
log.Fatal(i18n.G("unknown server %s, run \"abra server add %s\"?", server, server))
}
store := contextPkg.NewDefaultDockerContextStore()
contexts, err := store.Store.List()
if err != nil {
log.Fatal(i18n.G("unable to look up server context for %s: %s", server, err))
}
var contextCreated bool
if server == "default" {
contextCreated = true
}
for _, context := range contexts {
if context.Name == server {
contextCreated = true
}
}
if !contextCreated {
log.Fatal(i18n.G("%s missing context, run \"abra server add %s\"?", server, server))
}
cl, err := client.New(server)
if err != nil {
log.Fatal(err)
}
deployMeta, err := stack.IsDeployed(context.Background(), cl, appPkg.StackName(appName))
if err != nil {
log.Fatal(err)
}
if !deployMeta.IsDeployed {
log.Fatal(i18n.G("%s is not deployed?", appName))
}
filters := filters.NewArgs()
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(appName), "app"))
targetContainer, err := containerPkg.GetContainer(context.Background(), cl, filters, internal.NoInput)
if err != nil {
log.Fatal(i18n.G("unable to retrieve container for %s: %s", appName, err))
}
inspectResult, err := cl.ContainerInspect(context.Background(), targetContainer.ID)
if err != nil {
log.Fatal(i18n.G("unable to inspect container for %s: %s", appName, err))
}
deploymentEnv := make(map[string]string)
for _, envVar := range inspectResult.Config.Env {
split := strings.SplitN(envVar, "=", 2)
if len(split) != 2 {
log.Debug(i18n.G("no value attached to %s", envVar))
continue
}
key, val := split[0], split[1]
deploymentEnv[key] = val
}
log.Debug(i18n.G("pulled env values from %s deployment: %s", appName, deploymentEnv))
var (
recipeEnvVar string
recipeKey string
)
if r, ok := deploymentEnv["TYPE"]; ok {
recipeKey = "TYPE"
recipeEnvVar = r
}
if r, ok := deploymentEnv["RECIPE"]; ok {
recipeKey = "RECIPE"
recipeEnvVar = r
}
if recipeEnvVar == "" {
log.Fatal(i18n.G("unable to determine recipe type from %s, env: %v", appName, inspectResult.Config.Env))
}
var recipeName = recipeEnvVar
if strings.Contains(recipeEnvVar, ":") {
split := strings.Split(recipeEnvVar, ":")
recipeName = split[0]
}
recipe := internal.ValidateRecipe(
[]string{recipeName},
cmd.Name(),
)
version := deployMeta.Version
if deployMeta.IsChaos {
version = deployMeta.ChaosVersion
}
if _, err := recipe.EnsureVersion(version); err != nil {
log.Fatal(err)
}
mergedEnv, err := recipe.SampleEnv()
if err != nil {
log.Fatal(err)
}
log.Debug(i18n.G("retrieved env values from .env.sample of %s: %s", recipe.Name, mergedEnv))
for k, v := range deploymentEnv {
mergedEnv[k] = v
}
if !strings.Contains(recipeEnvVar, ":") {
mergedEnv[recipeKey] = fmt.Sprintf("%s:%s", mergedEnv[recipeKey], version)
}
log.Debug(i18n.G("final merged env values for %s are: %s", appName, mergedEnv))
envSample, err := os.ReadFile(recipe.SampleEnvPath)
if err != nil {
log.Fatal(err)
}
err = os.WriteFile(appEnvPath, envSample, 0o664)
if err != nil {
log.Fatal(i18n.G("unable to write new env %s: %s", appEnvPath, err))
}
read, err := os.ReadFile(appEnvPath)
if err != nil {
log.Fatal(i18n.G("unable to read new env %s: %s", appEnvPath, err))
}
sampleEnv, err := recipe.SampleEnv()
if err != nil {
log.Fatal(err)
}
var composeFileUpdated bool
newContents := string(read)
for key, val := range mergedEnv {
if sampleEnv[key] == val {
continue
}
if key == "COMPOSE_FILE" {
composeFileUpdated = true
continue
}
if m, _ := regexp.MatchString(fmt.Sprintf(`#%s=`, key), newContents); m {
log.Debug(i18n.G("uncommenting %s", key))
re := regexp.MustCompile(fmt.Sprintf(`#%s=`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf("%s=", key))
}
if m, _ := regexp.MatchString(fmt.Sprintf(`# %s=`, key), newContents); m {
log.Debug(i18n.G("uncommenting %s", key))
re := regexp.MustCompile(fmt.Sprintf(`# %s=`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf("%s=", key))
}
if m, _ := regexp.MatchString(fmt.Sprintf(`%s=".*"`, key), newContents); m {
log.Debug(i18n.G(`inserting %s="%s" (double quotes)`, key, val))
re := regexp.MustCompile(fmt.Sprintf(`%s=".*"`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf(`%s="%s"`, key, val))
continue
}
if m, _ := regexp.MatchString(fmt.Sprintf(`%s='.*'`, key), newContents); m {
log.Debug(i18n.G(`inserting %s='%s' (single quotes)`, key, val))
re := regexp.MustCompile(fmt.Sprintf(`%s='.*'`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf(`%s='%s'`, key, val))
continue
}
if m, _ := regexp.MatchString(fmt.Sprintf("%s=.*", key), newContents); m {
log.Debug(i18n.G("inserting %s=%s (no quotes)", key, val))
re := regexp.MustCompile(fmt.Sprintf("%s=.*", key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf("%s=%s", key, val))
}
}
err = os.WriteFile(appEnvPath, []byte(newContents), 0)
if err != nil {
log.Fatal(i18n.G("unable to write new env %s: %s", appEnvPath, err))
}
log.Info(i18n.G("%s successfully created", appEnvPath))
if composeFileUpdated {
log.Warn(i18n.G("manual update required: COMPOSE_FILE=\"%s\"", mergedEnv["COMPOSE_FILE"]))
}
},
}
var AppEnvCommand = &cobra.Command{
// translators: `app env` command group
Use: i18n.G("env [cmd] [args] [flags]"),
Aliases: strings.Split(appEnvAliases, ","),
// translators: Short description for `app env` command group
Short: i18n.G("Manage app environment values"),
}
var (
server string
)
func init() {
AppEnvPullCommand.Flags().BoolVarP(
&internal.Force,
i18n.G("force"),
i18n.G("f"),
false,
i18n.G("perform action without further prompt"),
)
AppEnvPullCommand.Flags().StringVarP(
&server,
i18n.G("server"),
i18n.G("s"),
"",
i18n.G("server associated with deployed app"),
)
AppEnvPullCommand.RegisterFlagCompletionFunc(
i18n.G("server"),
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return autocomplete.ServerNameComplete()
},
)
}

View File

@ -25,7 +25,6 @@ type appStatus struct {
Status string `json:"status"`
Chaos string `json:"chaos"`
ChaosVersion string `json:"chaosVersion"`
AutoUpdate string `json:"autoUpdate"`
Version string `json:"version"`
Upgrade string `json:"upgrade"`
}
@ -118,7 +117,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
version := i18n.G("unknown")
chaos := i18n.G("unknown")
chaosVersion := i18n.G("unknown")
autoUpdate := i18n.G("unknown")
if statusMeta, ok := statuses[app.StackName()]; ok {
if currentVersion, exists := statusMeta["version"]; exists {
if currentVersion != "" {
@ -131,9 +129,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
if chaosDeployVersion, exists := statusMeta["chaosVersion"]; exists {
chaosVersion = chaosDeployVersion
}
if autoUpdateState, exists := statusMeta["autoUpdate"]; exists {
autoUpdate = autoUpdateState
}
if statusMeta["status"] != "" {
status = statusMeta["status"]
}
@ -146,7 +141,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
appStats.Chaos = chaos
appStats.ChaosVersion = chaosVersion
appStats.Version = version
appStats.AutoUpdate = autoUpdate
var newUpdates []string
if version != "unknown" && chaos == "false" {
@ -165,6 +159,11 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
}
for _, update := range updates {
if ok := tagcmp.IsParsable(update); !ok {
log.Debug(i18n.G("unable to parse %s, skipping as upgrade option", update))
continue
}
parsedUpdate, err := tagcmp.Parse(update)
if err != nil {
log.Fatal(err)
@ -226,7 +225,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
i18n.G("CHAOS"),
i18n.G("VERSION"),
i18n.G("UPGRADE"),
i18n.G("AUTOUPDATE"),
}...,
)
}
@ -257,8 +255,7 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
appStat.Status,
chaosStatus,
appStat.Version,
appStat.Upgrade,
appStat.AutoUpdate}...,
appStat.Upgrade}...,
)
}

View File

@ -128,6 +128,10 @@ Use "--dry-run/-r" to see which secrets and volumes will be moved.`),
secretName := strings.Join(sname[:len(sname)-1], "_")
data := resources.Secrets[secretName]
if err := client.StoreSecret(newServerClient, s.Spec.Name, data); err != nil {
if strings.Contains(err.Error(), "already exists") {
log.Info(i18n.G("skipping secret (because it already exists) on %s: %s", s.Spec.Name, newServer))
continue
}
log.Fatal(i18n.G("failed to store secret on %s: %s", err, newServer))
}
log.Info(i18n.G("created secret on %s: %s", s.Spec.Name, newServer))

View File

@ -192,7 +192,27 @@ var AppNewCommand = &cobra.Command{
log.Info(i18n.G("%s created (version: %s)", appDomain, recipeVersion))
if len(secretsConfig) > 0 {
log.Warn(i18n.G("%s requires secret generation before deploying, run \"abra app secret generate %s --all\"", recipe.Name, appDomain))
var (
hasSecretToGenerate bool
hasSecretToSkip bool
)
for _, secretConfig := range secretsConfig {
if secretConfig.SkipGenerate {
hasSecretToSkip = true
continue
}
hasSecretToGenerate = true
}
if hasSecretToGenerate && !generateSecrets {
log.Warn(i18n.G("%s requires secret generation before deploy, run \"abra app secret generate %s --all\"", recipe.Name, appDomain))
}
if hasSecretToSkip {
log.Warn(i18n.G("%s requires secret insertion before deploy (#generate=false)", recipe.Name))
}
}
if len(appSecrets) > 0 {

View File

@ -128,6 +128,7 @@ Pass "--all-services/-a" to restart all services.`),
AppName: app.Name,
ServerName: app.Server,
Filters: f,
NoInput: internal.NoInput,
NoLog: true,
Quiet: true,
}
@ -166,7 +167,7 @@ func init() {
AppRestartCommand.Flags().BoolVarP(
&allServices,
i18n.G("all-services"),
i18n.G("a"),
i18n.GC("a", "app restart"),
false,
i18n.G("restart all services"),
)

View File

@ -2,6 +2,7 @@ package app
import (
"errors"
"fmt"
"strings"
appPkg "coopcloud.tech/abra/pkg/app"
@ -177,13 +178,14 @@ beforehand. See "abra app backup" for more.`),
log.Fatal(err)
}
appPkg.ExposeAllEnv(stackName, compose, app.Env)
newRecipeWithDowngradeVersion := fmt.Sprintf("%s:%s", app.Recipe.Name, chosenDowngrade)
appPkg.ExposeAllEnv(stackName, compose, app.Env, newRecipeWithDowngradeVersion)
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
if internal.Chaos {
appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
}
appPkg.SetUpdateLabel(compose, stackName, app.Env)
// Gather secrets
secretInfo, err := deploy.GatherSecretsForDeploy(cl, app, internal.ShowUnchanged)
@ -203,10 +205,15 @@ beforehand. See "abra app backup" for more.`),
log.Fatal(err)
}
deployedVersion := deployMeta.Version
if deployMeta.IsChaos {
deployedVersion = deployMeta.ChaosVersion
}
// NOTE(d1): no release notes implemeneted for rolling back
if err := internal.DeployOverview(
app,
deployMeta.Version,
deployedVersion,
chosenDowngrade,
"",
downgradeWarnMessages,
@ -239,6 +246,7 @@ beforehand. See "abra app backup" for more.`),
stackName,
app.Server,
internal.DontWaitConverge,
internal.NoInput,
f,
); err != nil {
log.Fatal(err)

View File

@ -165,7 +165,7 @@ var AppSecretInsertCommand = &cobra.Command{
Arbitrary secret insertion is not supported. Secrets that are inserted must
match those configured in the recipe beforehand.
This can be useful when you want to manually generate secrets for an app
This command can be useful when you want to manually generate secrets for an app
environment. Typically, you can let Abra generate them for you on app creation
(see "abra app new --secrets/-S" for more).`),
Example: i18n.G(` # insert regular secret
@ -574,7 +574,7 @@ func init() {
AppSecretGenerateCommand.Flags().BoolVarP(
&generateAllSecrets,
i18n.G("all"),
i18n.G("a"),
i18n.GC("a", "app secret generate"),
false,
i18n.G("generate all secrets"),
)
@ -614,7 +614,7 @@ func init() {
AppSecretRmCommand.Flags().BoolVarP(
&rmAllSecrets,
i18n.G("all"),
i18n.G("a"),
i18n.GC("a", "app secret rm"),
false,
i18n.G("remove all secrets"),
)

View File

@ -28,6 +28,7 @@ var AppUndeployCommand = &cobra.Command{
Use: i18n.G("undeploy <domain> [flags]"),
// translators: Short description for `app undeploy` command
Aliases: strings.Split(appUndeployAliases, ","),
Short: i18n.G("Undeploy a deployed app"),
Long: i18n.G(`This does not destroy any application data.
However, you should remain vigilant, as your swarm installation will consider
@ -65,10 +66,15 @@ Passing "--prune/-p" does not remove those volumes.`),
log.Fatal(i18n.G("%s is not deployed?", app.Name))
}
version := deployMeta.Version
if deployMeta.IsChaos {
version = deployMeta.ChaosVersion
}
if err := internal.DeployOverview(
app,
deployMeta.Version,
config.NO_DOMAIN_DEFAULT,
version,
config.MISSING_DEFAULT,
"",
nil,
nil,
@ -110,7 +116,7 @@ Passing "--prune/-p" does not remove those volumes.`),
log.Info(i18n.G("undeploy succeeded 🟢"))
if err := app.WriteRecipeVersion(deployMeta.Version, false); err != nil {
if err := app.WriteRecipeVersion(version, false); err != nil {
log.Fatal(i18n.G("writing recipe version failed: %s", err))
}
},

View File

@ -190,13 +190,14 @@ beforehand. See "abra app backup" for more.`),
log.Fatal(err)
}
appPkg.ExposeAllEnv(stackName, compose, app.Env)
newRecipeWithUpgradeVersion := fmt.Sprintf("%s:%s", app.Recipe.Name, chosenUpgrade)
appPkg.ExposeAllEnv(stackName, compose, app.Env, newRecipeWithUpgradeVersion)
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
if internal.Chaos {
appPkg.SetChaosVersionLabel(compose, stackName, chosenUpgrade)
}
appPkg.SetUpdateLabel(compose, stackName, app.Env)
envVars, err := appPkg.CheckEnv(app)
if err != nil {
@ -241,9 +242,14 @@ beforehand. See "abra app backup" for more.`),
)
}
deployedVersion := deployMeta.Version
if deployMeta.IsChaos {
deployedVersion = deployMeta.ChaosVersion
}
if err := internal.DeployOverview(
app,
deployMeta.Version,
deployedVersion,
chosenUpgrade,
upgradeReleaseNotes,
upgradeWarnMessages,
@ -276,6 +282,7 @@ beforehand. See "abra app backup" for more.`),
stackName,
app.Server,
internal.DontWaitConverge,
internal.NoInput,
f,
); err != nil {
log.Fatal(err)

View File

@ -64,14 +64,14 @@ func DeployOverview(
server = "local"
}
domain := app.Domain
domain := fmt.Sprintf("https://%s", app.Domain)
if domain == "" {
domain = config.NO_DOMAIN_DEFAULT
domain = config.MISSING_DEFAULT
}
envVersion := app.Recipe.EnvVersionRaw
if envVersion == "" {
envVersion = config.NO_VERSION_DEFAULT
envVersion = config.MISSING_DEFAULT
}
rows := [][]string{
@ -140,32 +140,40 @@ func DeployOverview(
}
func getDeployType(currentVersion, newVersion string) string {
if newVersion == config.NO_DOMAIN_DEFAULT {
if newVersion == config.MISSING_DEFAULT {
return i18n.G("UNDEPLOY")
}
if strings.Contains(newVersion, "+U") {
return i18n.G("CHAOS DEPLOY")
}
if strings.Contains(currentVersion, "+U") {
return i18n.G("UNCHAOS DEPLOY")
}
if currentVersion == newVersion {
return ("REDEPLOY")
}
if currentVersion == config.NO_VERSION_DEFAULT {
if currentVersion == config.MISSING_DEFAULT {
return i18n.G("NEW DEPLOY")
}
currentParsed, err := tagcmp.Parse(currentVersion)
if err != nil {
return i18n.G("DEPLOY")
}
newParsed, err := tagcmp.Parse(newVersion)
if err != nil {
return i18n.G("DEPLOY")
}
if currentParsed.IsLessThan(newParsed) {
return i18n.G("UPGRADE")
}
return i18n.G("DOWNGRADE")
}
@ -183,17 +191,17 @@ func MoveOverview(
domain := app.Domain
if domain == "" {
domain = config.NO_DOMAIN_DEFAULT
domain = config.MISSING_DEFAULT
}
secretsOverview := strings.Join(secrets, "\n")
if len(secrets) == 0 {
secretsOverview = config.NO_SECRETS_DEFAULT
secretsOverview = config.MISSING_DEFAULT
}
volumesOverview := strings.Join(volumes, "\n")
if len(volumes) == 0 {
volumesOverview = config.NO_VOLUMES_DEFAULT
volumesOverview = config.MISSING_DEFAULT
}
rows := [][]string{

View File

@ -119,7 +119,7 @@ func init() {
RecipeFetchCommand.Flags().BoolVarP(
&fetchAllRecipes,
i18n.G("all"),
i18n.G("a"),
i18n.GC("a", "recipe fetch"),
false,
i18n.G("fetch all recipes"),
)

View File

@ -135,14 +135,23 @@ your private key and enter your passphrase beforehand.
log.Fatal(err)
}
if tagString == "" && (!internal.Major && !internal.Minor && !internal.Patch) {
var err error
tagString, err = getLabelVersion(recipe, false)
if err != nil {
log.Fatal(err)
labelVersion, err := getLabelVersion(recipe, false)
if err != nil {
log.Fatal(err)
}
for _, tag := range tags {
previousTagLeftHand := strings.Split(tag, "+")[0]
newTagStringLeftHand := strings.Split(labelVersion, "+")[0]
if previousTagLeftHand == newTagStringLeftHand {
log.Fatal(i18n.G("%s+... conflicts with a previous release: %s", newTagStringLeftHand, tag))
}
}
if tagString == "" && (!internal.Major && !internal.Minor && !internal.Patch) {
tagString = labelVersion
}
isClean, err := gitPkg.IsClean(recipe.Dir)
if err != nil {
log.Fatal(err)
@ -321,7 +330,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
if !internal.NoInput {
prompt := &survey.Confirm{
Message: i18n.G("Use release note in release/next?"),
Message: i18n.G("use release note in release/next?"),
}
if err := survey.AskOne(prompt, &addNextAsReleaseNotes); err != nil {

View File

@ -140,13 +140,15 @@ likely to change.
if serviceVersions, ok := recipeVersion[latestRecipeVersion]; ok {
for serviceName := range serviceVersions {
serviceMeta := serviceVersions[serviceName]
changesTable.Row(
[]string{
serviceName,
fmt.Sprintf("%s:%s", serviceMeta.Image, serviceMeta.Tag),
fmt.Sprintf("%s:%s", serviceMeta.Image, imagesTmp[serviceMeta.Image]),
}...,
)
existingImageTag := fmt.Sprintf("%s:%s", serviceMeta.Image, serviceMeta.Tag)
newImageTag := fmt.Sprintf("%s:%s", serviceMeta.Image, imagesTmp[serviceMeta.Image])
if existingImageTag == newImageTag {
continue
}
changesTable.Row([]string{serviceName, existingImageTag, newImageTag}...)
}
}
}

View File

@ -381,7 +381,7 @@ func init() {
RecipeUpgradeCommand.Flags().BoolVarP(
&allTags,
i18n.G("all-tags"),
i18n.G("a"),
i18n.GC("a", "recipe upgrade"),
false,
i18n.G("list all tags, not just upgrades"),
)

View File

@ -283,6 +283,11 @@ Config:
app.AppBackupSnapshotsCommand,
)
app.AppEnvCommand.AddCommand(
app.AppEnvListCommand,
app.AppEnvPullCommand,
)
app.AppCommand.AddCommand(
app.AppBackupCommand,
app.AppCheckCommand,

View File

@ -20,7 +20,7 @@ import (
// translators: `abra server add` aliases. use a comma separated list of
// aliases with no spaces in between
var serverAddAliases = i18n.G("a")
var serverAddAliases = i18n.GC("a", "server add")
var ServerAddCommand = &cobra.Command{
// translators: `server add` command

View File

@ -96,7 +96,7 @@ func init() {
ServerPruneCommand.Flags().BoolVarP(
&allFilter,
i18n.G("all"),
i18n.G("a"),
i18n.GC("a", "server prune"),
false,
i18n.G("remove all unused images"),
)

View File

@ -1,558 +0,0 @@
package updater
import (
"context"
"errors"
"fmt"
"os"
"strconv"
"strings"
"coopcloud.tech/abra/cli/internal"
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/deploy"
"coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/upstream/convert"
"coopcloud.tech/abra/pkg/upstream/stack"
"coopcloud.tech/tagcmp"
charmLog "github.com/charmbracelet/log"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
dockerclient "github.com/docker/docker/client"
"github.com/spf13/cobra"
"coopcloud.tech/abra/pkg/log"
)
const SERVER = "localhost"
// translators: `kadabra notify` aliases. use a comma separated list of aliases
// with no spaces in between
var notifyAliases = i18n.G("n")
// NotifyCommand checks for available upgrades.
var NotifyCommand = &cobra.Command{
// translators: `notify` command
Use: i18n.G("notify [flags]"),
Aliases: strings.Split(notifyAliases, ","),
// translators: Short description for `notify` command
Short: i18n.G("Check for available upgrades"),
Long: i18n.G(`Notify on new versions for deployed apps.
If a new patch/minor version is available, a notification is printed.
Use "--major/-m" to include new major versions.`),
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
cl, err := client.New("default")
if err != nil {
log.Fatal(err)
}
stacks, err := stack.GetStacks(cl)
if err != nil {
log.Fatal(err)
}
for _, stackInfo := range stacks {
stackName := stackInfo.Name
recipeName, err := getLabel(cl, stackName, "recipe")
if err != nil {
log.Fatal(err)
}
if recipeName != "" {
_, err = getLatestUpgrade(cl, stackName, recipeName)
if err != nil {
log.Fatal(err)
}
}
}
},
}
// translators: `kadabra upgrade` aliases. use a comma separated list of aliases with
// no spaces in between
var upgradeAliases = i18n.G("u")
// UpgradeCommand upgrades apps.
var UpgradeCommand = &cobra.Command{
// translators: `app upgrade` command
Use: i18n.G("upgrade [[stack] [recipe] | --all] [flags]"),
Aliases: strings.Split(upgradeAliases, ","),
// translators: Short description for `app upgrade` command
Short: i18n.G("Upgrade apps"),
Long: i18n.G(`Upgrade an app by specifying stack name and recipe.
Use "--all" to upgrade every deployed app.
For each app with auto updates enabled, the deployed version is compared with
the current recipe catalogue version. If a new patch/minor version is
available, the app is upgraded.
To include major versions use the "--major/-m" flag. You probably don't want
that as it will break things. Only apps that are not deployed with "--chaos/-C"
are upgraded, to update chaos deployments use the "--chaos/-C" flag. Use it
with care.`),
Args: cobra.RangeArgs(0, 2),
// TODO(d1): complete stack/recipe
// ValidArgsFunction: func(
// cmd *cobra.Command,
// args []string,
// toComplete string) ([]string, cobra.ShellCompDirective) {
// },
Run: func(cmd *cobra.Command, args []string) {
cl, err := client.New("default")
if err != nil {
log.Fatal(err)
}
if !updateAll && len(args) != 2 {
log.Fatal(i18n.G("missing arguments or --all/-a flag"))
}
if !updateAll {
stackName := args[0]
recipeName := args[1]
err = tryUpgrade(cl, stackName, recipeName)
if err != nil {
log.Fatal(err)
}
return
}
stacks, err := stack.GetStacks(cl)
if err != nil {
log.Fatal(err)
}
for _, stackInfo := range stacks {
stackName := stackInfo.Name
recipeName, err := getLabel(cl, stackName, "recipe")
if err != nil {
log.Fatal(err)
}
err = tryUpgrade(cl, stackName, recipeName)
if err != nil {
log.Fatal(err)
}
}
},
}
// getLabel reads docker labels from running services in the format of "coop-cloud.${STACK_NAME}.${LABEL}".
func getLabel(cl *dockerclient.Client, stackName string, label string) (string, error) {
filter := filters.NewArgs()
filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, stackName))
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filter})
if err != nil {
return "", err
}
for _, service := range services {
labelKey := fmt.Sprintf("coop-cloud.%s.%s", stackName, label)
if labelValue, ok := service.Spec.Labels[labelKey]; ok {
return labelValue, nil
}
}
log.Debug(i18n.G("no %s label found for %s", label, stackName))
return "", nil
}
// getBoolLabel reads a boolean docker label from running services
func getBoolLabel(cl *dockerclient.Client, stackName string, label string) (bool, error) {
lableValue, err := getLabel(cl, stackName, label)
if err != nil {
return false, err
}
if lableValue != "" {
value, err := strconv.ParseBool(lableValue)
if err != nil {
return false, err
}
return value, nil
}
log.Debug(i18n.G("boolean label %s could not be found for %s, set default to false.", label, stackName))
return false, nil
}
// getEnv reads env variables from docker services.
func getEnv(cl *dockerclient.Client, stackName string) (envfile.AppEnv, error) {
envMap := make(map[string]string)
filter := filters.NewArgs()
filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, stackName))
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filter})
if err != nil {
return nil, err
}
for _, service := range services {
envList := service.Spec.TaskTemplate.ContainerSpec.Env
for _, envString := range envList {
splitString := strings.SplitN(envString, "=", 2)
if len(splitString) != 2 {
log.Debug(i18n.G("can't separate key from value: %s (this variable is probably unset)", envString))
continue
}
k := splitString[0]
v := splitString[1]
log.Debugf(i18n.G("for %s read env %s with value: %s from docker service", stackName, k, v))
envMap[k] = v
}
}
return envMap, nil
}
// getLatestUpgrade returns the latest available version for an app respecting
// the "--major" flag if it is newer than the currently deployed version.
func getLatestUpgrade(cl *dockerclient.Client, stackName string, recipeName string) (string, error) {
deployedVersion, err := getDeployedVersion(cl, stackName, recipeName)
if err != nil {
return "", err
}
availableUpgrades, err := getAvailableUpgrades(cl, stackName, recipeName, deployedVersion)
if err != nil {
return "", err
}
if len(availableUpgrades) == 0 {
log.Debugf(i18n.G("no available upgrades for %s", stackName))
return "", nil
}
var chosenUpgrade string
if len(availableUpgrades) > 0 {
chosenUpgrade = availableUpgrades[len(availableUpgrades)-1]
log.Info(i18n.G("%s (%s) can be upgraded from version %s to %s", stackName, recipeName, deployedVersion, chosenUpgrade))
}
return chosenUpgrade, nil
}
// getDeployedVersion returns the currently deployed version of an app.
func getDeployedVersion(cl *dockerclient.Client, stackName string, recipeName string) (string, error) {
log.Debug(i18n.G("retrieve deployed version whether %s is already deployed", stackName))
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
if err != nil {
return "", err
}
if !deployMeta.IsDeployed {
return "", errors.New(i18n.G("%s is not deployed?", stackName))
}
if deployMeta.Version == "unknown" {
return "", errors.New(i18n.G("failed to determine deployed version of %s", stackName))
}
return deployMeta.Version, nil
}
// getAvailableUpgrades returns all available versions of an app that are newer
// than the deployed version. It only includes major upgrades if the "--major"
// flag is set.
func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName string,
deployedVersion string) ([]string, error) {
catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
if err != nil {
return nil, err
}
versions, err := recipe.GetRecipeCatalogueVersions(recipeName, catl)
if err != nil {
return nil, err
}
if len(versions) == 0 {
log.Warn(i18n.G("no published releases for %s in the recipe catalogue?", recipeName))
return nil, nil
}
var availableUpgrades []string
for _, version := range versions {
parsedDeployedVersion, err := tagcmp.Parse(deployedVersion)
if err != nil {
return nil, err
}
parsedVersion, err := tagcmp.Parse(version)
if err != nil {
return nil, err
}
versionDelta, err := parsedDeployedVersion.UpgradeDelta(parsedVersion)
if err != nil {
return nil, err
}
if 0 < versionDelta.UpgradeType() && (versionDelta.UpgradeType() < 4 || includeMajorUpdates) {
availableUpgrades = append(availableUpgrades, version)
}
}
log.Debug(i18n.G("available updates for %s: %s", stackName, availableUpgrades))
return availableUpgrades, nil
}
// processRecipeRepoVersion clones, pulls, checks out the version and lints the
// recipe repository.
func processRecipeRepoVersion(r recipe.Recipe, version string) error {
if err := r.EnsureExists(); err != nil {
return err
}
if err := r.EnsureUpToDate(); err != nil {
return err
}
if _, err := r.EnsureVersion(version); err != nil {
return err
}
if err := lint.LintForErrors(r); err != nil {
return err
}
return nil
}
// createDeployConfig merges and enriches the compose config for the deployment.
func createDeployConfig(r recipe.Recipe, stackName string, env envfile.AppEnv) (*composetypes.Config, stack.Deploy, error) {
env["STACK_NAME"] = stackName
deployOpts := stack.Deploy{
Namespace: stackName,
Prune: false,
ResolveImage: stack.ResolveImageAlways,
Detach: false,
}
composeFiles, err := r.GetComposeFiles(env)
if err != nil {
return nil, deployOpts, err
}
deployOpts.Composefiles = composeFiles
compose, err := appPkg.GetAppComposeConfig(stackName, deployOpts, env)
if err != nil {
return nil, deployOpts, err
}
appPkg.ExposeAllEnv(stackName, compose, env)
// after the upgrade the deployment won't be in chaos state anymore
appPkg.SetChaosLabel(compose, stackName, false)
appPkg.SetRecipeLabel(compose, stackName, r.Name)
appPkg.SetUpdateLabel(compose, stackName, env)
return compose, deployOpts, nil
}
// tryUpgrade performs the upgrade if all the requirements are fulfilled.
func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error {
if recipeName == "" {
log.Debug(i18n.G("don't update %s due to missing recipe name", stackName))
return nil
}
chaos, err := getBoolLabel(cl, stackName, "chaos")
if err != nil {
return err
}
if chaos && !internal.Chaos {
log.Debug(i18n.G("don't update %s due to chaos deployment", stackName))
return nil
}
updatesEnabled, err := getBoolLabel(cl, stackName, "autoupdate")
if err != nil {
return err
}
if !updatesEnabled {
log.Debug(i18n.G("don't update %s due to disabled auto updates or missing ENABLE_AUTO_UPDATE env", stackName))
return nil
}
upgradeVersion, err := getLatestUpgrade(cl, stackName, recipeName)
if err != nil {
return err
}
if upgradeVersion == "" {
log.Debug(i18n.G("don't update %s due to no new version", stackName))
return nil
}
err = upgrade(cl, stackName, recipeName, upgradeVersion)
return err
}
// upgrade performs all necessary steps to upgrade an app.
func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion string) error {
env, err := getEnv(cl, stackName)
if err != nil {
return err
}
app := appPkg.App{
Name: stackName,
Recipe: recipe.Get(recipeName),
Server: SERVER,
Env: env,
}
r := recipe.Get(recipeName)
if err = processRecipeRepoVersion(r, upgradeVersion); err != nil {
return err
}
if err = deploy.MergeAbraShEnv(app.Recipe, app.Env); err != nil {
return err
}
compose, deployOpts, err := createDeployConfig(r, stackName, app.Env)
if err != nil {
return err
}
log.Info(i18n.G("upgrade %s (%s) to version %s", stackName, recipeName, upgradeVersion))
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
if err != nil {
return err
}
f, err := app.Filters(true, false, serviceNames...)
if err != nil {
return err
}
err = stack.RunDeploy(
cl,
deployOpts,
compose,
stackName,
app.Server,
true,
f,
)
return err
}
func newKadabraApp(version, commit string) *cobra.Command {
rootCmd := &cobra.Command{
// translators: `kadabra` binary name
Use: i18n.G("kadabra [cmd] [flags]"),
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
// translators: Short description for `kababra` binary
Short: i18n.G("The Co-op Cloud auto-updater 🤖 🚀"),
PersistentPreRun: func(cmd *cobra.Command, args []string) {
log.Logger.SetStyles(charmLog.DefaultStyles())
charmLog.SetDefault(log.Logger)
if internal.Debug {
log.SetLevel(log.DebugLevel)
log.SetOutput(os.Stderr)
log.SetReportCaller(true)
}
log.Debug(i18n.G("kadabra version %s, commit %s", version, commit))
},
}
rootCmd.PersistentFlags().BoolVarP(
&internal.Debug,
i18n.G("debug"),
i18n.G("d"),
false,
i18n.G("show debug messages"),
)
rootCmd.PersistentFlags().BoolVarP(
&internal.NoInput,
i18n.G("no-input"),
i18n.G("n"),
false,
i18n.G("toggle non-interactive mode"),
)
rootCmd.AddCommand(
NotifyCommand,
UpgradeCommand,
)
return rootCmd
}
// RunApp runs CLI abra app.
func RunApp(version, commit string) {
app := newKadabraApp(version, commit)
if err := app.Execute(); err != nil {
log.Fatal(err)
}
}
var (
includeMajorUpdates bool
updateAll bool
)
func init() {
NotifyCommand.Flags().BoolVarP(
&includeMajorUpdates,
"major",
"m",
false,
"check for major updates",
)
UpgradeCommand.Flags().BoolVarP(
&internal.Chaos,
i18n.G("chaos"),
i18n.G("C"),
false,
i18n.G("ignore uncommitted recipes changes"),
)
UpgradeCommand.Flags().BoolVarP(
&includeMajorUpdates,
i18n.G("major"),
i18n.G("m"),
false,
i18n.G("check for major updates"),
)
UpgradeCommand.Flags().BoolVarP(
&updateAll,
i18n.G("all"),
i18n.G("a"),
false,
i18n.G("update all deployed apps"),
)
}

View File

@ -1,23 +0,0 @@
// Package main provides the command-line entrypoint.
package main
import (
"coopcloud.tech/abra/cli/updater"
)
// Version is the current version of Kadabra.
var Version string
// Commit is the current git commit of Kadabra.
var Commit string
func main() {
if Version == "" {
Version = "dev"
}
if Commit == "" {
Commit = " "
}
updater.RunApp(Version, Commit)
}

106
go.mod
View File

@ -1,28 +1,26 @@
module coopcloud.tech/abra
go 1.24.0
toolchain go1.24.1
go 1.24.2
require (
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/charmbracelet/bubbletea v1.3.6
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/log v0.4.2
github.com/distribution/reference v0.6.0
github.com/docker/cli v28.3.3+incompatible
github.com/docker/docker v28.3.3+incompatible
github.com/docker/cli v29.0.0+incompatible
github.com/docker/docker v28.5.2+incompatible
github.com/docker/go-units v0.5.0
github.com/go-git/go-git/v5 v5.16.2
github.com/go-git/go-git/v5 v5.16.3
github.com/google/go-cmp v0.7.0
github.com/leonelquinteros/gotext v1.7.2
github.com/moby/sys/signal v0.7.1
github.com/moby/term v0.5.2
github.com/pkg/errors v0.9.1
github.com/schollz/progressbar/v3 v3.18.0
golang.org/x/term v0.34.0
golang.org/x/term v0.36.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.2
)
@ -38,17 +36,21 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.3.2 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/charmbracelet/colorprofile v0.3.3 // indirect
github.com/charmbracelet/x/ansi v0.11.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.5.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect
@ -60,30 +62,32 @@ require (
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/compress v1.18.1 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/moby/api v1.52.0 // indirect
github.com/moby/moby/client v0.1.0 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
@ -95,42 +99,42 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/runc v1.1.13 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/prometheus/common v0.67.2 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect
google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.7 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
@ -139,7 +143,7 @@ require (
github.com/containers/image v3.0.2+incompatible
github.com/containers/storage v1.38.2 // indirect
github.com/decentral1se/passgen v1.0.1
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/docker-credential-helpers v0.9.4 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/mux v1.8.1 // indirect
@ -147,11 +151,11 @@ require (
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/prometheus/client_golang v1.23.0 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.11.1
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/sys v0.35.0
golang.org/x/sys v0.38.0
)

235
go.sum
View File

@ -129,26 +129,35 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA=
github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
@ -164,6 +173,14 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
@ -296,8 +313,10 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
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/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
@ -316,19 +335,25 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo=
github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.0.0+incompatible h1:KgsN2RUFMNM8wChxryicn4p46BdQWpXOA1XLGBGPGAw=
github.com/docker/cli v29.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI=
github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
@ -393,6 +418,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
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-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -403,6 +430,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@ -439,7 +468,6 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -528,9 +556,12 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -579,8 +610,8 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
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/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@ -590,6 +621,10 @@ github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
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/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -611,8 +646,8 @@ github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -631,8 +666,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
@ -659,6 +694,10 @@ github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6U
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/client v0.1.0 h1:nt+hn6O9cyJQqq5UWnFGqsZRTS/JirUqzPjEl0Bdc/8=
github.com/moby/moby/client v0.1.0/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
@ -758,8 +797,8 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -775,8 +814,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -790,8 +829,10 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@ -805,8 +846,9 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@ -814,6 +856,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
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/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -839,6 +883,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@ -851,8 +897,8 @@ github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@ -861,9 +907,9 @@ github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bd
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/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/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
@ -878,8 +924,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
@ -937,37 +983,41 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -986,8 +1036,10 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -998,8 +1050,10 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -1063,8 +1117,10 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1082,8 +1138,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1162,13 +1216,17 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1178,16 +1236,20 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -1237,6 +1299,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@ -1281,10 +1345,14 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a h1:DMCgtIAIQGZqJXMVzJF4MV8BlWoJh2ZuFiRdAleyr58=
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 h1:8BWFtrvJRbplrKV5VHlIm4YM726eeBPPAL2QDNWhRrU=
google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 h1:tK4fkUnnRhig9TsTp4otV1FxwBFYgbKUq1RY0V6KZ4U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -1304,8 +1372,10 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
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=
@ -1319,8 +1389,10 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
@ -1358,6 +1430,7 @@ 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=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=

View File

@ -471,13 +471,6 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
result["chaosVersion"] = chaosVersion
}
labelKey = fmt.Sprintf("coop-cloud.%s.autoupdate", name)
if autoUpdate, ok := service.Spec.Labels[labelKey]; ok {
result["autoUpdate"] = autoUpdate
} else {
result["autoUpdate"] = "false"
}
labelKey = fmt.Sprintf("coop-cloud.%s.version", name)
if version, ok := service.Spec.Labels[labelKey]; ok {
result["version"] = version
@ -509,7 +502,11 @@ func GetAppComposeConfig(recipe string, opts stack.Deploy, appEnv envfile.AppEnv
}
// ExposeAllEnv exposes all env variables to the app container
func ExposeAllEnv(stackName string, compose *composetypes.Config, appEnv envfile.AppEnv) {
func ExposeAllEnv(
stackName string,
compose *composetypes.Config,
appEnv envfile.AppEnv,
toDeployVersion string) {
for _, service := range compose.Services {
if service.Name == "app" {
log.Debug(i18n.G("adding env vars to %s service config", stackName))
@ -517,6 +514,11 @@ func ExposeAllEnv(stackName string, compose *composetypes.Config, appEnv envfile
_, exists := service.Environment[k]
if !exists {
value := v
if k == "TYPE" || k == "RECIPE" {
// NOTE(d1): don't use the wrong version from the app env
// since we are deploying a new version
value = toDeployVersion
}
service.Environment[k] = &value
log.Debug(i18n.G("%s: %s: %s", stackName, k, value))
}
@ -631,6 +633,11 @@ func (a App) WipeRecipeVersion() error {
// WriteRecipeVersion writes the recipe version to the app .env file.
func (a App) WriteRecipeVersion(version string, dryRun bool) error {
if version == config.UNKNOWN_DEFAULT {
log.Debug(i18n.G("version is unknown, skipping env write"))
return nil
}
file, err := os.Open(a.Path)
if err != nil {
return err

View File

@ -224,3 +224,16 @@ func TestWriteRecipeVersionOverwrite(t *testing.T) {
assert.Equal(t, "foo", app.Recipe.EnvVersion)
}
func TestWriteRecipeVersionUnknown(t *testing.T) {
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName)
if err != nil {
t.Fatal(err)
}
if err := app.WriteRecipeVersion(config.UNKNOWN_DEFAULT, false); err != nil {
t.Fatal(err)
}
assert.NotEqual(t, config.UNKNOWN_DEFAULT, app.Recipe.EnvVersion)
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"strconv"
"coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
composetypes "github.com/docker/cli/cli/compose/types"
@ -56,23 +55,6 @@ func SetVersionLabel(compose *composetypes.Config, stackName string, version str
}
}
// SetUpdateLabel adds env ENABLE_AUTO_UPDATE as label to enable/disable the
// auto update process for this app. The default if this variable is not set is to disable
// the auto update process.
func SetUpdateLabel(compose *composetypes.Config, stackName string, appEnv envfile.AppEnv) {
for _, service := range compose.Services {
if service.Name == "app" {
enable_auto_update, exists := appEnv["ENABLE_AUTO_UPDATE"]
if !exists {
enable_auto_update = "false"
}
log.Debug(i18n.G("set label 'coop-cloud.%s.autoupdate' to %s for %s", stackName, enable_auto_update, stackName))
labelKey := fmt.Sprintf("coop-cloud.%s.autoupdate", stackName)
service.Deploy.Labels[labelKey] = enable_auto_update
}
}
}
// GetLabel reads docker labels in the format of "coop-cloud.${STACK_NAME}.${LABEL}" from the local compose files
func GetLabel(compose *composetypes.Config, stackName string, label string) string {
for _, service := range compose.Services {

View File

@ -6,9 +6,11 @@ import (
"errors"
"net/http"
"os"
"path"
"strings"
"time"
"coopcloud.tech/abra/pkg/config"
contextPkg "coopcloud.tech/abra/pkg/context"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
@ -35,13 +37,27 @@ func WithTimeout(timeout int) Opt {
// New initiates a new Docker client. New client connections are validated so
// that we ensure connections via SSH to the daemon can succeed. It takes into
// account that you may only want the local client and not communicate via SSH.
// For this use-case, please pass "default" as the contextName.
// For this use-case, please pass "default" as the serverName.
func New(serverName string, opts ...Opt) (*client.Client, error) {
var clientOpts []client.Opt
ctx, err := GetContext(serverName)
if err != nil {
return nil, errors.New(i18n.G("unknown server, run \"abra server add %s\"?", serverName))
serverDir := path.Join(config.SERVERS_DIR, serverName)
if _, err := os.Stat(serverDir); err != nil {
return nil, errors.New(i18n.G("server missing, run \"abra server add %s\"?", serverName))
}
// NOTE(p4u1): when the docker context does not exist but the server folder
// is there, let's create a new docker context.
if err = CreateContext(serverName); err != nil {
return nil, errors.New(i18n.G("server missing context, context creation failed: %s", err))
}
ctx, err = GetContext(serverName)
if err != nil {
return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName))
}
}
ctxEndpoint, err := contextPkg.GetContextEndpoint(ctx)

View File

@ -116,10 +116,7 @@ var (
DIRTY_DEFAULT = "+U"
NO_DOMAIN_DEFAULT = "N/A"
NO_VERSION_DEFAULT = "N/A"
NO_SECRETS_DEFAULT = "N/A"
NO_VOLUMES_DEFAULT = "N/A"
MISSING_DEFAULT = "-"
UNKNOWN_DEFAULT = "unknown"
)

View File

@ -20,6 +20,7 @@ var (
Locale = DefaultLocale
_, Mo = LoadLocale()
G = Mo.Get
GC = Mo.GetC
)
func LoadLocale() (string, *gotext.Mo) {

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -49,7 +49,7 @@ func (r Recipe) Ensure(ctx EnsureContext) error {
if r.EnvVersion != "" && !ctx.IgnoreEnvVersion {
log.Debug(i18n.G("ensuring env version %s", r.EnvVersion))
if strings.Contains(r.EnvVersion, "+U") {
return errors.New(i18n.G("can not redeploy chaos version (%s) without --chaos", r.EnvVersion))
return errors.New(i18n.G(`cannot redeploy previous chaos version (%s), did you mean to use "--chaos"?`))
}
if _, err := r.EnsureVersion(r.EnvVersion); err != nil {

View File

@ -379,7 +379,7 @@ func ReadRecipeCatalogue(offline bool) (RecipeCatalogue, error) {
if !offline {
if err := catalogue.EnsureUpToDate(); err != nil {
return nil, err
return nil, fmt.Errorf("unable to update catalogue: %s", err)
}
}

View File

@ -50,6 +50,11 @@ type Secret struct {
// Will have this remote name:
// test_example_com_test_pass_two_v2
RemoteName string
// LocalName iis the name of the secret in the recipe config. This is also
// the name that you pass to `abra app secret insert` and is shown on `abra
// app secret list`
LocalName string
}
// GeneratePassword generates passwords.
@ -133,7 +138,12 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
lastIdx := strings.LastIndex(secretConfig.Name, "_")
secretVersion := secretConfig.Name[lastIdx+1:]
value := Secret{Version: secretVersion, RemoteName: secretConfig.Name}
value := Secret{
Version: secretVersion,
RemoteName: secretConfig.Name,
LocalName: secretId,
}
if len(value.RemoteName) > config.MAX_DOCKER_SECRET_LENGTH {
return nil, errors.New(i18n.G("secret %s is > %d chars when combined with %s", secretId, config.MAX_DOCKER_SECRET_LENGTH, stackName))
@ -178,6 +188,8 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
// resolveCharset sets the passgen Alphabet required for a secret
func resolveCharset(input string) string {
switch strings.ToLower(input) {
case "hex":
return passgen.AlphabetNumericAmbiguous + "abcdef"
case "special":
return passgen.AlphabetSpecial
case "safespecial":

View File

@ -48,6 +48,12 @@ func TestReadSecretsConfig(t *testing.T) {
assert.Equal(t, "v1", secretsFromConfig["test_pass_six"].Version)
assert.Equal(t, 0, secretsFromConfig["test_pass_six"].Length)
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*_-+=", secretsFromConfig["test_pass_six"].Charset)
// Has a length modifier and a charset=hex modifier
assert.Equal(t, "test_example_com_test_pass_seven_v1", secretsFromConfig["test_pass_seven"].RemoteName)
assert.Equal(t, "v1", secretsFromConfig["test_pass_seven"].Version)
assert.Equal(t, 32, secretsFromConfig["test_pass_seven"].Length)
assert.Equal(t, "0123456789abcdef", secretsFromConfig["test_pass_seven"].Charset)
}
func TestReadSecretsConfigWithLongDomain(t *testing.T) {

View File

@ -4,3 +4,4 @@ SECRET_TEST_PASS_THREE_VERSION=v2
SECRET_TEST_PASS_FOUR_VERSION=v1 # length=12 charset=default,safespecial
SECRET_TEST_PASS_FIVE_VERSION=v1 # length=12 charset=default,special
SECRET_TEST_PASS_SIX_VERSION=v1 # charset=default,special
SECRET_TEST_PASS_SEVEN_VERSION=v1 # length=32 charset=hex

View File

@ -11,6 +11,7 @@ services:
- test_pass_four
- test_pass_five
- test_pass_six
- test_pass_seven
secrets:
test_pass_one:
@ -31,3 +32,6 @@ secrets:
test_pass_six:
external: true
name: ${STACK_NAME}_test_pass_six_${SECRET_TEST_PASS_SIX_VERSION}
test_pass_seven:
external: true
name: ${STACK_NAME}_test_pass_seven_${SECRET_TEST_PASS_SEVEN_VERSION}

View File

@ -247,7 +247,7 @@ func waitOnTasks(ctx context.Context, client apiclient.APIClient, namespace stri
}
}
if terminalStatesReached == len(tasks) {
if terminalStatesReached >= len(tasks) {
log.Debug(i18n.G("all tasks reached terminal state"))
break
}

View File

@ -19,7 +19,7 @@ import (
"coopcloud.tech/abra/pkg/ui"
"coopcloud.tech/abra/pkg/upstream/convert"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/formatter"
"github.com/docker/cli/cli/command/formatter"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
@ -201,6 +201,7 @@ func RunDeploy(
appName string,
serverName string,
dontWait bool,
noInput bool,
filters filters.Args,
) error {
log.Info(i18n.G("initialising deployment"))
@ -226,6 +227,7 @@ func RunDeploy(
appName,
serverName,
dontWait,
noInput,
filters,
)
}
@ -248,6 +250,7 @@ func deployCompose(
appName string,
serverName string,
dontWait bool,
noInput bool,
filters filters.Args,
) error {
namespace := convert.NewNamespace(opts.Namespace)
@ -311,6 +314,7 @@ func deployCompose(
Services: serviceIDs,
AppName: appName,
ServerName: serverName,
NoInput: noInput,
Filters: filters,
}
@ -561,6 +565,7 @@ func timestamp() string {
type WaitOpts struct {
AppName string
Filters filters.Args
NoInput bool
NoLog bool
Quiet bool
ServerName string
@ -570,7 +575,13 @@ type WaitOpts struct {
func WaitOnServices(ctx context.Context, cl *dockerClient.Client, opts WaitOpts) error {
timeout := time.Duration(WaitTimeout) * time.Second
model := ui.DeployInitialModel(ctx, cl, opts.Services, opts.AppName, timeout, opts.Filters)
tui := tea.NewProgram(model)
var tui *tea.Program
if opts.NoInput {
tui = tea.NewProgram(model, tea.WithoutRenderer(), tea.WithInput(nil))
} else {
tui = tea.NewProgram(model)
}
if !opts.Quiet {
log.Info(i18n.G("polling deployment status"))

View File

@ -0,0 +1,5 @@
# cloud-init
This folder contains cloud-init files for installing Abra and its dependencies.
For more information, see <https://cloudinit.readthedocs.io/en/latest/index.html>

View File

@ -0,0 +1,49 @@
#cloud-config
package_update: true
package_upgrade: true
package_reboot_if_required: true
# https://packages.debian.org/bookworm/docker.io
packages:
- ca-certificates
- curl
- docker.io
- docker-compose
# https://stackoverflow.com/a/74084180
- apparmor
# https://docs.coopcloud.tech/operators/tutorial/#server-setup
runcmd:
- curl -fsSL https://install.abra.coopcloud.tech | env HOME=/root bash
- docker swarm init
- docker network create -d overlay proxy
write_files:
# Add abra to PATH and set EDITOR
- path: /etc/profile.d/custom_path.sh
content: |
export PATH=$PATH:$HOME/.local/bin
export EDITOR=vim
owner: root:root
permissions: '0755'
# Send container log to journald: https://docs.coopcloud.tech/operators/handbook/#how-do-i-persist-container-logs-after-they-go-away
- path: /etc/docker/daemon.json
content: |
{
"log-driver": "journald",
"log-opts": {
"labels":"com.docker.swarm.service.name"
}
}
owner: root:root
permissions: '0644'
# Rotate logs
- path: /etc/systemd/journald.conf
content: |
[Journal]
Storage=persistent
SystemMaxUse=5G
MaxFileSec=1month
owner: root:root
permissions: '0644'

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash
ABRA_VERSION="0.11.0-beta"
ABRA_VERSION="0.12.0-beta"
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$ABRA_VERSION"
RC_VERSION="0.11.0-beta"
RC_VERSION="0.12.0-beta"
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$RC_VERSION"
for arg in "$@"; do
@ -14,15 +14,15 @@ done
function show_banner {
echo ""
echo " ____ ____ _ _ "
echo " / ___|___ ___ _ __ / ___| | ___ _ _ __| |"
echo " | | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' |"
echo " | |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| |"
echo " \____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|"
echo " |_|"
echo " ____ ____ _ _ "
echo " / ___|___ ___ _ __ / ___| | ___ _ _ __| |"
echo " | | / _ \ ___ / _ \| '_ \ | | | |/ _ \| | | |/ _' |"
echo " | |__| (_) |___| (_) | |_) | | |___| | (_) | |_| | (_| |"
echo " \____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|"
echo " |_|"
echo ""
echo ""
echo " === Public interest infrastructure === "
echo " === Public interest infrastructure === "
echo ""
echo ""
}

View File

@ -51,7 +51,7 @@ echo "========================================================================"
echo "BUILDING ABRA"
echo "========================================================================"
export PATH="/usr/lib/go-1.21/bin:$PATH"
make build-abra
make build
echo "========================================================================"
echo "========================================================================"

View File

@ -175,7 +175,7 @@ teardown(){
}
# bats test_tags=slow
@test "bail if env has a hash but no --chaos" {
@test "do not bail if env version is a hash but no --chaos" {
wantHash=$(_get_n_hash 3)
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
@ -250,6 +250,7 @@ teardown(){
run $ABRA app deploy "$TEST_APP_DOMAIN" \
--no-input --no-converge-checks --chaos
assert_success
assert_output --regexp "NEW DEPLOYMENT.*${_get_head_hash:0:8}"
}
# bats test_tags=slow
@ -367,6 +368,21 @@ teardown(){
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
assert_failure
assert_output --partial "secret not generated"
}
@test "error if secret not inserted" {
run sed -i 's/COMPOSE_FILE="compose.yml"/COMPOSE_FILE="compose.yml:compose.skip_pass.yml"/g' \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run sed -i 's/#SECRET_TEST_SKIP_PASS_VERSION=v1/SECRET_TEST_SKIP_PASS_VERSION=v1/g' \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
assert_failure
assert_output --partial "secret not inserted"
}
# bats test_tags=slow
@ -527,7 +543,7 @@ teardown(){
# bats test_tags=slow
@test "ignore timeout when not present in env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
assert_success
refute_output --partial "timeout: set to"
}
@ -538,6 +554,7 @@ teardown(){
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
# NOTE(d1}: --debug required
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug
assert_success
assert_output --partial "timeout: set to 120"
@ -561,3 +578,27 @@ teardown(){
assert_success
refute_output --partial "IMAGES"
}
# bats test_tags=slow
@test "re-deploy updates existing env vars" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "WITH_COMMENT=foo"
run sed -i 's/WITH_COMMENT=foo/WITH_COMMENT=bar/g' \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --force
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
refute_output --partial "WITH_COMMENT=foo"
assert_output --partial "WITH_COMMENT=bar"
}

View File

@ -127,3 +127,14 @@ teardown(){
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
}
# bats test_tags=slow
@test "new env version written to container env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "$TEST_RECIIPE:0.1.0+1.20.0"
}

View File

@ -38,8 +38,8 @@ teardown(){
assert_success
assert_output --partial 'NEW DEPLOY OVERVIEW'
assert_output --partial 'CURRENT DEPLOYMENT N/A'
assert_output --partial 'ENV VERSION N/A'
assert_output --partial 'CURRENT DEPLOYMENT -'
assert_output --partial 'ENV VERSION -'
assert_output --partial "NEW DEPLOYMENT ${latestRelease}"
assert_output --partial "IMAGES nginx: ${latestRelease##*+} (new)"
assert_output --partial "CONFIGS test_conf: v1 (new)"
@ -57,7 +57,7 @@ teardown(){
assert_success
assert_output --partial 'NEW DEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT N/A"
assert_output --partial "CURRENT DEPLOYMENT -"
assert_output --partial "ENV VERSION ${latestRelease}"
assert_output --partial "NEW DEPLOYMENT ${latestRelease}"
assert_output --partial "IMAGES nginx: ${latestRelease##*+} (new)"
@ -68,6 +68,13 @@ teardown(){
assert_success
}
@test "domain shown with https" {
run $ABRA app deploy "$TEST_APP_DOMAIN" \
--no-input --no-converge-checks
assert_success
assert_output --partial "https://$TEST_DOMAIN"
}
# bats test_tags=slow
@test "show changed config version on re-deploy" {
run $ABRA app deploy "$TEST_APP_DOMAIN" \
@ -102,7 +109,7 @@ teardown(){
assert_success
assert_output --partial 'NEW DEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT N/A"
assert_output --partial "CURRENT DEPLOYMENT -"
assert_output --partial "ENV VERSION 0.1.1+1.20.2"
assert_output --partial "NEW DEPLOYMENT 0.1.1+1.20.2"
@ -125,7 +132,7 @@ teardown(){
assert_success
assert_output --partial 'NEW DEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT N/A"
assert_output --partial "CURRENT DEPLOYMENT -"
assert_output --partial "ENV VERSION 0.1.1+1.20.2"
assert_output --partial "NEW DEPLOYMENT ${latestRelease}"
@ -163,7 +170,7 @@ teardown(){
assert_success
}
@test "can not redeploy chaos version without --chaos" {
@test "cannot redeploy chaos version without --chaos" {
headHash=$(_get_head_hash)
latestRelease=$(_latest_release)
@ -181,7 +188,7 @@ teardown(){
run $ABRA app deploy "$TEST_APP_DOMAIN" \
--no-input --no-converge-checks --force --debug
assert_failure
assert_output --regexp 'can not redeploy chaos version .*' + "${headHash:0:8}+U"
assert_output --regexp 'cannot redeploy previous chaos version .*' + "${headHash:0:8}+U"
}
@test "deploy then force commit deploy" {
@ -219,7 +226,7 @@ teardown(){
assert_success
assert_output --partial 'NEW DEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT N/A"
assert_output --partial "CURRENT DEPLOYMENT -"
assert_output --partial "ENV VERSION ${latestRelease}"
assert_output --partial "NEW DEPLOYMENT ${headHash:0:8}"

View File

@ -28,17 +28,17 @@ teardown(){
}
@test "validate app argument" {
run $ABRA app env
run $ABRA app env list
assert_failure
run $ABRA app env DOESNTEXIST
run $ABRA app env list DOESNTEXIST
assert_failure
}
@test "show env version" {
latestRelease=$(_latest_release)
run $ABRA app env "$TEST_APP_DOMAIN"
run $ABRA app env list "$TEST_APP_DOMAIN"
assert_success
assert_output --partial "$latestRelease"
}
@ -48,7 +48,7 @@ teardown(){
assert_success
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
run $ABRA app env "$TEST_APP_DOMAIN"
run $ABRA app env list "$TEST_APP_DOMAIN"
assert_success
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
@ -57,3 +57,44 @@ teardown(){
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
}
@test "app env pull explodes when no deployed app" {
run $ABRA app env pull "$TEST_APP_DOMAIN" -s "$TEST_SERVER"
assert_failure
}
# bats test_tags=slow
@test "app env pull recreates app env when missing" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
assert_success
run rm -rf "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
run $ABRA app env pull "$TEST_APP_DOMAIN" -s "$TEST_SERVER"
assert_success
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
}
# bats test_tags=slow
@test "app env pull recreates correct version" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input
assert_success
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run rm -rf "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
run $ABRA app env pull "$TEST_APP_DOMAIN" -s "$TEST_SERVER"
assert_success
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
}

View File

@ -15,6 +15,7 @@ teardown_file(){
_undeploy_app
_rm_app
_rm_server
_reset_recipe
if [[ -d "$ABRA_DIR/servers/foo" ]]; then
run rm -rf "$ABRA_DIR/servers/foo"
@ -159,23 +160,6 @@ teardown(){
assert_not_exists "$ABRA_DIR/servers/foo.com"
}
@test "list with status skips unknown servers" {
if [[ ! -d "$ABRA_DIR/servers/foo" ]]; then
run mkdir -p "$ABRA_DIR/servers/foo"
assert_success
assert_exists "$ABRA_DIR/servers/foo"
run cp "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" \
"$ABRA_DIR/servers/foo/$TEST_APP_DOMAIN.env"
assert_success
assert_exists "$ABRA_DIR/servers/foo/$TEST_APP_DOMAIN.env"
fi
run $ABRA app ls --status
assert_success
assert_output --partial "unknown server"
}
# bats test_tags=slow
@test "list does not fail if missing .env" {
_deploy_app
@ -193,3 +177,16 @@ teardown(){
<(jq -S "." <(echo '{}'))
assert_success
}
# bats test_tags=slow
@test "list ignores borked tags" {
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag \
-a "2.4.8_1" -m "feat: completely borked tag"
assert_success
_deploy_app
run $ABRA app ls --status --debug
assert_success
assert_output --partial "unable to parse 2.4.8_1"
}

View File

@ -22,8 +22,15 @@ teardown(){
_reset_recipe
_reset_tags
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
if [[ -f "$ABRA_DIR/recipes/$TEST_RECIPE/foo" ]]; then
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
fi
if [[ -f "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env" ]]; then
run rm -rf "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env"
assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env"
fi
}
@test "create new app" {
@ -270,3 +277,32 @@ teardown(){
assert_success
refute_output --partial "requires secret generation"
}
@test "do not warn about generation when generate=false" {
run $ABRA app new --domain "$TEST_APP_DOMAIN" renovate "1.0.1+41-full"
assert_success
refute_output --partial "requires secret generation"
}
@test "warn about insertion when generate=false" {
run $ABRA app new --domain "$TEST_APP_DOMAIN" renovate "1.0.1+41-full"
assert_success
assert_output --partial "requires secret insertion"
}
@test "warn about both insert/generate when generate=false/true" {
run $ABRA app new rauthy "1.0.0+0.32.3" \
--no-input \
--server "$TEST_SERVER" \
--domain "rauthy.$TEST_APP_DOMAIN"
assert_success
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env"
assert_output --partial "requires secret generation"
assert_output --partial "requires secret insertion"
}
@test "no warn about generation if already generated" {
run $ABRA app new "$TEST_RECIPE" --domain "$TEST_APP_DOMAIN" --secrets
assert_success
refute_output --partial "requires secret generation"
}

View File

@ -181,7 +181,7 @@ teardown(){
}
# bats test_tags=slow
@test "rollback chaos deployment is not possible" {
@test "rollback chaos deployment is possible" {
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success
@ -191,12 +191,13 @@ teardown(){
assert_output --partial "${tagHash:0:8}"
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks
assert_failure
assert_output --partial 'current deployment' + "${tagHash:0:8}" + 'is not a known version'
assert_success
assert_output --regexp "CURRENT DEPLOYMENT.*${tagHash:0:8}"
assert_output --regexp "ENV VERSION.*${tagHash:0:8}"
}
# bats test_tags=slow
@test "chaos commit rollback not possible" {
@test "specific chaos commit rollback not possible" {
_deploy_app
tagHash=$(_get_tag_hash "0.2.0+1.21.0")

View File

@ -33,10 +33,29 @@ teardown(){
assert_success
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" \
--no-input --no-converge-checks --debug
--no-input --no-converge-checks
assert_success
run grep -q "TYPE=abra-test-recipe:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
}
# bats test_tags=slow
@test "new env version written to container env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input
assert_success
run grep -q "TYPE=abra-test-recipe:0.2.0+1.21.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" \
--no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "$TEST_RECIIPE:0.1.0+1.20.0"
}

View File

@ -75,7 +75,7 @@ teardown(){
assert_output --partial 'DOWNGRADE OVERVIEW'
assert_output --partial 'CURRENT DEPLOYMENT 0.2.0+1.21.0'
assert_output --partial 'ENV VERSION N/A'
assert_output --partial 'ENV VERSION -'
assert_output --partial 'NEW DEPLOYMENT 0.1.0+1.20.0'
run grep -q "TYPE=$TEST_RECIPE:${latestRelease}" \

View File

@ -106,6 +106,7 @@ teardown(){
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input
assert_success
assert_output --regexp "CURRENT DEPLOYMENT.*${_get_head_hash:0:8}"
}
# bats test_tags=slow

View File

@ -36,7 +36,7 @@ teardown(){
assert_output --partial 'UNDEPLOY OVERVIEW'
assert_output --partial 'CURRENT DEPLOYMENT 0.1.0+1.20.0'
assert_output --partial 'ENV VERSION 0.1.0+1.20.0'
assert_output --partial 'NEW DEPLOYMENT N/A'
assert_output --partial 'NEW DEPLOYMENT -'
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
@ -57,7 +57,7 @@ teardown(){
assert_output --partial 'UNDEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT ${headHash:0:8}"
assert_output --partial "ENV VERSION ${headHash:0:8}"
assert_output --partial 'NEW DEPLOYMENT N/A'
assert_output --partial 'NEW DEPLOYMENT -'
run grep -q "TYPE=$TEST_RECIPE:${headHash:0:8}" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
@ -81,7 +81,7 @@ teardown(){
assert_output --partial 'UNDEPLOY OVERVIEW'
assert_output --partial "CURRENT DEPLOYMENT ${headHash:0:8}+U"
assert_output --partial "ENV VERSION ${headHash:0:8}+U"
assert_output --partial 'NEW DEPLOYMENT N/A'
assert_output --partial 'NEW DEPLOYMENT -'
run grep -q "TYPE=$TEST_RECIPE:${headHash:0:8}" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"

View File

@ -256,7 +256,7 @@ teardown(){
}
# bats test_tags=slow
@test "upgrade commit deployment not possible" {
@test "specific version upgrade after chaos deploy" {
tagHash=$(_get_tag_hash "0.1.0+1.20.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success
@ -266,20 +266,29 @@ teardown(){
assert_output --partial "${tagHash:0:8}"
run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks
assert_failure
assert_output --partial "not a known version"
assert_success
assert_output --regexp "CURRENT DEPLOYMENT.*${tagHash:0:8}"
assert_output --regexp "ENV VERSION.*${tagHash:0:8}"
assert_output --regexp "NEW DEPLOYMENT.*0\.1\.1\+1\.20\.2"
}
@test "chaos commit upgrade not possible" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
# bats test_tags=slow
@test "upgrade to latest after chaos deploy" {
latestRelease=$(_latest_release)
tagHash=$(_get_tag_hash "0.1.0+1.20.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success
assert_output --partial '0.1.0+1.20.0'
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --chaos
assert_success
assert_output --partial "${tagHash:0:8}"
run $ABRA app upgrade "$TEST_APP_DOMAIN" "$tagHash" --no-input --no-converge-checks
assert_failure
assert_output --partial "not a known version"
run $ABRA app upgrade "$TEST_APP_DOMAIN" --no-input --no-converge-checks
assert_success
assert_output --regexp "CURRENT DEPLOYMENT.*${tagHash:0:8}"
assert_output --regexp "ENV VERSION.*${tagHash:0:8}"
assert_output --partial "${latestRelease}"
}
# bats test_tags=slow

View File

@ -40,3 +40,21 @@ teardown(){
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
}
# bats test_tags=slow
@test "new env version written to container env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input
assert_success
run grep -q "TYPE=abra-test-recipe:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "$TEST_RECIIPE:0.2.0+1.21.0"
}

View File

@ -75,7 +75,7 @@ teardown(){
assert_output --partial 'UPGRADE OVERVIEW'
assert_output --partial 'CURRENT DEPLOYMENT 0.2.0+1.21.0'
assert_output --partial 'ENV VERSION N/A'
assert_output --partial 'ENV VERSION -'
assert_output --partial "NEW DEPLOYMENT $latestRelease"
run grep -q "TYPE=$TEST_RECIPE:${latestRelease}" \

View File

@ -53,7 +53,8 @@ teardown(){
--domain "foobar.$TEST_SERVER"
assert_success
run $ABRA app deploy "foobar.$TEST_SERVER" --no-input
run $ABRA app deploy "foobar.$TEST_SERVER" \
--no-input --no-converge-checks
assert_success
}

View File

@ -101,6 +101,9 @@ teardown() {
assert_success
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
run $ABRA recipe sync "$TEST_RECIPE" --no-input --patch
assert_success
run $ABRA recipe release "$TEST_RECIPE" --no-input --patch
assert_success
assert_output --partial 'no -p/--publish passed, not publishing'
@ -119,6 +122,9 @@ teardown() {
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" commit -m "added some release notes"
assert_success
run $ABRA recipe sync "$TEST_RECIPE" --no-input --patch
assert_success
run $ABRA recipe release "$TEST_RECIPE" --no-input --minor
assert_success
assert_output --partial 'no -p/--publish passed, not publishing'
@ -127,3 +133,27 @@ teardown() {
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/release/0.4.0+1.21.0"
assert_file_contains "$ABRA_DIR/recipes/$TEST_RECIPE/release/0.4.0+1.21.0" "those are some release notes for the next release"
}
@test "recipe release conflict fails" {
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success
run sed -i "s/nginx:1.21.0/nginx:1.29.1/g" "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml"
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
assert_success
assert_output --regexp 'nginx:1.29.1'
run sed -i "s/0.2.0+1.21.0/0.2.0+1.29.1/g" "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml"
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.2\.0\+1\.29\.1'
run $ABRA recipe release "$TEST_RECIPE" --no-input --minor
assert_failure
assert_output --partial '0.2.0+... conflicts with a previous release: 0.2.0+1.21.0'
}

View File

@ -19,6 +19,10 @@ setup(){
teardown(){
_reset_recipe
_reset_tags
if [[ -d "$ABRA_DIR/recipes/foobar" ]]; then
run rm -rf "$ABRA_DIR/recipes/foobar"
assert_success
fi
}
@test "validate recipe argument" {
@ -126,3 +130,71 @@ teardown(){
assert_line --index 0 --partial 'synced label'
refute_line --index 1 --partial 'synced label'
}
@test "sync with no tags or previous release" {
_remove_tags
run $ABRA recipe upgrade "$TEST_RECIPE" --no-input --patch
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
assert_success
assert_output --partial 'image: nginx:1.21.6'
# NOTE(d1): ensure the latest tag is the one we expect
_remove_tags
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag \
-a "0.3.0+1.21.0" -m "fake: 0.3.0+1.21.0"
assert_success
run $ABRA recipe sync "$TEST_RECIPE" --no-input --patch
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.3\.1\+1\.2.*'
}
@test "sync recipe without input fails with prompt" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run $ABRA recipe sync foobar --no-input --patch
assert_failure
assert_output --partial "input required for initial version"
}
@test "sync new recipe: development release" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run bash -c "echo 0.1.0 | $ABRA recipe sync foobar --patch"
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.1\.0\+1\.2.*'
}
@test "sync new recipe: public release" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run bash -c "echo 1.0.0 | $ABRA recipe sync foobar --patch"
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=1\.0\.0\+1\.2.*'
}
@test "sync newly created recipe with no version label" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run sed -i 's/- "coop-cloud.${STACK_NAME}.version="/#- "coop-cloud.${STACK_NAME}.version="/g' \
"$ABRA_DIR/recipes/foobar/compose.yml"
assert_success
run bash -c "echo 0.1.0 | $ABRA recipe sync foobar --patch"
assert_failure
assert_output --partial "automagic insertion not supported yet"
}

View File

@ -12,11 +12,7 @@ setup_suite(){
fi
if [[ ! -f "$PWD/abra" ]]; then
make build-abra
fi
if [[ ! -f "$PWD/kadabra" ]]; then
make build-kadabra
make
fi
if [[ -d "$ABRA_DIR" ]]; then

View File

@ -26,6 +26,10 @@ linters:
- whitespace
- wrapcheck
exclusions:
rules:
- text: '(slog|log)\.\w+'
linters:
- noctx
generated: lax
presets:
- common-false-positives

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2023 Charmbracelet, Inc
Copyright (c) 2020-2025 Charmbracelet, Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -9,7 +9,7 @@
<br>
<a href="https://github.com/charmbracelet/bubbletea/releases"><img src="https://img.shields.io/github/release/charmbracelet/bubbletea.svg" alt="Latest Release"></a>
<a href="https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc"><img src="https://godoc.org/github.com/charmbracelet/bubbletea?status.svg" alt="GoDoc"></a>
<a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg" alt="Build Status"></a>
<a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status"></a>
</p>
The fun, functional and stateful way to build terminal apps. A Go framework
@ -395,6 +395,6 @@ of days past.
Part of [Charm](https://charm.sh).
<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>
<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-banner-next.jpg" width="400"></a>
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة

View File

@ -13,6 +13,27 @@ import (
// return tea.Batch(someCommand, someOtherCommand)
// }
func Batch(cmds ...Cmd) Cmd {
return compactCmds[BatchMsg](cmds)
}
// BatchMsg is a message used to perform a bunch of commands concurrently with
// no ordering guarantees. You can send a BatchMsg with Batch.
type BatchMsg []Cmd
// Sequence runs the given commands one at a time, in order. Contrast this with
// Batch, which runs commands concurrently.
func Sequence(cmds ...Cmd) Cmd {
return compactCmds[sequenceMsg](cmds)
}
// sequenceMsg is used internally to run the given commands in order.
type sequenceMsg []Cmd
// compactCmds ignores any nil commands in cmds, and returns the most direct
// command possible. That is, considering the non-nil commands, if there are
// none it returns nil, if there is exactly one it returns that command
// directly, else it returns the non-nil commands as type T.
func compactCmds[T ~[]Cmd](cmds []Cmd) Cmd {
var validCmds []Cmd //nolint:prealloc
for _, c := range cmds {
if c == nil {
@ -27,26 +48,11 @@ func Batch(cmds ...Cmd) Cmd {
return validCmds[0]
default:
return func() Msg {
return BatchMsg(validCmds)
return T(validCmds)
}
}
}
// BatchMsg is a message used to perform a bunch of commands concurrently with
// no ordering guarantees. You can send a BatchMsg with Batch.
type BatchMsg []Cmd
// Sequence runs the given commands one at a time, in order. Contrast this with
// Batch, which runs commands concurrently.
func Sequence(cmds ...Cmd) Cmd {
return func() Msg {
return sequenceMsg(cmds)
}
}
// sequenceMsg is used internally to run the given commands in order.
type sequenceMsg []Cmd
// Every is a command that ticks in sync with the system clock. So, if you
// wanted to tick with the system clock every second, minute or hour you
// could use this. It's also handy for having different things tick in sync.

View File

@ -108,7 +108,7 @@ func prepareConsole(input windows.Handle, modes ...uint32) (originalMode uint32,
return originalMode, nil
}
// cancelMixin represents a goroutine-safe cancelation status.
// cancelMixin represents a goroutine-safe cancellation status.
type cancelMixin struct {
unsafeCanceled bool
lock sync.Mutex

View File

@ -109,12 +109,12 @@ func peekAndReadConsInput(con *conInputReader) ([]coninput.InputRecord, error) {
return events, nil
}
// Convert i to unit32 or panic if it cannot be converted. Check satisifes lint G115.
// Convert i to unit32 or panic if it cannot be converted. Check satisfies lint G115.
func intToUint32OrDie(i int) uint32 {
if i < 0 {
panic("cannot convert numEvents " + fmt.Sprint(i) + " to uint32")
}
return uint32(i)
return uint32(i) //nolint:gosec
}
// Keeps peeking until there is data or the input is cancelled.
@ -158,16 +158,16 @@ func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, action Mou
return button, action
}
switch {
case btn == coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
switch btn {
case coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button
button = MouseButtonLeft
case btn == coninput.RIGHTMOST_BUTTON_PRESSED: // right button
case coninput.RIGHTMOST_BUTTON_PRESSED: // right button
button = MouseButtonRight
case btn == coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
case coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button
button = MouseButtonMiddle
case btn == coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
case coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward)
button = MouseButtonBackward
case btn == coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
case coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward)
button = MouseButtonForward
}

View File

@ -131,7 +131,7 @@ func EnableBracketedPaste() Msg {
type enableBracketedPasteMsg struct{}
// DisableBracketedPaste is a special command that tells the Bubble Tea program
// to accept bracketed paste input.
// to stop processing bracketed paste input.
//
// Note that bracketed paste will be automatically disabled when the
// program quits.

View File

@ -277,7 +277,7 @@ func (r *standardRenderer) flush() {
// using the full terminal window.
buf.WriteString(ansi.CursorPosition(0, len(newLines)))
} else {
buf.WriteString(ansi.CursorBackward(r.width))
buf.WriteByte('\r')
}
_, _ = r.out.Write(buf.Bytes())

View File

@ -24,7 +24,6 @@ import (
"github.com/charmbracelet/x/term"
"github.com/muesli/cancelreader"
"golang.org/x/sync/errgroup"
)
// ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic.
@ -73,7 +72,7 @@ const (
customInput
)
// String implements the stringer interface for [inputType]. It is inteded to
// String implements the stringer interface for [inputType]. It is intended to
// be used in testing.
func (i inputType) String() string {
return [...]string{
@ -220,7 +219,7 @@ func Suspend() Msg {
// You can send this message with [Suspend()].
type SuspendMsg struct{}
// ResumeMsg can be listen to to do something once a program is resumed back
// ResumeMsg can be listen to do something once a program is resumed back
// from a suspend state.
type ResumeMsg struct{}
@ -472,42 +471,12 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
p.exec(msg.cmd, msg.fn)
case BatchMsg:
for _, cmd := range msg {
select {
case <-p.ctx.Done():
return model, nil
case cmds <- cmd:
}
}
go p.execBatchMsg(msg)
continue
case sequenceMsg:
go func() {
// Execute commands one at a time, in order.
for _, cmd := range msg {
if cmd == nil {
continue
}
msg := cmd()
if batchMsg, ok := msg.(BatchMsg); ok {
g, _ := errgroup.WithContext(p.ctx)
for _, cmd := range batchMsg {
cmd := cmd
g.Go(func() error {
p.Send(cmd())
return nil
})
}
//nolint:errcheck,gosec
g.Wait() // wait for all commands from batch msg to finish
continue
}
p.Send(msg)
}
}()
go p.execSequenceMsg(msg)
continue
case setWindowTitleMsg:
p.SetWindowTitle(string(msg))
@ -535,6 +504,74 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
}
}
func (p *Program) execSequenceMsg(msg sequenceMsg) {
if !p.startupOptions.has(withoutCatchPanics) {
defer func() {
if r := recover(); r != nil {
p.recoverFromGoPanic(r)
}
}()
}
// Execute commands one at a time, in order.
for _, cmd := range msg {
if cmd == nil {
continue
}
msg := cmd()
switch msg := msg.(type) {
case BatchMsg:
p.execBatchMsg(msg)
case sequenceMsg:
p.execSequenceMsg(msg)
default:
p.Send(msg)
}
}
}
func (p *Program) execBatchMsg(msg BatchMsg) {
if !p.startupOptions.has(withoutCatchPanics) {
defer func() {
if r := recover(); r != nil {
p.recoverFromGoPanic(r)
}
}()
}
// Execute commands one at a time.
var wg sync.WaitGroup
for _, cmd := range msg {
if cmd == nil {
continue
}
wg.Add(1)
go func() {
defer wg.Done()
if !p.startupOptions.has(withoutCatchPanics) {
defer func() {
if r := recover(); r != nil {
p.recoverFromGoPanic(r)
}
}()
}
msg := cmd()
switch msg := msg.(type) {
case BatchMsg:
p.execBatchMsg(msg)
case sequenceMsg:
p.execSequenceMsg(msg)
default:
p.Send(msg)
}
}()
}
wg.Wait() // wait for all commands from batch msg to finish
}
// Run initializes the program and runs its event loops, blocking until it gets
// terminated by either [Program.Quit], [Program.Kill], or its signal handler.
// Returns the final model.

View File

@ -56,7 +56,7 @@ func (p *Program) initInput() (err error) {
// Open the Windows equivalent of a TTY.
func openInputTTY() (*os.File, error) {
f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644)
f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644) //nolint:gosec
if err != nil {
return nil, fmt.Errorf("error opening file: %w", err)
}

View File

@ -153,29 +153,24 @@ func envColorProfile(env environ) (p Profile) {
p = ANSI
}
parts := strings.Split(term, "-")
switch parts[0] {
case "alacritty",
"contour",
"foot",
"ghostty",
"kitty",
"rio",
"st",
"wezterm":
switch {
case strings.Contains(term, "alacritty"),
strings.Contains(term, "contour"),
strings.Contains(term, "foot"),
strings.Contains(term, "ghostty"),
strings.Contains(term, "kitty"),
strings.Contains(term, "rio"),
strings.Contains(term, "st"),
strings.Contains(term, "wezterm"):
return TrueColor
case "xterm":
if len(parts) > 1 {
switch parts[1] {
case "ghostty", "kitty":
// These terminals can be defined as xterm-TERMNAME
return TrueColor
}
}
case "tmux", "screen":
case strings.HasPrefix(term, "tmux"), strings.HasPrefix(term, "screen"):
if p < ANSI256 {
p = ANSI256
}
case strings.HasPrefix(term, "xterm"):
if p < ANSI {
p = ANSI
}
}
if isCloudShell, _ := strconv.ParseBool(env.get("GOOGLE_CLOUD_SHELL")); isCloudShell {

24
vendor/github.com/charmbracelet/x/ansi/inband.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
package ansi
import "fmt"
// InBandResize encodes an in-band terminal resize event sequence.
//
// CSI 48 ; height_cells ; widht_cells ; height_pixels ; width_pixels t
//
// See https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83
func InBandResize(heightCells, widthCells, heightPixels, widthPixels int) string {
if heightCells < 0 {
heightCells = 0
}
if widthCells < 0 {
widthCells = 0
}
if heightPixels < 0 {
heightPixels = 0
}
if widthPixels < 0 {
widthPixels = 0
}
return fmt.Sprintf("\x1b[48;%d;%d;%d;%dt", heightCells, widthCells, heightPixels, widthPixels)
}

View File

@ -108,7 +108,7 @@ func DECRST(modes ...Mode) string {
func setMode(reset bool, modes ...Mode) (s string) {
if len(modes) == 0 {
return //nolint:nakedret
return s
}
cmd := "h"
@ -142,7 +142,7 @@ func setMode(reset bool, modes ...Mode) (s string) {
if len(dec) > 0 {
s += seq + "?" + strings.Join(dec, ";") + cmd
}
return //nolint:nakedret
return s
}
// RequestMode (DECRQM) returns a sequence to request a mode from the terminal.
@ -228,12 +228,12 @@ func (m DECMode) Mode() int {
//
// See: https://vt100.net/docs/vt510-rm/KAM.html
const (
KeyboardActionMode = ANSIMode(2)
KAM = KeyboardActionMode
ModeKeyboardAction = ANSIMode(2)
KAM = ModeKeyboardAction
SetKeyboardActionMode = "\x1b[2h"
ResetKeyboardActionMode = "\x1b[2l"
RequestKeyboardActionMode = "\x1b[2$p"
SetModeKeyboardAction = "\x1b[2h"
ResetModeKeyboardAction = "\x1b[2l"
RequestModeKeyboardAction = "\x1b[2$p"
)
// Insert/Replace Mode (IRM) is a mode that determines whether characters are
@ -245,12 +245,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/IRM.html
const (
InsertReplaceMode = ANSIMode(4)
IRM = InsertReplaceMode
ModeInsertReplace = ANSIMode(4)
IRM = ModeInsertReplace
SetInsertReplaceMode = "\x1b[4h"
ResetInsertReplaceMode = "\x1b[4l"
RequestInsertReplaceMode = "\x1b[4$p"
SetModeInsertReplace = "\x1b[4h"
ResetModeInsertReplace = "\x1b[4l"
RequestModeInsertReplace = "\x1b[4$p"
)
// BiDirectional Support Mode (BDSM) is a mode that determines whether the
@ -260,12 +260,12 @@ const (
//
// See ECMA-48 7.2.1.
const (
BiDirectionalSupportMode = ANSIMode(8)
BDSM = BiDirectionalSupportMode
ModeBiDirectionalSupport = ANSIMode(8)
BDSM = ModeBiDirectionalSupport
SetBiDirectionalSupportMode = "\x1b[8h"
ResetBiDirectionalSupportMode = "\x1b[8l"
RequestBiDirectionalSupportMode = "\x1b[8$p"
SetModeBiDirectionalSupport = "\x1b[8h"
ResetModeBiDirectionalSupport = "\x1b[8l"
RequestModeBiDirectionalSupport = "\x1b[8$p"
)
// Send Receive Mode (SRM) or Local Echo Mode is a mode that determines whether
@ -274,17 +274,17 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/SRM.html
const (
SendReceiveMode = ANSIMode(12)
LocalEchoMode = SendReceiveMode
SRM = SendReceiveMode
ModeSendReceive = ANSIMode(12)
ModeLocalEcho = ModeSendReceive
SRM = ModeSendReceive
SetSendReceiveMode = "\x1b[12h"
ResetSendReceiveMode = "\x1b[12l"
RequestSendReceiveMode = "\x1b[12$p"
SetModeSendReceive = "\x1b[12h"
ResetModeSendReceive = "\x1b[12l"
RequestModeSendReceive = "\x1b[12$p"
SetLocalEchoMode = "\x1b[12h"
ResetLocalEchoMode = "\x1b[12l"
RequestLocalEchoMode = "\x1b[12$p"
SetModeLocalEcho = "\x1b[12h"
ResetModeLocalEcho = "\x1b[12l"
RequestModeLocalEcho = "\x1b[12$p"
)
// Line Feed/New Line Mode (LNM) is a mode that determines whether the terminal
@ -299,12 +299,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/LNM.html
const (
LineFeedNewLineMode = ANSIMode(20)
LNM = LineFeedNewLineMode
ModeLineFeedNewLine = ANSIMode(20)
LNM = ModeLineFeedNewLine
SetLineFeedNewLineMode = "\x1b[20h"
ResetLineFeedNewLineMode = "\x1b[20l"
RequestLineFeedNewLineMode = "\x1b[20$p"
SetModeLineFeedNewLine = "\x1b[20h"
ResetModeLineFeedNewLine = "\x1b[20l"
RequestModeLineFeedNewLine = "\x1b[20$p"
)
// Cursor Keys Mode (DECCKM) is a mode that determines whether the cursor keys
@ -312,18 +312,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECCKM.html
const (
CursorKeysMode = DECMode(1)
DECCKM = CursorKeysMode
ModeCursorKeys = DECMode(1)
DECCKM = ModeCursorKeys
SetCursorKeysMode = "\x1b[?1h"
ResetCursorKeysMode = "\x1b[?1l"
RequestCursorKeysMode = "\x1b[?1$p"
)
// Deprecated: use [SetCursorKeysMode] and [ResetCursorKeysMode] instead.
const (
EnableCursorKeys = "\x1b[?1h" //nolint:revive // grouped constants
DisableCursorKeys = "\x1b[?1l"
SetModeCursorKeys = "\x1b[?1h"
ResetModeCursorKeys = "\x1b[?1l"
RequestModeCursorKeys = "\x1b[?1$p"
)
// Origin Mode (DECOM) is a mode that determines whether the cursor moves to the
@ -331,12 +325,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECOM.html
const (
OriginMode = DECMode(6)
DECOM = OriginMode
ModeOrigin = DECMode(6)
DECOM = ModeOrigin
SetOriginMode = "\x1b[?6h"
ResetOriginMode = "\x1b[?6l"
RequestOriginMode = "\x1b[?6$p"
SetModeOrigin = "\x1b[?6h"
ResetModeOrigin = "\x1b[?6l"
RequestModeOrigin = "\x1b[?6$p"
)
// Auto Wrap Mode (DECAWM) is a mode that determines whether the cursor wraps
@ -344,12 +338,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECAWM.html
const (
AutoWrapMode = DECMode(7)
DECAWM = AutoWrapMode
ModeAutoWrap = DECMode(7)
DECAWM = ModeAutoWrap
SetAutoWrapMode = "\x1b[?7h"
ResetAutoWrapMode = "\x1b[?7l"
RequestAutoWrapMode = "\x1b[?7$p"
SetModeAutoWrap = "\x1b[?7h"
ResetModeAutoWrap = "\x1b[?7l"
RequestModeAutoWrap = "\x1b[?7$p"
)
// X10 Mouse Mode is a mode that determines whether the mouse reports on button
@ -364,39 +358,29 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
X10MouseMode = DECMode(9)
ModeMouseX10 = DECMode(9)
SetX10MouseMode = "\x1b[?9h"
ResetX10MouseMode = "\x1b[?9l"
RequestX10MouseMode = "\x1b[?9$p"
SetModeMouseX10 = "\x1b[?9h"
ResetModeMouseX10 = "\x1b[?9l"
RequestModeMouseX10 = "\x1b[?9$p"
)
// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
//
// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
const (
TextCursorEnableMode = DECMode(25)
DECTCEM = TextCursorEnableMode
ModeTextCursorEnable = DECMode(25)
DECTCEM = ModeTextCursorEnable
SetTextCursorEnableMode = "\x1b[?25h"
ResetTextCursorEnableMode = "\x1b[?25l"
RequestTextCursorEnableMode = "\x1b[?25$p"
SetModeTextCursorEnable = "\x1b[?25h"
ResetModeTextCursorEnable = "\x1b[?25l"
RequestModeTextCursorEnable = "\x1b[?25$p"
)
// These are aliases for [SetTextCursorEnableMode] and [ResetTextCursorEnableMode].
// These are aliases for [SetModeTextCursorEnable] and [ResetModeTextCursorEnable].
const (
ShowCursor = SetTextCursorEnableMode
HideCursor = ResetTextCursorEnableMode
)
// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
//
// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
//
// Deprecated: use [SetTextCursorEnableMode] and [ResetTextCursorEnableMode] instead.
const (
CursorEnableMode = DECMode(25)
RequestCursorVisibility = "\x1b[?25$p"
ShowCursor = SetModeTextCursorEnable
HideCursor = ResetModeTextCursorEnable
)
// Numeric Keypad Mode (DECNKM) is a mode that determines whether the keypad
@ -406,12 +390,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECNKM.html
const (
NumericKeypadMode = DECMode(66)
DECNKM = NumericKeypadMode
ModeNumericKeypad = DECMode(66)
DECNKM = ModeNumericKeypad
SetNumericKeypadMode = "\x1b[?66h"
ResetNumericKeypadMode = "\x1b[?66l"
RequestNumericKeypadMode = "\x1b[?66$p"
SetModeNumericKeypad = "\x1b[?66h"
ResetModeNumericKeypad = "\x1b[?66l"
RequestModeNumericKeypad = "\x1b[?66$p"
)
// Backarrow Key Mode (DECBKM) is a mode that determines whether the backspace
@ -419,12 +403,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECBKM.html
const (
BackarrowKeyMode = DECMode(67)
DECBKM = BackarrowKeyMode
ModeBackarrowKey = DECMode(67)
DECBKM = ModeBackarrowKey
SetBackarrowKeyMode = "\x1b[?67h"
ResetBackarrowKeyMode = "\x1b[?67l"
RequestBackarrowKeyMode = "\x1b[?67$p"
SetModeBackarrowKey = "\x1b[?67h"
ResetModeBackarrowKey = "\x1b[?67l"
RequestModeBackarrowKey = "\x1b[?67$p"
)
// Left Right Margin Mode (DECLRMM) is a mode that determines whether the left
@ -432,47 +416,33 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECLRMM.html
const (
LeftRightMarginMode = DECMode(69)
DECLRMM = LeftRightMarginMode
ModeLeftRightMargin = DECMode(69)
DECLRMM = ModeLeftRightMargin
SetLeftRightMarginMode = "\x1b[?69h"
ResetLeftRightMarginMode = "\x1b[?69l"
RequestLeftRightMarginMode = "\x1b[?69$p"
SetModeLeftRightMargin = "\x1b[?69h"
ResetModeLeftRightMargin = "\x1b[?69l"
RequestModeLeftRightMargin = "\x1b[?69$p"
)
// Normal Mouse Mode is a mode that determines whether the mouse reports on
// button presses and releases. It will also report modifier keys, wheel
// events, and extra buttons.
//
// It uses the same encoding as [X10MouseMode] with a few differences:
// It uses the same encoding as [ModeMouseX10] with a few differences:
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
NormalMouseMode = DECMode(1000)
ModeMouseNormal = DECMode(1000)
SetNormalMouseMode = "\x1b[?1000h"
ResetNormalMouseMode = "\x1b[?1000l"
RequestNormalMouseMode = "\x1b[?1000$p"
)
// VT Mouse Tracking is a mode that determines whether the mouse reports on
// button press and release.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [NormalMouseMode] instead.
const (
MouseMode = DECMode(1000)
EnableMouse = "\x1b[?1000h"
DisableMouse = "\x1b[?1000l"
RequestMouse = "\x1b[?1000$p"
SetModeMouseNormal = "\x1b[?1000h"
ResetModeMouseNormal = "\x1b[?1000l"
RequestModeMouseNormal = "\x1b[?1000$p"
)
// Highlight Mouse Tracking is a mode that determines whether the mouse reports
// on button presses, releases, and highlighted cells.
//
// It uses the same encoding as [NormalMouseMode] with a few differences:
// It uses the same encoding as [ModeMouseNormal] with a few differences:
//
// On highlight events, the terminal responds with the following encoding:
//
@ -481,11 +451,11 @@ const (
//
// Where the parameters are startx, starty, endx, endy, mousex, and mousey.
const (
HighlightMouseMode = DECMode(1001)
ModeMouseHighlight = DECMode(1001)
SetHighlightMouseMode = "\x1b[?1001h"
ResetHighlightMouseMode = "\x1b[?1001l"
RequestHighlightMouseMode = "\x1b[?1001$p"
SetModeMouseHighlight = "\x1b[?1001h"
ResetModeMouseHighlight = "\x1b[?1001l"
RequestModeMouseHighlight = "\x1b[?1001$p"
)
// VT Hilite Mouse Tracking is a mode that determines whether the mouse reports on
@ -493,65 +463,29 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [HighlightMouseMode] instead.
const (
MouseHiliteMode = DECMode(1001)
EnableMouseHilite = "\x1b[?1001h"
DisableMouseHilite = "\x1b[?1001l"
RequestMouseHilite = "\x1b[?1001$p"
)
// Button Event Mouse Tracking is essentially the same as [NormalMouseMode],
// Button Event Mouse Tracking is essentially the same as [ModeMouseNormal],
// but it also reports button-motion events when a button is pressed.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
ButtonEventMouseMode = DECMode(1002)
ModeMouseButtonEvent = DECMode(1002)
SetButtonEventMouseMode = "\x1b[?1002h"
ResetButtonEventMouseMode = "\x1b[?1002l"
RequestButtonEventMouseMode = "\x1b[?1002$p"
SetModeMouseButtonEvent = "\x1b[?1002h"
ResetModeMouseButtonEvent = "\x1b[?1002l"
RequestModeMouseButtonEvent = "\x1b[?1002$p"
)
// Cell Motion Mouse Tracking is a mode that determines whether the mouse
// reports on button press, release, and motion events.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [ButtonEventMouseMode] instead.
const (
MouseCellMotionMode = DECMode(1002)
EnableMouseCellMotion = "\x1b[?1002h"
DisableMouseCellMotion = "\x1b[?1002l"
RequestMouseCellMotion = "\x1b[?1002$p"
)
// Any Event Mouse Tracking is the same as [ButtonEventMouseMode], except that
// Any Event Mouse Tracking is the same as [ModeMouseButtonEvent], except that
// all motion events are reported even if no mouse buttons are pressed.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
AnyEventMouseMode = DECMode(1003)
ModeMouseAnyEvent = DECMode(1003)
SetAnyEventMouseMode = "\x1b[?1003h"
ResetAnyEventMouseMode = "\x1b[?1003l"
RequestAnyEventMouseMode = "\x1b[?1003$p"
)
// All Mouse Tracking is a mode that determines whether the mouse reports on
// button press, release, motion, and highlight events.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [AnyEventMouseMode] instead.
const (
MouseAllMotionMode = DECMode(1003)
EnableMouseAllMotion = "\x1b[?1003h"
DisableMouseAllMotion = "\x1b[?1003l"
RequestMouseAllMotion = "\x1b[?1003$p"
SetModeMouseAnyEvent = "\x1b[?1003h"
ResetModeMouseAnyEvent = "\x1b[?1003l"
RequestModeMouseAnyEvent = "\x1b[?1003$p"
)
// Focus Event Mode is a mode that determines whether the terminal reports focus
@ -564,22 +498,11 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Focus-Tracking
const (
FocusEventMode = DECMode(1004)
ModeFocusEvent = DECMode(1004)
SetFocusEventMode = "\x1b[?1004h"
ResetFocusEventMode = "\x1b[?1004l"
RequestFocusEventMode = "\x1b[?1004$p"
)
// Deprecated: use [SetFocusEventMode], [ResetFocusEventMode], and
// [RequestFocusEventMode] instead.
// Focus reporting mode constants.
const (
ReportFocusMode = DECMode(1004) //nolint:revive // grouped constants
EnableReportFocus = "\x1b[?1004h"
DisableReportFocus = "\x1b[?1004l"
RequestReportFocus = "\x1b[?1004$p"
SetModeFocusEvent = "\x1b[?1004h"
ResetModeFocusEvent = "\x1b[?1004l"
RequestModeFocusEvent = "\x1b[?1004$p"
)
// SGR Extended Mouse Mode is a mode that changes the mouse tracking encoding
@ -589,24 +512,15 @@ const (
//
// CSI < Cb ; Cx ; Cy M
//
// Where Cb is the same as [NormalMouseMode], and Cx and Cy are the x and y.
// Where Cb is the same as [ModeMouseNormal], and Cx and Cy are the x and y.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
SgrExtMouseMode = DECMode(1006)
ModeMouseExtSgr = DECMode(1006)
SetSgrExtMouseMode = "\x1b[?1006h"
ResetSgrExtMouseMode = "\x1b[?1006l"
RequestSgrExtMouseMode = "\x1b[?1006$p"
)
// Deprecated: use [SgrExtMouseMode] [SetSgrExtMouseMode],
// [ResetSgrExtMouseMode], and [RequestSgrExtMouseMode] instead.
const (
MouseSgrExtMode = DECMode(1006) //nolint:revive // grouped constants
EnableMouseSgrExt = "\x1b[?1006h"
DisableMouseSgrExt = "\x1b[?1006l"
RequestMouseSgrExt = "\x1b[?1006$p"
SetModeMouseExtSgr = "\x1b[?1006h"
ResetModeMouseExtSgr = "\x1b[?1006l"
RequestModeMouseExtSgr = "\x1b[?1006$p"
)
// UTF-8 Extended Mouse Mode is a mode that changes the mouse tracking encoding
@ -614,11 +528,11 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
Utf8ExtMouseMode = DECMode(1005)
ModeMouseExtUtf8 = DECMode(1005)
SetUtf8ExtMouseMode = "\x1b[?1005h"
ResetUtf8ExtMouseMode = "\x1b[?1005l"
RequestUtf8ExtMouseMode = "\x1b[?1005$p"
SetModeMouseExtUtf8 = "\x1b[?1005h"
ResetModeMouseExtUtf8 = "\x1b[?1005l"
RequestModeMouseExtUtf8 = "\x1b[?1005$p"
)
// URXVT Extended Mouse Mode is a mode that changes the mouse tracking encoding
@ -626,25 +540,25 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
UrxvtExtMouseMode = DECMode(1015)
ModeMouseExtUrxvt = DECMode(1015)
SetUrxvtExtMouseMode = "\x1b[?1015h"
ResetUrxvtExtMouseMode = "\x1b[?1015l"
RequestUrxvtExtMouseMode = "\x1b[?1015$p"
SetModeMouseExtUrxvt = "\x1b[?1015h"
ResetModeMouseExtUrxvt = "\x1b[?1015l"
RequestModeMouseExtUrxvt = "\x1b[?1015$p"
)
// SGR Pixel Extended Mouse Mode is a mode that changes the mouse tracking
// encoding to use SGR parameters with pixel coordinates.
//
// This is similar to [SgrExtMouseMode], but also reports pixel coordinates.
// This is similar to [ModeMouseExtSgr], but also reports pixel coordinates.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
SgrPixelExtMouseMode = DECMode(1016)
ModeMouseExtSgrPixel = DECMode(1016)
SetSgrPixelExtMouseMode = "\x1b[?1016h"
ResetSgrPixelExtMouseMode = "\x1b[?1016l"
RequestSgrPixelExtMouseMode = "\x1b[?1016$p"
SetModeMouseExtSgrPixel = "\x1b[?1016h"
ResetModeMouseExtSgrPixel = "\x1b[?1016l"
RequestModeMouseExtSgrPixel = "\x1b[?1016$p"
)
// Alternate Screen Mode is a mode that determines whether the alternate screen
@ -653,11 +567,11 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
const (
AltScreenMode = DECMode(1047)
ModeAltScreen = DECMode(1047)
SetAltScreenMode = "\x1b[?1047h"
ResetAltScreenMode = "\x1b[?1047l"
RequestAltScreenMode = "\x1b[?1047$p"
SetModeAltScreen = "\x1b[?1047h"
ResetModeAltScreen = "\x1b[?1047l"
RequestModeAltScreen = "\x1b[?1047$p"
)
// Save Cursor Mode is a mode that saves the cursor position.
@ -665,42 +579,24 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
const (
SaveCursorMode = DECMode(1048)
ModeSaveCursor = DECMode(1048)
SetSaveCursorMode = "\x1b[?1048h"
ResetSaveCursorMode = "\x1b[?1048l"
RequestSaveCursorMode = "\x1b[?1048$p"
SetModeSaveCursor = "\x1b[?1048h"
ResetModeSaveCursor = "\x1b[?1048l"
RequestModeSaveCursor = "\x1b[?1048$p"
)
// Alternate Screen Save Cursor Mode is a mode that saves the cursor position as in
// [SaveCursorMode], switches to the alternate screen buffer as in [AltScreenMode],
// [ModeSaveCursor], switches to the alternate screen buffer as in [ModeAltScreen],
// and clears the screen on switch.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
const (
AltScreenSaveCursorMode = DECMode(1049)
ModeAltScreenSaveCursor = DECMode(1049)
SetAltScreenSaveCursorMode = "\x1b[?1049h"
ResetAltScreenSaveCursorMode = "\x1b[?1049l"
RequestAltScreenSaveCursorMode = "\x1b[?1049$p"
)
// Alternate Screen Buffer is a mode that determines whether the alternate screen
// buffer is active.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
//
// Deprecated: use [AltScreenSaveCursorMode] instead.
const (
AltScreenBufferMode = DECMode(1049)
SetAltScreenBufferMode = "\x1b[?1049h"
ResetAltScreenBufferMode = "\x1b[?1049l"
RequestAltScreenBufferMode = "\x1b[?1049$p"
EnableAltScreenBuffer = "\x1b[?1049h"
DisableAltScreenBuffer = "\x1b[?1049l"
RequestAltScreenBuffer = "\x1b[?1049$p"
SetModeAltScreenSaveCursor = "\x1b[?1049h"
ResetModeAltScreenSaveCursor = "\x1b[?1049l"
RequestModeAltScreenSaveCursor = "\x1b[?1049$p"
)
// Bracketed Paste Mode is a mode that determines whether pasted text is
@ -709,19 +605,11 @@ const (
// See: https://cirw.in/blog/bracketed-paste
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode
const (
BracketedPasteMode = DECMode(2004)
ModeBracketedPaste = DECMode(2004)
SetBracketedPasteMode = "\x1b[?2004h"
ResetBracketedPasteMode = "\x1b[?2004l"
RequestBracketedPasteMode = "\x1b[?2004$p"
)
// Deprecated: use [SetBracketedPasteMode], [ResetBracketedPasteMode], and
// [RequestBracketedPasteMode] instead.
const (
EnableBracketedPaste = "\x1b[?2004h" //nolint:revive // grouped constants
DisableBracketedPaste = "\x1b[?2004l"
RequestBracketedPaste = "\x1b[?2004$p"
SetModeBracketedPaste = "\x1b[?2004h"
ResetModeBracketedPaste = "\x1b[?2004l"
RequestModeBracketedPaste = "\x1b[?2004$p"
)
// Synchronized Output Mode is a mode that determines whether output is
@ -729,23 +617,11 @@ const (
//
// See: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
const (
SynchronizedOutputMode = DECMode(2026)
ModeSynchronizedOutput = DECMode(2026)
SetSynchronizedOutputMode = "\x1b[?2026h"
ResetSynchronizedOutputMode = "\x1b[?2026l"
RequestSynchronizedOutputMode = "\x1b[?2026$p"
)
// Synchronized Output Mode. See [SynchronizedOutputMode].
//
// Deprecated: use [SynchronizedOutputMode], [SetSynchronizedOutputMode], and
// [ResetSynchronizedOutputMode], and [RequestSynchronizedOutputMode] instead.
const (
SyncdOutputMode = DECMode(2026)
EnableSyncdOutput = "\x1b[?2026h"
DisableSyncdOutput = "\x1b[?2026l"
RequestSyncdOutput = "\x1b[?2026$p"
SetModeSynchronizedOutput = "\x1b[?2026h"
ResetModeSynchronizedOutput = "\x1b[?2026l"
RequestModeSynchronizedOutput = "\x1b[?2026$p"
)
// Unicode Core Mode is a mode that determines whether the terminal should use
@ -754,41 +630,16 @@ const (
//
// See: https://github.com/contour-terminal/terminal-unicode-core
const (
UnicodeCoreMode = DECMode(2027)
ModeUnicodeCore = DECMode(2027)
SetUnicodeCoreMode = "\x1b[?2027h"
ResetUnicodeCoreMode = "\x1b[?2027l"
RequestUnicodeCoreMode = "\x1b[?2027$p"
SetModeUnicodeCore = "\x1b[?2027h"
ResetModeUnicodeCore = "\x1b[?2027l"
RequestModeUnicodeCore = "\x1b[?2027$p"
)
// Grapheme Clustering Mode is a mode that determines whether the terminal
// should look for grapheme clusters instead of single runes in the rendered
// text. This makes the terminal properly render combining characters such as
// emojis.
//
// See: https://github.com/contour-terminal/terminal-unicode-core
//
// Deprecated: use [GraphemeClusteringMode], [SetUnicodeCoreMode],
// [ResetUnicodeCoreMode], and [RequestUnicodeCoreMode] instead.
const (
GraphemeClusteringMode = DECMode(2027)
SetGraphemeClusteringMode = "\x1b[?2027h"
ResetGraphemeClusteringMode = "\x1b[?2027l"
RequestGraphemeClusteringMode = "\x1b[?2027$p"
)
// Grapheme Clustering Mode. See [GraphemeClusteringMode].
//
// Deprecated: use [SetUnicodeCoreMode], [ResetUnicodeCoreMode], and
// [RequestUnicodeCoreMode] instead.
const (
EnableGraphemeClustering = "\x1b[?2027h"
DisableGraphemeClustering = "\x1b[?2027l"
RequestGraphemeClustering = "\x1b[?2027$p"
)
// LightDarkMode is a mode that enables reporting the operating system's color
// ModeLightDark is a mode that enables reporting the operating system's color
// scheme (light or dark) preference. It reports the color scheme as a [DSR]
// and [LightDarkReport] escape sequences encoded as follows:
//
@ -802,14 +653,14 @@ const (
//
// See: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/
const (
LightDarkMode = DECMode(2031)
ModeLightDark = DECMode(2031)
SetLightDarkMode = "\x1b[?2031h"
ResetLightDarkMode = "\x1b[?2031l"
RequestLightDarkMode = "\x1b[?2031$p"
SetModeLightDark = "\x1b[?2031h"
ResetModeLightDark = "\x1b[?2031l"
RequestModeLightDark = "\x1b[?2031$p"
)
// InBandResizeMode is a mode that reports terminal resize events as escape
// ModeInBandResize is a mode that reports terminal resize events as escape
// sequences. This is useful for systems that do not support [SIGWINCH] like
// Windows.
//
@ -819,11 +670,11 @@ const (
//
// See: https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83
const (
InBandResizeMode = DECMode(2048)
ModeInBandResize = DECMode(2048)
SetInBandResizeMode = "\x1b[?2048h"
ResetInBandResizeMode = "\x1b[?2048l"
RequestInBandResizeMode = "\x1b[?2048$p"
SetModeInBandResize = "\x1b[?2048h"
ResetModeInBandResize = "\x1b[?2048l"
RequestModeInBandResize = "\x1b[?2048$p"
)
// Win32Input is a mode that determines whether input is processed by the
@ -831,17 +682,9 @@ const (
//
// See: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md
const (
Win32InputMode = DECMode(9001)
ModeWin32Input = DECMode(9001)
SetWin32InputMode = "\x1b[?9001h"
ResetWin32InputMode = "\x1b[?9001l"
RequestWin32InputMode = "\x1b[?9001$p"
)
// Deprecated: use [SetWin32InputMode], [ResetWin32InputMode], and
// [RequestWin32InputMode] instead.
const (
EnableWin32Input = "\x1b[?9001h" //nolint:revive // grouped constants
DisableWin32Input = "\x1b[?9001l"
RequestWin32Input = "\x1b[?9001$p"
SetModeWin32Input = "\x1b[?9001h"
ResetModeWin32Input = "\x1b[?9001l"
RequestModeWin32Input = "\x1b[?9001$p"
)

View File

@ -0,0 +1,495 @@
package ansi
// Keyboard Action Mode (KAM) controls locking of the keyboard.
//
// Deprecated: use [ModeKeyboardAction] instead.
const (
KeyboardActionMode = ANSIMode(2)
SetKeyboardActionMode = "\x1b[2h"
ResetKeyboardActionMode = "\x1b[2l"
RequestKeyboardActionMode = "\x1b[2$p"
)
// Insert/Replace Mode (IRM) determines whether characters are inserted or replaced.
//
// Deprecated: use [ModeInsertReplace] instead.
const (
InsertReplaceMode = ANSIMode(4)
SetInsertReplaceMode = "\x1b[4h"
ResetInsertReplaceMode = "\x1b[4l"
RequestInsertReplaceMode = "\x1b[4$p"
)
// BiDirectional Support Mode (BDSM) determines whether the terminal supports bidirectional text.
//
// Deprecated: use [ModeBiDirectionalSupport] instead.
const (
BiDirectionalSupportMode = ANSIMode(8)
SetBiDirectionalSupportMode = "\x1b[8h"
ResetBiDirectionalSupportMode = "\x1b[8l"
RequestBiDirectionalSupportMode = "\x1b[8$p"
)
// Send Receive Mode (SRM) or Local Echo Mode determines whether the terminal echoes characters.
//
// Deprecated: use [ModeSendReceive] instead.
const (
SendReceiveMode = ANSIMode(12)
LocalEchoMode = SendReceiveMode
SetSendReceiveMode = "\x1b[12h"
ResetSendReceiveMode = "\x1b[12l"
RequestSendReceiveMode = "\x1b[12$p"
SetLocalEchoMode = "\x1b[12h"
ResetLocalEchoMode = "\x1b[12l"
RequestLocalEchoMode = "\x1b[12$p"
)
// Line Feed/New Line Mode (LNM) determines whether the terminal interprets line feed as new line.
//
// Deprecated: use [ModeLineFeedNewLine] instead.
const (
LineFeedNewLineMode = ANSIMode(20)
SetLineFeedNewLineMode = "\x1b[20h"
ResetLineFeedNewLineMode = "\x1b[20l"
RequestLineFeedNewLineMode = "\x1b[20$p"
)
// Cursor Keys Mode (DECCKM) determines whether cursor keys send ANSI or application sequences.
//
// Deprecated: use [ModeCursorKeys] instead.
const (
CursorKeysMode = DECMode(1)
SetCursorKeysMode = "\x1b[?1h"
ResetCursorKeysMode = "\x1b[?1l"
RequestCursorKeysMode = "\x1b[?1$p"
)
// Cursor Keys mode.
//
// Deprecated: use [SetModeCursorKeys] and [ResetModeCursorKeys] instead.
const (
EnableCursorKeys = "\x1b[?1h"
DisableCursorKeys = "\x1b[?1l"
)
// Origin Mode (DECOM) determines whether the cursor moves to home or margin position.
//
// Deprecated: use [ModeOrigin] instead.
const (
OriginMode = DECMode(6)
SetOriginMode = "\x1b[?6h"
ResetOriginMode = "\x1b[?6l"
RequestOriginMode = "\x1b[?6$p"
)
// Auto Wrap Mode (DECAWM) determines whether the cursor wraps to the next line.
//
// Deprecated: use [ModeAutoWrap] instead.
const (
AutoWrapMode = DECMode(7)
SetAutoWrapMode = "\x1b[?7h"
ResetAutoWrapMode = "\x1b[?7l"
RequestAutoWrapMode = "\x1b[?7$p"
)
// X10 Mouse Mode determines whether the mouse reports on button presses.
//
// Deprecated: use [ModeMouseX10] instead.
const (
X10MouseMode = DECMode(9)
SetX10MouseMode = "\x1b[?9h"
ResetX10MouseMode = "\x1b[?9l"
RequestX10MouseMode = "\x1b[?9$p"
)
// Text Cursor Enable Mode (DECTCEM) shows/hides the cursor.
//
// Deprecated: use [ModeTextCursorEnable] instead.
const (
TextCursorEnableMode = DECMode(25)
SetTextCursorEnableMode = "\x1b[?25h"
ResetTextCursorEnableMode = "\x1b[?25l"
RequestTextCursorEnableMode = "\x1b[?25$p"
)
// Text Cursor Enable mode.
//
// Deprecated: use [SetModeTextCursorEnable] and [ResetModeTextCursorEnable] instead.
const (
CursorEnableMode = DECMode(25)
RequestCursorVisibility = "\x1b[?25$p"
)
// Numeric Keypad Mode (DECNKM) determines whether the keypad sends application or numeric sequences.
//
// Deprecated: use [ModeNumericKeypad] instead.
const (
NumericKeypadMode = DECMode(66)
SetNumericKeypadMode = "\x1b[?66h"
ResetNumericKeypadMode = "\x1b[?66l"
RequestNumericKeypadMode = "\x1b[?66$p"
)
// Backarrow Key Mode (DECBKM) determines whether the backspace key sends backspace or delete.
//
// Deprecated: use [ModeBackarrowKey] instead.
const (
BackarrowKeyMode = DECMode(67)
SetBackarrowKeyMode = "\x1b[?67h"
ResetBackarrowKeyMode = "\x1b[?67l"
RequestBackarrowKeyMode = "\x1b[?67$p"
)
// Left Right Margin Mode (DECLRMM) determines whether left and right margins can be set.
//
// Deprecated: use [ModeLeftRightMargin] instead.
const (
LeftRightMarginMode = DECMode(69)
SetLeftRightMarginMode = "\x1b[?69h"
ResetLeftRightMarginMode = "\x1b[?69l"
RequestLeftRightMarginMode = "\x1b[?69$p"
)
// Normal Mouse Mode determines whether the mouse reports on button presses and releases.
//
// Deprecated: use [ModeMouseNormal] instead.
const (
NormalMouseMode = DECMode(1000)
SetNormalMouseMode = "\x1b[?1000h"
ResetNormalMouseMode = "\x1b[?1000l"
RequestNormalMouseMode = "\x1b[?1000$p"
)
// VT Mouse Tracking mode.
//
// Deprecated: use [ModeMouseNormal] instead.
const (
MouseMode = DECMode(1000)
EnableMouse = "\x1b[?1000h"
DisableMouse = "\x1b[?1000l"
RequestMouse = "\x1b[?1000$p"
)
// Highlight Mouse Tracking determines whether the mouse reports on button presses and highlighted cells.
//
// Deprecated: use [ModeMouseHighlight] instead.
const (
HighlightMouseMode = DECMode(1001)
SetHighlightMouseMode = "\x1b[?1001h"
ResetHighlightMouseMode = "\x1b[?1001l"
RequestHighlightMouseMode = "\x1b[?1001$p"
)
// VT Hilite Mouse Tracking mode.
//
// Deprecated: use [ModeMouseHighlight] instead.
const (
MouseHiliteMode = DECMode(1001)
EnableMouseHilite = "\x1b[?1001h"
DisableMouseHilite = "\x1b[?1001l"
RequestMouseHilite = "\x1b[?1001$p"
)
// Button Event Mouse Tracking reports button-motion events when a button is pressed.
//
// Deprecated: use [ModeMouseButtonEvent] instead.
const (
ButtonEventMouseMode = DECMode(1002)
SetButtonEventMouseMode = "\x1b[?1002h"
ResetButtonEventMouseMode = "\x1b[?1002l"
RequestButtonEventMouseMode = "\x1b[?1002$p"
)
// Cell Motion Mouse Tracking mode.
//
// Deprecated: use [ModeMouseButtonEvent] instead.
const (
MouseCellMotionMode = DECMode(1002)
EnableMouseCellMotion = "\x1b[?1002h"
DisableMouseCellMotion = "\x1b[?1002l"
RequestMouseCellMotion = "\x1b[?1002$p"
)
// Any Event Mouse Tracking reports all motion events.
//
// Deprecated: use [ModeMouseAnyEvent] instead.
const (
AnyEventMouseMode = DECMode(1003)
SetAnyEventMouseMode = "\x1b[?1003h"
ResetAnyEventMouseMode = "\x1b[?1003l"
RequestAnyEventMouseMode = "\x1b[?1003$p"
)
// All Mouse Tracking mode.
//
// Deprecated: use [ModeMouseAnyEvent] instead.
const (
MouseAllMotionMode = DECMode(1003)
EnableMouseAllMotion = "\x1b[?1003h"
DisableMouseAllMotion = "\x1b[?1003l"
RequestMouseAllMotion = "\x1b[?1003$p"
)
// Focus Event Mode determines whether the terminal reports focus and blur events.
//
// Deprecated: use [ModeFocusEvent] instead.
const (
FocusEventMode = DECMode(1004)
SetFocusEventMode = "\x1b[?1004h"
ResetFocusEventMode = "\x1b[?1004l"
RequestFocusEventMode = "\x1b[?1004$p"
)
// Focus reporting mode.
//
// Deprecated: use [SetModeFocusEvent], [ResetModeFocusEvent], and
// [RequestModeFocusEvent] instead.
const (
ReportFocusMode = DECMode(1004)
EnableReportFocus = "\x1b[?1004h"
DisableReportFocus = "\x1b[?1004l"
RequestReportFocus = "\x1b[?1004$p"
)
// UTF-8 Extended Mouse Mode changes the mouse tracking encoding to use UTF-8 parameters.
//
// Deprecated: use [ModeMouseExtUtf8] instead.
const (
Utf8ExtMouseMode = DECMode(1005)
SetUtf8ExtMouseMode = "\x1b[?1005h"
ResetUtf8ExtMouseMode = "\x1b[?1005l"
RequestUtf8ExtMouseMode = "\x1b[?1005$p"
)
// SGR Extended Mouse Mode changes the mouse tracking encoding to use SGR parameters.
//
// Deprecated: use [ModeMouseExtSgr] instead.
const (
SgrExtMouseMode = DECMode(1006)
SetSgrExtMouseMode = "\x1b[?1006h"
ResetSgrExtMouseMode = "\x1b[?1006l"
RequestSgrExtMouseMode = "\x1b[?1006$p"
)
// Mouse SGR Extended mode.
//
// Deprecated: use [ModeMouseExtSgr], [SetModeMouseExtSgr],
// [ResetModeMouseExtSgr], and [RequestModeMouseExtSgr] instead.
const (
MouseSgrExtMode = DECMode(1006)
EnableMouseSgrExt = "\x1b[?1006h"
DisableMouseSgrExt = "\x1b[?1006l"
RequestMouseSgrExt = "\x1b[?1006$p"
)
// URXVT Extended Mouse Mode changes the mouse tracking encoding to use an alternate encoding.
//
// Deprecated: use [ModeMouseUrxvtExt] instead.
const (
UrxvtExtMouseMode = DECMode(1015)
SetUrxvtExtMouseMode = "\x1b[?1015h"
ResetUrxvtExtMouseMode = "\x1b[?1015l"
RequestUrxvtExtMouseMode = "\x1b[?1015$p"
)
// SGR Pixel Extended Mouse Mode changes the mouse tracking encoding to use SGR parameters with pixel coordinates.
//
// Deprecated: use [ModeMouseExtSgrPixel] instead.
const (
SgrPixelExtMouseMode = DECMode(1016)
SetSgrPixelExtMouseMode = "\x1b[?1016h"
ResetSgrPixelExtMouseMode = "\x1b[?1016l"
RequestSgrPixelExtMouseMode = "\x1b[?1016$p"
)
// Alternate Screen Mode determines whether the alternate screen buffer is active.
//
// Deprecated: use [ModeAltScreen] instead.
const (
AltScreenMode = DECMode(1047)
SetAltScreenMode = "\x1b[?1047h"
ResetAltScreenMode = "\x1b[?1047l"
RequestAltScreenMode = "\x1b[?1047$p"
)
// Save Cursor Mode saves the cursor position.
//
// Deprecated: use [ModeSaveCursor] instead.
const (
SaveCursorMode = DECMode(1048)
SetSaveCursorMode = "\x1b[?1048h"
ResetSaveCursorMode = "\x1b[?1048l"
RequestSaveCursorMode = "\x1b[?1048$p"
)
// Alternate Screen Save Cursor Mode saves the cursor position and switches to alternate screen.
//
// Deprecated: use [ModeAltScreenSaveCursor] instead.
const (
AltScreenSaveCursorMode = DECMode(1049)
SetAltScreenSaveCursorMode = "\x1b[?1049h"
ResetAltScreenSaveCursorMode = "\x1b[?1049l"
RequestAltScreenSaveCursorMode = "\x1b[?1049$p"
)
// Alternate Screen Buffer mode.
//
// Deprecated: use [ModeAltScreenSaveCursor] instead.
const (
AltScreenBufferMode = DECMode(1049)
SetAltScreenBufferMode = "\x1b[?1049h"
ResetAltScreenBufferMode = "\x1b[?1049l"
RequestAltScreenBufferMode = "\x1b[?1049$p"
EnableAltScreenBuffer = "\x1b[?1049h"
DisableAltScreenBuffer = "\x1b[?1049l"
RequestAltScreenBuffer = "\x1b[?1049$p"
)
// Bracketed Paste Mode determines whether pasted text is bracketed with escape sequences.
//
// Deprecated: use [ModeBracketedPaste] instead.
const (
BracketedPasteMode = DECMode(2004)
SetBracketedPasteMode = "\x1b[?2004h"
ResetBracketedPasteMode = "\x1b[?2004l"
RequestBracketedPasteMode = "\x1b[?2004$p"
)
// Deprecated: use [SetModeBracketedPaste], [ResetModeBracketedPaste], and
// [RequestModeBracketedPaste] instead.
const (
EnableBracketedPaste = "\x1b[?2004h" //nolint:revive
DisableBracketedPaste = "\x1b[?2004l"
RequestBracketedPaste = "\x1b[?2004$p"
)
// Synchronized Output Mode determines whether output is synchronized with the terminal.
//
// Deprecated: use [ModeSynchronizedOutput] instead.
const (
SynchronizedOutputMode = DECMode(2026)
SetSynchronizedOutputMode = "\x1b[?2026h"
ResetSynchronizedOutputMode = "\x1b[?2026l"
RequestSynchronizedOutputMode = "\x1b[?2026$p"
)
// Synchronized output mode.
//
// Deprecated: use [ModeSynchronizedOutput], [SetModeSynchronizedOutput],
// [ResetModeSynchronizedOutput], and [RequestModeSynchronizedOutput] instead.
const (
SyncdOutputMode = DECMode(2026)
EnableSyncdOutput = "\x1b[?2026h"
DisableSyncdOutput = "\x1b[?2026l"
RequestSyncdOutput = "\x1b[?2026$p"
)
// Unicode Core Mode determines whether the terminal uses Unicode grapheme clustering.
//
// Deprecated: use [ModeUnicodeCore] instead.
const (
UnicodeCoreMode = DECMode(2027)
SetUnicodeCoreMode = "\x1b[?2027h"
ResetUnicodeCoreMode = "\x1b[?2027l"
RequestUnicodeCoreMode = "\x1b[?2027$p"
)
// Grapheme Clustering Mode determines whether the terminal looks for grapheme clusters.
//
// Deprecated: use [ModeUnicodeCore], [SetModeUnicodeCore],
// [ResetModeUnicodeCore], and [RequestModeUnicodeCore] instead.
const (
GraphemeClusteringMode = DECMode(2027)
SetGraphemeClusteringMode = "\x1b[?2027h"
ResetGraphemeClusteringMode = "\x1b[?2027l"
RequestGraphemeClusteringMode = "\x1b[?2027$p"
)
// Unicode Core mode.
//
// Deprecated: use [SetModeUnicodeCore], [ResetModeUnicodeCore], and
// [RequestModeUnicodeCore] instead.
const (
EnableGraphemeClustering = "\x1b[?2027h"
DisableGraphemeClustering = "\x1b[?2027l"
RequestGraphemeClustering = "\x1b[?2027$p"
)
// Light Dark Mode enables reporting the operating system's color scheme preference.
//
// Deprecated: use [ModeLightDark] instead.
const (
LightDarkMode = DECMode(2031)
SetLightDarkMode = "\x1b[?2031h"
ResetLightDarkMode = "\x1b[?2031l"
RequestLightDarkMode = "\x1b[?2031$p"
)
// In Band Resize Mode reports terminal resize events as escape sequences.
//
// Deprecated: use [ModeInBandResize] instead.
const (
InBandResizeMode = DECMode(2048)
SetInBandResizeMode = "\x1b[?2048h"
ResetInBandResizeMode = "\x1b[?2048l"
RequestInBandResizeMode = "\x1b[?2048$p"
)
// Win32Input determines whether input is processed by the Win32 console and Conpty.
//
// Deprecated: use [ModeWin32Input] instead.
const (
Win32InputMode = DECMode(9001)
SetWin32InputMode = "\x1b[?9001h"
ResetWin32InputMode = "\x1b[?9001l"
RequestWin32InputMode = "\x1b[?9001$p"
)
// Deprecated: use [SetModeWin32Input], [ResetModeWin32Input], and
// [RequestModeWin32Input] instead.
const (
EnableWin32Input = "\x1b[?9001h" //nolint:revive
DisableWin32Input = "\x1b[?9001l"
RequestWin32Input = "\x1b[?9001$p"
)

View File

@ -134,7 +134,7 @@ func EncodeMouseButton(b MouseButton, motion, shift, alt, ctrl bool) (m byte) {
m |= bitMotion
}
return //nolint:nakedret
return m
}
// x10Offset is the offset for X10 mouse events.

View File

@ -1,5 +1,10 @@
package ansi
import (
"fmt"
"strings"
)
// Notify sends a desktop notification using iTerm's OSC 9.
//
// OSC 9 ; Mc ST
@ -11,3 +16,17 @@ package ansi
func Notify(s string) string {
return "\x1b]9;" + s + "\x07"
}
// DesktopNotification sends a desktop notification based on the extensible OSC
// 99 escape code.
//
// OSC 99 ; <metadata> ; <payload> ST
// OSC 99 ; <metadata> ; <payload> BEL
//
// Where <metadata> is a colon-separated list of key-value pairs, and
// <payload> is the notification body.
//
// See: https://sw.kovidgoyal.net/kitty/desktop-notifications/
func DesktopNotification(payload string, metadata ...string) string {
return fmt.Sprintf("\x1b]99;%s;%s\x07", strings.Join(metadata, ":"), payload)
}

34
vendor/github.com/charmbracelet/x/ansi/palette.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package ansi
import (
"fmt"
"image/color"
)
// SetPalette sets the palette color for the given index. The index is a 16
// color index between 0 and 15. The color is a 24-bit RGB color.
//
// OSC P n rrggbb BEL
//
// Where n is the color index in hex (0-f), and rrggbb is the color in
// hexadecimal format (e.g., ff0000 for red).
//
// This sequence is specific to the Linux Console and may not work in other
// terminal emulators.
//
// See https://man7.org/linux/man-pages/man4/console_codes.4.html
func SetPalette(i int, c color.Color) string {
if c == nil || i < 0 || i > 15 {
return ""
}
r, g, b, _ := c.RGBA()
return fmt.Sprintf("\x1b]P%x%02x%02x%02x\x07", i, r>>8, g>>8, b>>8)
}
// ResetPalette resets the color palette to the default values.
//
// This sequence is specific to the Linux Console and may not work in other
// terminal emulators.
//
// See https://man7.org/linux/man-pages/man4/console_codes.4.html
const ResetPalette = "\x1b]R\x07"

View File

@ -4,8 +4,9 @@ import (
"unicode/utf8"
"github.com/charmbracelet/x/ansi/parser"
"github.com/clipperhouse/displaywidth"
"github.com/clipperhouse/uax29/v2/graphemes"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// State represents the state of the ANSI escape sequence parser used by
@ -176,10 +177,7 @@ func decodeSequence[T string | []byte](m Method, b T, state State, p *Parser) (s
}
if utf8.RuneStart(c) {
seq, _, width, _ = FirstGraphemeCluster(b, -1)
if m == WcWidth {
width = runewidth.StringWidth(string(seq))
}
seq, width = FirstGraphemeCluster(b, m)
i += len(seq)
return b[:i], width, i, NormalState
}
@ -434,17 +432,22 @@ func HasEscPrefix[T string | []byte](b T) bool {
return len(b) > 0 && b[0] == ESC
}
// FirstGraphemeCluster returns the first grapheme cluster in the given string or byte slice.
// This is a syntactic sugar function that wraps
// uniseg.FirstGraphemeClusterInString and uniseg.FirstGraphemeCluster.
func FirstGraphemeCluster[T string | []byte](b T, state int) (T, T, int, int) {
// FirstGraphemeCluster returns the first grapheme cluster in the given string
// or byte slice, and its monospace display width.
func FirstGraphemeCluster[T string | []byte](b T, m Method) (T, int) {
switch b := any(b).(type) {
case string:
cluster, rest, width, newState := uniseg.FirstGraphemeClusterInString(b, state)
return T(cluster), T(rest), width, newState
cluster := graphemes.FromString(b).First()
if m == WcWidth {
return T(cluster), runewidth.StringWidth(cluster)
}
return T(cluster), displaywidth.String(cluster)
case []byte:
cluster, rest, width, newState := uniseg.FirstGraphemeCluster(b, state)
return T(cluster), T(rest), width, newState
cluster := graphemes.FromBytes(b).First()
if m == WcWidth {
return T(cluster), runewidth.StringWidth(string(cluster))
}
return T(cluster), displaywidth.Bytes(cluster)
}
panic("unreachable")
}
@ -490,7 +493,7 @@ func Command(prefix, inter, final byte) (c int) {
c = int(final)
c |= int(prefix) << parser.PrefixShift
c |= int(inter) << parser.IntermedShift
return
return c
}
// Param represents a sequence parameter. Sequence parameters with
@ -520,5 +523,5 @@ func Parameter(p int, hasMore bool) (s int) {
if hasMore {
s |= parser.HasMoreFlag
}
return
return s
}

49
vendor/github.com/charmbracelet/x/ansi/progress.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package ansi
import "strconv"
// ResetProgressBar is a sequence that resets the progress bar to its default
// state (hidden).
//
// OSC 9 ; 4 ; 0 BEL
//
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
const ResetProgressBar = "\x1b]9;4;0\x07"
// SetProgressBar returns a sequence for setting the progress bar to a specific
// percentage (0-100) in the "default" state.
//
// OSC 9 ; 4 ; 1 Percentage BEL
//
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
func SetProgressBar(percentage int) string {
return "\x1b]9;4;1;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07"
}
// SetErrorProgressBar returns a sequence for setting the progress bar to a
// specific percentage (0-100) in the "Error" state..
//
// OSC 9 ; 4 ; 2 Percentage BEL
//
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
func SetErrorProgressBar(percentage int) string {
return "\x1b]9;4;2;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07"
}
// SetIndeterminateProgressBar is a sequence that sets the progress bar to the
// indeterminate state.
//
// OSC 9 ; 4 ; 3 BEL
//
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
const SetIndeterminateProgressBar = "\x1b]9;4;3\x07"
// SetWarningProgressBar is a sequence that sets the progress bar to the
// "Warning" state.
//
// OSC 9 ; 4 ; 4 Percentage BEL
//
// See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences
func SetWarningProgressBar(percentage int) string {
return "\x1b]9;4;4;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07"
}

View File

@ -21,59 +21,59 @@ func SGR(ps ...Attr) string {
}
var attrStrings = map[int]string{
ResetAttr: resetAttr,
BoldAttr: boldAttr,
FaintAttr: faintAttr,
ItalicAttr: italicAttr,
UnderlineAttr: underlineAttr,
SlowBlinkAttr: slowBlinkAttr,
RapidBlinkAttr: rapidBlinkAttr,
ReverseAttr: reverseAttr,
ConcealAttr: concealAttr,
StrikethroughAttr: strikethroughAttr,
NormalIntensityAttr: normalIntensityAttr,
NoItalicAttr: noItalicAttr,
NoUnderlineAttr: noUnderlineAttr,
NoBlinkAttr: noBlinkAttr,
NoReverseAttr: noReverseAttr,
NoConcealAttr: noConcealAttr,
NoStrikethroughAttr: noStrikethroughAttr,
BlackForegroundColorAttr: blackForegroundColorAttr,
RedForegroundColorAttr: redForegroundColorAttr,
GreenForegroundColorAttr: greenForegroundColorAttr,
YellowForegroundColorAttr: yellowForegroundColorAttr,
BlueForegroundColorAttr: blueForegroundColorAttr,
MagentaForegroundColorAttr: magentaForegroundColorAttr,
CyanForegroundColorAttr: cyanForegroundColorAttr,
WhiteForegroundColorAttr: whiteForegroundColorAttr,
ExtendedForegroundColorAttr: extendedForegroundColorAttr,
DefaultForegroundColorAttr: defaultForegroundColorAttr,
BlackBackgroundColorAttr: blackBackgroundColorAttr,
RedBackgroundColorAttr: redBackgroundColorAttr,
GreenBackgroundColorAttr: greenBackgroundColorAttr,
YellowBackgroundColorAttr: yellowBackgroundColorAttr,
BlueBackgroundColorAttr: blueBackgroundColorAttr,
MagentaBackgroundColorAttr: magentaBackgroundColorAttr,
CyanBackgroundColorAttr: cyanBackgroundColorAttr,
WhiteBackgroundColorAttr: whiteBackgroundColorAttr,
ExtendedBackgroundColorAttr: extendedBackgroundColorAttr,
DefaultBackgroundColorAttr: defaultBackgroundColorAttr,
ExtendedUnderlineColorAttr: extendedUnderlineColorAttr,
DefaultUnderlineColorAttr: defaultUnderlineColorAttr,
BrightBlackForegroundColorAttr: brightBlackForegroundColorAttr,
BrightRedForegroundColorAttr: brightRedForegroundColorAttr,
BrightGreenForegroundColorAttr: brightGreenForegroundColorAttr,
BrightYellowForegroundColorAttr: brightYellowForegroundColorAttr,
BrightBlueForegroundColorAttr: brightBlueForegroundColorAttr,
BrightMagentaForegroundColorAttr: brightMagentaForegroundColorAttr,
BrightCyanForegroundColorAttr: brightCyanForegroundColorAttr,
BrightWhiteForegroundColorAttr: brightWhiteForegroundColorAttr,
BrightBlackBackgroundColorAttr: brightBlackBackgroundColorAttr,
BrightRedBackgroundColorAttr: brightRedBackgroundColorAttr,
BrightGreenBackgroundColorAttr: brightGreenBackgroundColorAttr,
BrightYellowBackgroundColorAttr: brightYellowBackgroundColorAttr,
BrightBlueBackgroundColorAttr: brightBlueBackgroundColorAttr,
BrightMagentaBackgroundColorAttr: brightMagentaBackgroundColorAttr,
BrightCyanBackgroundColorAttr: brightCyanBackgroundColorAttr,
BrightWhiteBackgroundColorAttr: brightWhiteBackgroundColorAttr,
AttrReset: attrReset,
AttrBold: attrBold,
AttrFaint: attrFaint,
AttrItalic: attrItalic,
AttrUnderline: attrUnderline,
AttrBlink: attrBlink,
AttrRapidBlink: attrRapidBlink,
AttrReverse: attrReverse,
AttrConceal: attrConceal,
AttrStrikethrough: attrStrikethrough,
AttrNormalIntensity: attrNormalIntensity,
AttrNoItalic: attrNoItalic,
AttrNoUnderline: attrNoUnderline,
AttrNoBlink: attrNoBlink,
AttrNoReverse: attrNoReverse,
AttrNoConceal: attrNoConceal,
AttrNoStrikethrough: attrNoStrikethrough,
AttrBlackForegroundColor: attrBlackForegroundColor,
AttrRedForegroundColor: attrRedForegroundColor,
AttrGreenForegroundColor: attrGreenForegroundColor,
AttrYellowForegroundColor: attrYellowForegroundColor,
AttrBlueForegroundColor: attrBlueForegroundColor,
AttrMagentaForegroundColor: attrMagentaForegroundColor,
AttrCyanForegroundColor: attrCyanForegroundColor,
AttrWhiteForegroundColor: attrWhiteForegroundColor,
AttrExtendedForegroundColor: attrExtendedForegroundColor,
AttrDefaultForegroundColor: attrDefaultForegroundColor,
AttrBlackBackgroundColor: attrBlackBackgroundColor,
AttrRedBackgroundColor: attrRedBackgroundColor,
AttrGreenBackgroundColor: attrGreenBackgroundColor,
AttrYellowBackgroundColor: attrYellowBackgroundColor,
AttrBlueBackgroundColor: attrBlueBackgroundColor,
AttrMagentaBackgroundColor: attrMagentaBackgroundColor,
AttrCyanBackgroundColor: attrCyanBackgroundColor,
AttrWhiteBackgroundColor: attrWhiteBackgroundColor,
AttrExtendedBackgroundColor: attrExtendedBackgroundColor,
AttrDefaultBackgroundColor: attrDefaultBackgroundColor,
AttrExtendedUnderlineColor: attrExtendedUnderlineColor,
AttrDefaultUnderlineColor: attrDefaultUnderlineColor,
AttrBrightBlackForegroundColor: attrBrightBlackForegroundColor,
AttrBrightRedForegroundColor: attrBrightRedForegroundColor,
AttrBrightGreenForegroundColor: attrBrightGreenForegroundColor,
AttrBrightYellowForegroundColor: attrBrightYellowForegroundColor,
AttrBrightBlueForegroundColor: attrBrightBlueForegroundColor,
AttrBrightMagentaForegroundColor: attrBrightMagentaForegroundColor,
AttrBrightCyanForegroundColor: attrBrightCyanForegroundColor,
AttrBrightWhiteForegroundColor: attrBrightWhiteForegroundColor,
AttrBrightBlackBackgroundColor: attrBrightBlackBackgroundColor,
AttrBrightRedBackgroundColor: attrBrightRedBackgroundColor,
AttrBrightGreenBackgroundColor: attrBrightGreenBackgroundColor,
AttrBrightYellowBackgroundColor: attrBrightYellowBackgroundColor,
AttrBrightBlueBackgroundColor: attrBrightBlueBackgroundColor,
AttrBrightMagentaBackgroundColor: attrBrightMagentaBackgroundColor,
AttrBrightCyanBackgroundColor: attrBrightCyanBackgroundColor,
AttrBrightWhiteBackgroundColor: attrBrightWhiteBackgroundColor,
}

View File

@ -17,7 +17,9 @@ type Attr = int
// Style represents an ANSI SGR (Select Graphic Rendition) style.
type Style []string
// NewStyle returns a new style with the given attributes.
// NewStyle returns a new style with the given attributes. Attributes are SGR
// (Select Graphic Rendition) codes that control text formatting like bold,
// italic, colors, etc.
func NewStyle(attrs ...Attr) Style {
if len(attrs) == 0 {
return Style{}
@ -46,7 +48,8 @@ func (s Style) String() string {
return "\x1b[" + strings.Join(s, ";") + "m"
}
// Styled returns a styled string with the given style applied.
// Styled returns a styled string with the given style applied. The style is
// applied at the beginning and reset at the end of the string.
func (s Style) Styled(str string) string {
if len(s) == 0 {
return str
@ -54,161 +57,211 @@ func (s Style) Styled(str string) string {
return s.String() + str + ResetStyle
}
// Reset appends the reset style attribute to the style.
// Reset appends the reset style attribute to the style. This resets all
// formatting attributes to their defaults.
func (s Style) Reset() Style {
return append(s, resetAttr)
return append(s, attrReset)
}
// Bold appends the bold style attribute to the style.
// Bold appends the bold or normal intensity style attribute to the style.
// You can use [Style.Normal] to reset to normal intensity.
func (s Style) Bold() Style {
return append(s, boldAttr)
return append(s, attrBold)
}
// Faint appends the faint style attribute to the style.
// Faint appends the faint or normal intensity style attribute to the style.
// You can use [Style.Normal] to reset to normal intensity.
func (s Style) Faint() Style {
return append(s, faintAttr)
return append(s, attrFaint)
}
// Italic appends the italic style attribute to the style.
func (s Style) Italic() Style {
return append(s, italicAttr)
// Italic appends the italic or no italic style attribute to the style.
// When v is true, text is rendered in italic. When false, italic is disabled.
func (s Style) Italic(v bool) Style {
if v {
return append(s, attrItalic)
}
return append(s, attrNoItalic)
}
// Underline appends the underline style attribute to the style.
func (s Style) Underline() Style {
return append(s, underlineAttr)
// Underline appends the underline or no underline style attribute to the style.
// When v is true, text is underlined. When false, underline is disabled.
func (s Style) Underline(v bool) Style {
if v {
return append(s, attrUnderline)
}
return append(s, attrNoUnderline)
}
// UnderlineStyle appends the underline style attribute to the style.
// Supports various underline styles including single, double, curly, dotted,
// and dashed.
func (s Style) UnderlineStyle(u UnderlineStyle) Style {
switch u {
case NoUnderlineStyle:
return s.NoUnderline()
case SingleUnderlineStyle:
return s.Underline()
case DoubleUnderlineStyle:
return append(s, doubleUnderlineStyle)
case CurlyUnderlineStyle:
return append(s, curlyUnderlineStyle)
case DottedUnderlineStyle:
return append(s, dottedUnderlineStyle)
case DashedUnderlineStyle:
return append(s, dashedUnderlineStyle)
case UnderlineStyleNone:
return s.Underline(false)
case UnderlineStyleSingle:
return s.Underline(true)
case UnderlineStyleDouble:
return append(s, underlineStyleDouble)
case UnderlineStyleCurly:
return append(s, underlineStyleCurly)
case UnderlineStyleDotted:
return append(s, underlineStyleDotted)
case UnderlineStyleDashed:
return append(s, underlineStyleDashed)
}
return s
}
// DoubleUnderline appends the double underline style attribute to the style.
// This is a convenience method for UnderlineStyle(DoubleUnderlineStyle).
func (s Style) DoubleUnderline() Style {
return s.UnderlineStyle(DoubleUnderlineStyle)
// Blink appends the slow blink or no blink style attribute to the style.
// When v is true, text blinks slowly (less than 150 per minute). When false,
// blinking is disabled.
func (s Style) Blink(v bool) Style {
if v {
return append(s, attrBlink)
}
return append(s, attrNoBlink)
}
// CurlyUnderline appends the curly underline style attribute to the style.
// This is a convenience method for UnderlineStyle(CurlyUnderlineStyle).
func (s Style) CurlyUnderline() Style {
return s.UnderlineStyle(CurlyUnderlineStyle)
// RapidBlink appends the rapid blink or no blink style attribute to the style.
// When v is true, text blinks rapidly (150+ per minute). When false, blinking
// is disabled.
//
// Note that this is not widely supported in terminal emulators.
func (s Style) RapidBlink(v bool) Style {
if v {
return append(s, attrRapidBlink)
}
return append(s, attrNoBlink)
}
// DottedUnderline appends the dotted underline style attribute to the style.
// This is a convenience method for UnderlineStyle(DottedUnderlineStyle).
func (s Style) DottedUnderline() Style {
return s.UnderlineStyle(DottedUnderlineStyle)
// Reverse appends the reverse or no reverse style attribute to the style.
// When v is true, foreground and background colors are swapped. When false,
// reverse video is disabled.
func (s Style) Reverse(v bool) Style {
if v {
return append(s, attrReverse)
}
return append(s, attrNoReverse)
}
// DashedUnderline appends the dashed underline style attribute to the style.
// This is a convenience method for UnderlineStyle(DashedUnderlineStyle).
func (s Style) DashedUnderline() Style {
return s.UnderlineStyle(DashedUnderlineStyle)
// Conceal appends the conceal or no conceal style attribute to the style.
// When v is true, text is hidden/concealed. When false, concealment is
// disabled.
func (s Style) Conceal(v bool) Style {
if v {
return append(s, attrConceal)
}
return append(s, attrNoConceal)
}
// SlowBlink appends the slow blink style attribute to the style.
func (s Style) SlowBlink() Style {
return append(s, slowBlinkAttr)
// Strikethrough appends the strikethrough or no strikethrough style attribute
// to the style. When v is true, text is rendered with a horizontal line through
// it. When false, strikethrough is disabled.
func (s Style) Strikethrough(v bool) Style {
if v {
return append(s, attrStrikethrough)
}
return append(s, attrNoStrikethrough)
}
// RapidBlink appends the rapid blink style attribute to the style.
func (s Style) RapidBlink() Style {
return append(s, rapidBlinkAttr)
}
// Reverse appends the reverse style attribute to the style.
func (s Style) Reverse() Style {
return append(s, reverseAttr)
}
// Conceal appends the conceal style attribute to the style.
func (s Style) Conceal() Style {
return append(s, concealAttr)
}
// Strikethrough appends the strikethrough style attribute to the style.
func (s Style) Strikethrough() Style {
return append(s, strikethroughAttr)
}
// NormalIntensity appends the normal intensity style attribute to the style.
func (s Style) NormalIntensity() Style {
return append(s, normalIntensityAttr)
// Normal appends the normal intensity style attribute to the style. This
// resets [Style.Bold] and [Style.Faint] attributes.
func (s Style) Normal() Style {
return append(s, attrNormalIntensity)
}
// NoItalic appends the no italic style attribute to the style.
//
// Deprecated: use [Style.Italic](false) instead.
func (s Style) NoItalic() Style {
return append(s, noItalicAttr)
return append(s, attrNoItalic)
}
// NoUnderline appends the no underline style attribute to the style.
//
// Deprecated: use [Style.Underline](false) instead.
func (s Style) NoUnderline() Style {
return append(s, noUnderlineAttr)
return append(s, attrNoUnderline)
}
// NoBlink appends the no blink style attribute to the style.
//
// Deprecated: use [Style.Blink](false) or [Style.RapidBlink](false) instead.
func (s Style) NoBlink() Style {
return append(s, noBlinkAttr)
return append(s, attrNoBlink)
}
// NoReverse appends the no reverse style attribute to the style.
//
// Deprecated: use [Style.Reverse](false) instead.
func (s Style) NoReverse() Style {
return append(s, noReverseAttr)
return append(s, attrNoReverse)
}
// NoConceal appends the no conceal style attribute to the style.
//
// Deprecated: use [Style.Conceal](false) instead.
func (s Style) NoConceal() Style {
return append(s, noConcealAttr)
return append(s, attrNoConceal)
}
// NoStrikethrough appends the no strikethrough style attribute to the style.
//
// Deprecated: use [Style.Strikethrough](false) instead.
func (s Style) NoStrikethrough() Style {
return append(s, noStrikethroughAttr)
return append(s, attrNoStrikethrough)
}
// DefaultForegroundColor appends the default foreground color style attribute to the style.
//
// Deprecated: use [Style.ForegroundColor](nil) instead.
func (s Style) DefaultForegroundColor() Style {
return append(s, defaultForegroundColorAttr)
return append(s, attrDefaultForegroundColor)
}
// DefaultBackgroundColor appends the default background color style attribute to the style.
//
// Deprecated: use [Style.BackgroundColor](nil) instead.
func (s Style) DefaultBackgroundColor() Style {
return append(s, defaultBackgroundColorAttr)
return append(s, attrDefaultBackgroundColor)
}
// DefaultUnderlineColor appends the default underline color style attribute to the style.
//
// Deprecated: use [Style.UnderlineColor](nil) instead.
func (s Style) DefaultUnderlineColor() Style {
return append(s, defaultUnderlineColorAttr)
return append(s, attrDefaultUnderlineColor)
}
// ForegroundColor appends the foreground color style attribute to the style.
// If c is nil, the default foreground color is used. Supports [BasicColor],
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
func (s Style) ForegroundColor(c Color) Style {
if c == nil {
return append(s, attrDefaultForegroundColor)
}
return append(s, foregroundColorString(c))
}
// BackgroundColor appends the background color style attribute to the style.
// If c is nil, the default background color is used. Supports [BasicColor],
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
func (s Style) BackgroundColor(c Color) Style {
if c == nil {
return append(s, attrDefaultBackgroundColor)
}
return append(s, backgroundColorString(c))
}
// UnderlineColor appends the underline color style attribute to the style.
// If c is nil, the default underline color is used. Supports [BasicColor],
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
func (s Style) UnderlineColor(c Color) Style {
if c == nil {
return append(s, attrDefaultUnderlineColor)
}
return append(s, underlineColorString(c))
}
@ -217,146 +270,216 @@ func (s Style) UnderlineColor(c Color) Style {
type UnderlineStyle = byte
const (
doubleUnderlineStyle = "4:2"
curlyUnderlineStyle = "4:3"
dottedUnderlineStyle = "4:4"
dashedUnderlineStyle = "4:5"
underlineStyleDouble = "4:2"
underlineStyleCurly = "4:3"
underlineStyleDotted = "4:4"
underlineStyleDashed = "4:5"
)
// Underline styles constants.
const (
UnderlineStyleNone UnderlineStyle = iota
UnderlineStyleSingle
UnderlineStyleDouble
UnderlineStyleCurly
UnderlineStyleDotted
UnderlineStyleDashed
)
// Underline styles constants.
//
// Deprecated: use [UnderlineStyleNone], [UnderlineStyleSingle], etc. instead.
const (
// NoUnderlineStyle is the default underline style.
NoUnderlineStyle UnderlineStyle = iota
// SingleUnderlineStyle is a single underline style.
SingleUnderlineStyle
// DoubleUnderlineStyle is a double underline style.
DoubleUnderlineStyle
// CurlyUnderlineStyle is a curly underline style.
CurlyUnderlineStyle
// DottedUnderlineStyle is a dotted underline style.
DottedUnderlineStyle
// DashedUnderlineStyle is a dashed underline style.
DashedUnderlineStyle
)
// SGR (Select Graphic Rendition) style attributes.
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
const (
ResetAttr Attr = 0
BoldAttr Attr = 1
FaintAttr Attr = 2
ItalicAttr Attr = 3
UnderlineAttr Attr = 4
SlowBlinkAttr Attr = 5
RapidBlinkAttr Attr = 6
ReverseAttr Attr = 7
ConcealAttr Attr = 8
StrikethroughAttr Attr = 9
NormalIntensityAttr Attr = 22
NoItalicAttr Attr = 23
NoUnderlineAttr Attr = 24
NoBlinkAttr Attr = 25
NoReverseAttr Attr = 27
NoConcealAttr Attr = 28
NoStrikethroughAttr Attr = 29
BlackForegroundColorAttr Attr = 30
RedForegroundColorAttr Attr = 31
GreenForegroundColorAttr Attr = 32
YellowForegroundColorAttr Attr = 33
BlueForegroundColorAttr Attr = 34
MagentaForegroundColorAttr Attr = 35
CyanForegroundColorAttr Attr = 36
WhiteForegroundColorAttr Attr = 37
ExtendedForegroundColorAttr Attr = 38
DefaultForegroundColorAttr Attr = 39
BlackBackgroundColorAttr Attr = 40
RedBackgroundColorAttr Attr = 41
GreenBackgroundColorAttr Attr = 42
YellowBackgroundColorAttr Attr = 43
BlueBackgroundColorAttr Attr = 44
MagentaBackgroundColorAttr Attr = 45
CyanBackgroundColorAttr Attr = 46
WhiteBackgroundColorAttr Attr = 47
ExtendedBackgroundColorAttr Attr = 48
DefaultBackgroundColorAttr Attr = 49
ExtendedUnderlineColorAttr Attr = 58
DefaultUnderlineColorAttr Attr = 59
BrightBlackForegroundColorAttr Attr = 90
BrightRedForegroundColorAttr Attr = 91
BrightGreenForegroundColorAttr Attr = 92
BrightYellowForegroundColorAttr Attr = 93
BrightBlueForegroundColorAttr Attr = 94
BrightMagentaForegroundColorAttr Attr = 95
BrightCyanForegroundColorAttr Attr = 96
BrightWhiteForegroundColorAttr Attr = 97
BrightBlackBackgroundColorAttr Attr = 100
BrightRedBackgroundColorAttr Attr = 101
BrightGreenBackgroundColorAttr Attr = 102
BrightYellowBackgroundColorAttr Attr = 103
BrightBlueBackgroundColorAttr Attr = 104
BrightMagentaBackgroundColorAttr Attr = 105
BrightCyanBackgroundColorAttr Attr = 106
BrightWhiteBackgroundColorAttr Attr = 107
AttrReset Attr = 0
AttrBold Attr = 1
AttrFaint Attr = 2
AttrItalic Attr = 3
AttrUnderline Attr = 4
AttrBlink Attr = 5
AttrRapidBlink Attr = 6
AttrReverse Attr = 7
AttrConceal Attr = 8
AttrStrikethrough Attr = 9
AttrNormalIntensity Attr = 22
AttrNoItalic Attr = 23
AttrNoUnderline Attr = 24
AttrNoBlink Attr = 25
AttrNoReverse Attr = 27
AttrNoConceal Attr = 28
AttrNoStrikethrough Attr = 29
AttrBlackForegroundColor Attr = 30
AttrRedForegroundColor Attr = 31
AttrGreenForegroundColor Attr = 32
AttrYellowForegroundColor Attr = 33
AttrBlueForegroundColor Attr = 34
AttrMagentaForegroundColor Attr = 35
AttrCyanForegroundColor Attr = 36
AttrWhiteForegroundColor Attr = 37
AttrExtendedForegroundColor Attr = 38
AttrDefaultForegroundColor Attr = 39
AttrBlackBackgroundColor Attr = 40
AttrRedBackgroundColor Attr = 41
AttrGreenBackgroundColor Attr = 42
AttrYellowBackgroundColor Attr = 43
AttrBlueBackgroundColor Attr = 44
AttrMagentaBackgroundColor Attr = 45
AttrCyanBackgroundColor Attr = 46
AttrWhiteBackgroundColor Attr = 47
AttrExtendedBackgroundColor Attr = 48
AttrDefaultBackgroundColor Attr = 49
AttrExtendedUnderlineColor Attr = 58
AttrDefaultUnderlineColor Attr = 59
AttrBrightBlackForegroundColor Attr = 90
AttrBrightRedForegroundColor Attr = 91
AttrBrightGreenForegroundColor Attr = 92
AttrBrightYellowForegroundColor Attr = 93
AttrBrightBlueForegroundColor Attr = 94
AttrBrightMagentaForegroundColor Attr = 95
AttrBrightCyanForegroundColor Attr = 96
AttrBrightWhiteForegroundColor Attr = 97
AttrBrightBlackBackgroundColor Attr = 100
AttrBrightRedBackgroundColor Attr = 101
AttrBrightGreenBackgroundColor Attr = 102
AttrBrightYellowBackgroundColor Attr = 103
AttrBrightBlueBackgroundColor Attr = 104
AttrBrightMagentaBackgroundColor Attr = 105
AttrBrightCyanBackgroundColor Attr = 106
AttrBrightWhiteBackgroundColor Attr = 107
RGBColorIntroducerAttr Attr = 2
ExtendedColorIntroducerAttr Attr = 5
AttrRGBColorIntroducer Attr = 2
AttrExtendedColorIntroducer Attr = 5
)
// SGR (Select Graphic Rendition) style attributes.
//
// Deprecated: use Attr* constants instead.
const (
ResetAttr = AttrReset
BoldAttr = AttrBold
FaintAttr = AttrFaint
ItalicAttr = AttrItalic
UnderlineAttr = AttrUnderline
SlowBlinkAttr = AttrBlink
RapidBlinkAttr = AttrRapidBlink
ReverseAttr = AttrReverse
ConcealAttr = AttrConceal
StrikethroughAttr = AttrStrikethrough
NormalIntensityAttr = AttrNormalIntensity
NoItalicAttr = AttrNoItalic
NoUnderlineAttr = AttrNoUnderline
NoBlinkAttr = AttrNoBlink
NoReverseAttr = AttrNoReverse
NoConcealAttr = AttrNoConceal
NoStrikethroughAttr = AttrNoStrikethrough
BlackForegroundColorAttr = AttrBlackForegroundColor
RedForegroundColorAttr = AttrRedForegroundColor
GreenForegroundColorAttr = AttrGreenForegroundColor
YellowForegroundColorAttr = AttrYellowForegroundColor
BlueForegroundColorAttr = AttrBlueForegroundColor
MagentaForegroundColorAttr = AttrMagentaForegroundColor
CyanForegroundColorAttr = AttrCyanForegroundColor
WhiteForegroundColorAttr = AttrWhiteForegroundColor
ExtendedForegroundColorAttr = AttrExtendedForegroundColor
DefaultForegroundColorAttr = AttrDefaultForegroundColor
BlackBackgroundColorAttr = AttrBlackBackgroundColor
RedBackgroundColorAttr = AttrRedBackgroundColor
GreenBackgroundColorAttr = AttrGreenBackgroundColor
YellowBackgroundColorAttr = AttrYellowBackgroundColor
BlueBackgroundColorAttr = AttrBlueBackgroundColor
MagentaBackgroundColorAttr = AttrMagentaBackgroundColor
CyanBackgroundColorAttr = AttrCyanBackgroundColor
WhiteBackgroundColorAttr = AttrWhiteBackgroundColor
ExtendedBackgroundColorAttr = AttrExtendedBackgroundColor
DefaultBackgroundColorAttr = AttrDefaultBackgroundColor
ExtendedUnderlineColorAttr = AttrExtendedUnderlineColor
DefaultUnderlineColorAttr = AttrDefaultUnderlineColor
BrightBlackForegroundColorAttr = AttrBrightBlackForegroundColor
BrightRedForegroundColorAttr = AttrBrightRedForegroundColor
BrightGreenForegroundColorAttr = AttrBrightGreenForegroundColor
BrightYellowForegroundColorAttr = AttrBrightYellowForegroundColor
BrightBlueForegroundColorAttr = AttrBrightBlueForegroundColor
BrightMagentaForegroundColorAttr = AttrBrightMagentaForegroundColor
BrightCyanForegroundColorAttr = AttrBrightCyanForegroundColor
BrightWhiteForegroundColorAttr = AttrBrightWhiteForegroundColor
BrightBlackBackgroundColorAttr = AttrBrightBlackBackgroundColor
BrightRedBackgroundColorAttr = AttrBrightRedBackgroundColor
BrightGreenBackgroundColorAttr = AttrBrightGreenBackgroundColor
BrightYellowBackgroundColorAttr = AttrBrightYellowBackgroundColor
BrightBlueBackgroundColorAttr = AttrBrightBlueBackgroundColor
BrightMagentaBackgroundColorAttr = AttrBrightMagentaBackgroundColor
BrightCyanBackgroundColorAttr = AttrBrightCyanBackgroundColor
BrightWhiteBackgroundColorAttr = AttrBrightWhiteBackgroundColor
RGBColorIntroducerAttr = AttrRGBColorIntroducer
ExtendedColorIntroducerAttr = AttrExtendedColorIntroducer
)
const (
resetAttr = "0"
boldAttr = "1"
faintAttr = "2"
italicAttr = "3"
underlineAttr = "4"
slowBlinkAttr = "5"
rapidBlinkAttr = "6"
reverseAttr = "7"
concealAttr = "8"
strikethroughAttr = "9"
normalIntensityAttr = "22"
noItalicAttr = "23"
noUnderlineAttr = "24"
noBlinkAttr = "25"
noReverseAttr = "27"
noConcealAttr = "28"
noStrikethroughAttr = "29"
blackForegroundColorAttr = "30"
redForegroundColorAttr = "31"
greenForegroundColorAttr = "32"
yellowForegroundColorAttr = "33"
blueForegroundColorAttr = "34"
magentaForegroundColorAttr = "35"
cyanForegroundColorAttr = "36"
whiteForegroundColorAttr = "37"
extendedForegroundColorAttr = "38"
defaultForegroundColorAttr = "39"
blackBackgroundColorAttr = "40"
redBackgroundColorAttr = "41"
greenBackgroundColorAttr = "42"
yellowBackgroundColorAttr = "43"
blueBackgroundColorAttr = "44"
magentaBackgroundColorAttr = "45"
cyanBackgroundColorAttr = "46"
whiteBackgroundColorAttr = "47"
extendedBackgroundColorAttr = "48"
defaultBackgroundColorAttr = "49"
extendedUnderlineColorAttr = "58"
defaultUnderlineColorAttr = "59"
brightBlackForegroundColorAttr = "90"
brightRedForegroundColorAttr = "91"
brightGreenForegroundColorAttr = "92"
brightYellowForegroundColorAttr = "93"
brightBlueForegroundColorAttr = "94"
brightMagentaForegroundColorAttr = "95"
brightCyanForegroundColorAttr = "96"
brightWhiteForegroundColorAttr = "97"
brightBlackBackgroundColorAttr = "100"
brightRedBackgroundColorAttr = "101"
brightGreenBackgroundColorAttr = "102"
brightYellowBackgroundColorAttr = "103"
brightBlueBackgroundColorAttr = "104"
brightMagentaBackgroundColorAttr = "105"
brightCyanBackgroundColorAttr = "106"
brightWhiteBackgroundColorAttr = "107"
attrReset = "0"
attrBold = "1"
attrFaint = "2"
attrItalic = "3"
attrUnderline = "4"
attrBlink = "5"
attrRapidBlink = "6"
attrReverse = "7"
attrConceal = "8"
attrStrikethrough = "9"
attrNormalIntensity = "22"
attrNoItalic = "23"
attrNoUnderline = "24"
attrNoBlink = "25"
attrNoReverse = "27"
attrNoConceal = "28"
attrNoStrikethrough = "29"
attrBlackForegroundColor = "30"
attrRedForegroundColor = "31"
attrGreenForegroundColor = "32"
attrYellowForegroundColor = "33"
attrBlueForegroundColor = "34"
attrMagentaForegroundColor = "35"
attrCyanForegroundColor = "36"
attrWhiteForegroundColor = "37"
attrExtendedForegroundColor = "38"
attrDefaultForegroundColor = "39"
attrBlackBackgroundColor = "40"
attrRedBackgroundColor = "41"
attrGreenBackgroundColor = "42"
attrYellowBackgroundColor = "43"
attrBlueBackgroundColor = "44"
attrMagentaBackgroundColor = "45"
attrCyanBackgroundColor = "46"
attrWhiteBackgroundColor = "47"
attrExtendedBackgroundColor = "48"
attrDefaultBackgroundColor = "49"
attrExtendedUnderlineColor = "58"
attrDefaultUnderlineColor = "59"
attrBrightBlackForegroundColor = "90"
attrBrightRedForegroundColor = "91"
attrBrightGreenForegroundColor = "92"
attrBrightYellowForegroundColor = "93"
attrBrightBlueForegroundColor = "94"
attrBrightMagentaForegroundColor = "95"
attrBrightCyanForegroundColor = "96"
attrBrightWhiteForegroundColor = "97"
attrBrightBlackBackgroundColor = "100"
attrBrightRedBackgroundColor = "101"
attrBrightGreenBackgroundColor = "102"
attrBrightYellowBackgroundColor = "103"
attrBrightBlueBackgroundColor = "104"
attrBrightMagentaBackgroundColor = "105"
attrBrightCyanBackgroundColor = "106"
attrBrightWhiteBackgroundColor = "107"
)
// foregroundColorString returns the style SGR attribute for the given
@ -369,37 +492,37 @@ func foregroundColorString(c Color) string {
// "3<n>" or "9<n>" where n is the color number from 0 to 7
switch c {
case Black:
return blackForegroundColorAttr
return attrBlackForegroundColor
case Red:
return redForegroundColorAttr
return attrRedForegroundColor
case Green:
return greenForegroundColorAttr
return attrGreenForegroundColor
case Yellow:
return yellowForegroundColorAttr
return attrYellowForegroundColor
case Blue:
return blueForegroundColorAttr
return attrBlueForegroundColor
case Magenta:
return magentaForegroundColorAttr
return attrMagentaForegroundColor
case Cyan:
return cyanForegroundColorAttr
return attrCyanForegroundColor
case White:
return whiteForegroundColorAttr
return attrWhiteForegroundColor
case BrightBlack:
return brightBlackForegroundColorAttr
return attrBrightBlackForegroundColor
case BrightRed:
return brightRedForegroundColorAttr
return attrBrightRedForegroundColor
case BrightGreen:
return brightGreenForegroundColorAttr
return attrBrightGreenForegroundColor
case BrightYellow:
return brightYellowForegroundColorAttr
return attrBrightYellowForegroundColor
case BrightBlue:
return brightBlueForegroundColorAttr
return attrBrightBlueForegroundColor
case BrightMagenta:
return brightMagentaForegroundColorAttr
return attrBrightMagentaForegroundColor
case BrightCyan:
return brightCyanForegroundColorAttr
return attrBrightCyanForegroundColor
case BrightWhite:
return brightWhiteForegroundColorAttr
return attrBrightWhiteForegroundColor
}
case ExtendedColor:
// 256-color ANSI foreground
@ -414,7 +537,7 @@ func foregroundColorString(c Color) string {
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10)
}
return defaultForegroundColorAttr
return attrDefaultForegroundColor
}
// backgroundColorString returns the style SGR attribute for the given
@ -427,37 +550,37 @@ func backgroundColorString(c Color) string {
// "4<n>" or "10<n>" where n is the color number from 0 to 7
switch c {
case Black:
return blackBackgroundColorAttr
return attrBlackBackgroundColor
case Red:
return redBackgroundColorAttr
return attrRedBackgroundColor
case Green:
return greenBackgroundColorAttr
return attrGreenBackgroundColor
case Yellow:
return yellowBackgroundColorAttr
return attrYellowBackgroundColor
case Blue:
return blueBackgroundColorAttr
return attrBlueBackgroundColor
case Magenta:
return magentaBackgroundColorAttr
return attrMagentaBackgroundColor
case Cyan:
return cyanBackgroundColorAttr
return attrCyanBackgroundColor
case White:
return whiteBackgroundColorAttr
return attrWhiteBackgroundColor
case BrightBlack:
return brightBlackBackgroundColorAttr
return attrBrightBlackBackgroundColor
case BrightRed:
return brightRedBackgroundColorAttr
return attrBrightRedBackgroundColor
case BrightGreen:
return brightGreenBackgroundColorAttr
return attrBrightGreenBackgroundColor
case BrightYellow:
return brightYellowBackgroundColorAttr
return attrBrightYellowBackgroundColor
case BrightBlue:
return brightBlueBackgroundColorAttr
return attrBrightBlueBackgroundColor
case BrightMagenta:
return brightMagentaBackgroundColorAttr
return attrBrightMagentaBackgroundColor
case BrightCyan:
return brightCyanBackgroundColorAttr
return attrBrightCyanBackgroundColor
case BrightWhite:
return brightWhiteBackgroundColorAttr
return attrBrightWhiteBackgroundColor
}
case ExtendedColor:
// 256-color ANSI foreground
@ -472,7 +595,7 @@ func backgroundColorString(c Color) string {
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10)
}
return defaultBackgroundColorAttr
return attrDefaultBackgroundColor
}
// underlineColorString returns the style SGR attribute for the given underline
@ -498,7 +621,7 @@ func underlineColorString(c Color) string {
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10)
}
return defaultUnderlineColorAttr
return attrDefaultUnderlineColor
}
// ReadStyleColor decodes a color from a slice of parameters. It returns the
@ -526,7 +649,7 @@ func underlineColorString(c Color) string {
// 2. Support ignoring and omitting the color space id (second parameter) with respect to RGB colors
// 3. Support ignoring and omitting the 6th parameter with respect to RGB and CMY colors
// 4. Support reading RGBA colors
func ReadStyleColor(params Params, co *color.Color) (n int) {
func ReadStyleColor(params Params, co *color.Color) int {
if len(params) < 2 { // Need at least SGR type and color type
return 0
}
@ -535,7 +658,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
s := params[0]
p := params[1]
colorType := p.Param(0)
n = 2
n := 2
paramsfn := func() (p1, p2, p3, p4 int) {
// Where should we start reading the color?
@ -594,7 +717,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
B: uint8(b), //nolint:gosec
A: 0xff,
}
return //nolint:nakedret
return n
case 3: // CMY direct color
if len(params) < 5 {
@ -612,7 +735,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
Y: uint8(y), //nolint:gosec
K: 0,
}
return //nolint:nakedret
return n
case 4: // CMYK direct color
if len(params) < 6 {
@ -630,7 +753,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
Y: uint8(y), //nolint:gosec
K: uint8(k), //nolint:gosec
}
return //nolint:nakedret
return n
case 5: // indexed color
if len(params) < 3 {
@ -665,7 +788,7 @@ func ReadStyleColor(params Params, co *color.Color) (n int) {
B: uint8(b), //nolint:gosec
A: uint8(a), //nolint:gosec
}
return //nolint:nakedret
return n
default:
return 0

View File

@ -1,11 +1,11 @@
package ansi
import (
"bytes"
"strings"
"github.com/charmbracelet/x/ansi/parser"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
"github.com/clipperhouse/displaywidth"
"github.com/clipperhouse/uax29/v2/graphemes"
)
// Cut the string, without adding any prefix or tail strings. This function is
@ -74,12 +74,11 @@ func truncate(m Method, s string, length int, tail string) string {
return ""
}
var cluster []byte
var buf bytes.Buffer
var cluster string
var buf strings.Builder
curWidth := 0
ignoring := false
pstate := parser.GroundState // initial state
b := []byte(s)
i := 0
// Here we iterate over the bytes of the string and collect printable
@ -88,16 +87,12 @@ func truncate(m Method, s string, length int, tail string) string {
//
// Once we reach the given length, we start ignoring characters and only
// collect ANSI escape codes until we reach the end of string.
for i < len(b) {
state, action := parser.Table.Transition(pstate, b[i])
for i < len(s) {
state, action := parser.Table.Transition(pstate, s[i])
if state == parser.Utf8State {
// This action happens when we transition to the Utf8State.
var width int
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
cluster, width = FirstGraphemeCluster(s[i:], m)
// increment the index by the length of the cluster
i += len(cluster)
curWidth += width
@ -118,7 +113,7 @@ func truncate(m Method, s string, length int, tail string) string {
continue
}
buf.Write(cluster)
buf.WriteString(cluster)
// Done collecting, now we're back in the ground state.
pstate = parser.GroundState
@ -152,7 +147,7 @@ func truncate(m Method, s string, length int, tail string) string {
}
fallthrough
default:
buf.WriteByte(b[i])
buf.WriteByte(s[i])
i++
}
@ -193,27 +188,23 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
return s
}
var cluster []byte
var buf bytes.Buffer
var cluster string
var buf strings.Builder
curWidth := 0
ignoring := true
pstate := parser.GroundState
b := []byte(s)
i := 0
for i < len(b) {
for i < len(s) {
if !ignoring {
buf.Write(b[i:])
buf.WriteString(s[i:])
break
}
state, action := parser.Table.Transition(pstate, b[i])
state, action := parser.Table.Transition(pstate, s[i])
if state == parser.Utf8State {
var width int
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
cluster, width = FirstGraphemeCluster(s[i:], m)
i += len(cluster)
curWidth += width
@ -224,7 +215,7 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
}
if curWidth > n {
buf.Write(cluster)
buf.WriteString(cluster)
}
if ignoring {
@ -259,7 +250,7 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
}
fallthrough
default:
buf.WriteByte(b[i])
buf.WriteByte(s[i])
i++
}
@ -278,22 +269,22 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
// You can use this with [Truncate], [TruncateLeft], and [Cut].
func ByteToGraphemeRange(str string, byteStart, byteStop int) (charStart, charStop int) {
bytePos, charPos := 0, 0
gr := uniseg.NewGraphemes(str)
gr := graphemes.FromString(str)
for byteStart > bytePos {
if !gr.Next() {
break
}
bytePos += len(gr.Str())
charPos += max(1, gr.Width())
bytePos += len(gr.Value())
charPos += max(1, displaywidth.String(gr.Value()))
}
charStart = charPos
for byteStop > bytePos {
if !gr.Next() {
break
}
bytePos += len(gr.Str())
charPos += max(1, gr.Width())
bytePos += len(gr.Value())
charPos += max(1, displaywidth.String(gr.Value()))
}
charStop = charPos
return
return charStart, charStop
}

17
vendor/github.com/charmbracelet/x/ansi/urxvt.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
package ansi
import (
"fmt"
"strings"
)
// URxvtExt returns an escape sequence for calling a URxvt perl extension with
// the given name and parameters.
//
// OSC 777 ; extension_name ; param1 ; param2 ; ... ST
// OSC 777 ; extension_name ; param1 ; param2 ; ... BEL
//
// See: https://man.archlinux.org/man/extra/rxvt-unicode/urxvt.7.en#XTerm_Operating_System_Commands
func URxvtExt(extension string, params ...string) string {
return fmt.Sprintf("\x1b]777;%s;%s\x07", extension, strings.Join(params, ";"))
}

View File

@ -4,8 +4,6 @@ import (
"bytes"
"github.com/charmbracelet/x/ansi/parser"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// Strip removes ANSI escape codes from a string.
@ -83,20 +81,16 @@ func stringWidth(m Method, s string) int {
}
var (
pstate = parser.GroundState // initial state
cluster string
width int
pstate = parser.GroundState // initial state
width int
)
for i := 0; i < len(s); i++ {
state, action := parser.Table.Transition(pstate, s[i])
if state == parser.Utf8State {
var w int
cluster, _, w, _ = uniseg.FirstGraphemeClusterInString(s[i:], -1)
if m == WcWidth {
w = runewidth.StringWidth(cluster)
}
cluster, w := FirstGraphemeCluster(s[i:], m)
width += w
i += len(cluster) - 1
pstate = parser.GroundState
continue

View File

@ -8,16 +8,22 @@ import (
const (
// ResizeWindowWinOp is a window operation that resizes the terminal
// window.
//
// Deprecated: Use constant number directly with [WindowOp].
ResizeWindowWinOp = 4
// RequestWindowSizeWinOp is a window operation that requests a report of
// the size of the terminal window in pixels. The response is in the form:
// CSI 4 ; height ; width t
//
// Deprecated: Use constant number directly with [WindowOp].
RequestWindowSizeWinOp = 14
// RequestCellSizeWinOp is a window operation that requests a report of
// the size of the terminal cell size in pixels. The response is in the form:
// CSI 6 ; height ; width t
//
// Deprecated: Use constant number directly with [WindowOp].
RequestCellSizeWinOp = 16
)

View File

@ -2,12 +2,11 @@ package ansi
import (
"bytes"
"strings"
"unicode"
"unicode/utf8"
"github.com/charmbracelet/x/ansi/parser"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// nbsp is a non-breaking space.
@ -55,12 +54,9 @@ func hardwrap(m Method, s string, limit int, preserveSpace bool) string {
i := 0
for i < len(b) {
state, action := parser.Table.Transition(pstate, b[i])
if state == parser.Utf8State { //nolint:nestif
if state == parser.Utf8State {
var width int
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
cluster, width = FirstGraphemeCluster(b[i:], m)
i += len(cluster)
if curWidth+width > limit {
@ -192,10 +188,7 @@ func wordwrap(m Method, s string, limit int, breakpoints string) string {
state, action := parser.Table.Transition(pstate, b[i])
if state == parser.Utf8State { //nolint:nestif
var width int
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
cluster, width = FirstGraphemeCluster(b[i:], m)
i += len(cluster)
r, _ := utf8.DecodeRune(cluster)
@ -303,7 +296,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
}
var (
cluster []byte
cluster string
buf bytes.Buffer
word bytes.Buffer
space bytes.Buffer
@ -311,10 +304,12 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
curWidth int // written width of the line
wordLen int // word buffer len without ANSI escape codes
pstate = parser.GroundState // initial state
b = []byte(s)
)
addSpace := func() {
if spaceWidth == 0 && space.Len() == 0 {
return
}
curWidth += spaceWidth
buf.Write(space.Bytes())
space.Reset()
@ -341,30 +336,27 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
}
i := 0
for i < len(b) {
state, action := parser.Table.Transition(pstate, b[i])
for i < len(s) {
state, action := parser.Table.Transition(pstate, s[i])
if state == parser.Utf8State { //nolint:nestif
var width int
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
cluster, width = FirstGraphemeCluster(s[i:], m)
i += len(cluster)
r, _ := utf8.DecodeRune(cluster)
r, _ := utf8.DecodeRuneInString(cluster)
switch {
case r != utf8.RuneError && unicode.IsSpace(r) && r != nbsp: // nbsp is a non-breaking space
addWord()
space.WriteRune(r)
spaceWidth += width
case bytes.ContainsAny(cluster, breakpoints):
case strings.ContainsAny(cluster, breakpoints):
addSpace()
if curWidth+wordLen+width > limit {
word.Write(cluster)
word.WriteString(cluster)
wordLen += width
} else {
addWord()
buf.Write(cluster)
buf.WriteString(cluster)
curWidth += width
}
default:
@ -373,12 +365,17 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
addWord()
}
word.Write(cluster)
word.WriteString(cluster)
wordLen += width
if curWidth+wordLen+spaceWidth > limit {
addNewline()
}
if wordLen == limit {
// Hardwrap the word if it's too long
addWord()
}
}
pstate = parser.GroundState
@ -387,7 +384,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
switch action {
case parser.PrintAction, parser.ExecuteAction:
switch r := rune(b[i]); {
switch r := rune(s[i]); {
case r == '\n':
if wordLen == 0 {
if curWidth+spaceWidth > limit {
@ -424,6 +421,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
if curWidth == limit {
addNewline()
}
word.WriteRune(r)
wordLen++
@ -438,7 +436,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
}
default:
word.WriteByte(b[i])
word.WriteByte(s[i])
}
// We manage the UTF8 state separately manually above.

View File

@ -1,3 +1,4 @@
// Package cellbuf provides terminal cell buffer functionality.
package cellbuf
import (
@ -24,7 +25,7 @@ func NewCell(r rune, comb ...rune) (c *Cell) {
}
c.Comb = comb
c.Width = runewidth.StringWidth(string(append([]rune{r}, comb...)))
return
return c
}
// NewCellString returns a new cell with the given string content. This is a
@ -46,7 +47,7 @@ func NewCellString(s string) (c *Cell) {
c.Comb = append(c.Comb, r)
}
}
return
return c
}
// NewGraphemeCell returns a new cell. This is a convenience function that
@ -71,7 +72,7 @@ func newGraphemeCell(s string, w int) (c *Cell) {
c.Comb = append(c.Comb, r)
}
}
return
return c
}
// Line represents a line in the terminal.
@ -104,7 +105,7 @@ func (l Line) String() (s string) {
}
}
s = strings.TrimRight(s, " ")
return
return s
}
// At returns the cell at the given x position.
@ -150,7 +151,7 @@ func (l Line) set(x int, c *Cell, clone bool) bool {
for j := 1; j < maxCellWidth && x-j >= 0; j++ {
wide := l.At(x - j)
if wide != nil && wide.Width > 1 && j < wide.Width {
for k := 0; k < wide.Width; k++ {
for k := range wide.Width {
l[x-j+k] = wide.Clone().Blank()
}
break
@ -206,7 +207,7 @@ func (b *Buffer) String() (s string) {
s += "\r\n"
}
}
return
return s
}
// Line returns a pointer to the line at the given y position.
@ -296,7 +297,7 @@ func (b *Buffer) FillRect(c *Cell, rect Rectangle) {
}
for y := rect.Min.Y; y < rect.Max.Y; y++ {
for x := rect.Min.X; x < rect.Max.X; x += cellWidth {
b.setCell(x, y, c, false) //nolint:errcheck
b.setCell(x, y, c, false)
}
}
}

View File

@ -96,7 +96,7 @@ func (c *Cell) Clear() bool {
func (c *Cell) Clone() (n *Cell) {
n = new(Cell)
*n = *c
return
return n
}
// Blank makes the cell a blank cell by setting the rune to a space, comb to
@ -164,12 +164,12 @@ type UnderlineStyle = ansi.UnderlineStyle
// These are the available underline styles.
const (
NoUnderline = ansi.NoUnderlineStyle
SingleUnderline = ansi.SingleUnderlineStyle
DoubleUnderline = ansi.DoubleUnderlineStyle
CurlyUnderline = ansi.CurlyUnderlineStyle
DottedUnderline = ansi.DottedUnderlineStyle
DashedUnderline = ansi.DashedUnderlineStyle
NoUnderline = ansi.UnderlineStyleNone
SingleUnderline = ansi.UnderlineStyleSingle
DoubleUnderline = ansi.UnderlineStyleDouble
CurlyUnderline = ansi.UnderlineStyleCurly
DottedUnderline = ansi.UnderlineStyleDotted
DashedUnderline = ansi.UnderlineStyleDashed
)
// Style represents the Style of a cell.
@ -189,7 +189,7 @@ func (s Style) Sequence() string {
var b ansi.Style
if s.Attrs != 0 {
if s.Attrs != 0 { //nolint:nestif
if s.Attrs&BoldAttr != 0 {
b = b.Bold()
}
@ -197,36 +197,31 @@ func (s Style) Sequence() string {
b = b.Faint()
}
if s.Attrs&ItalicAttr != 0 {
b = b.Italic()
b = b.Italic(true)
}
if s.Attrs&SlowBlinkAttr != 0 {
b = b.SlowBlink()
b = b.Blink(true)
}
if s.Attrs&RapidBlinkAttr != 0 {
b = b.RapidBlink()
b = b.RapidBlink(true)
}
if s.Attrs&ReverseAttr != 0 {
b = b.Reverse()
b = b.Reverse(true)
}
if s.Attrs&ConcealAttr != 0 {
b = b.Conceal()
b = b.Conceal(true)
}
if s.Attrs&StrikethroughAttr != 0 {
b = b.Strikethrough()
b = b.Strikethrough(true)
}
}
if s.UlStyle != NoUnderline {
switch s.UlStyle {
case SingleUnderline:
b = b.Underline()
case DoubleUnderline:
b = b.DoubleUnderline()
case CurlyUnderline:
b = b.CurlyUnderline()
case DottedUnderline:
b = b.DottedUnderline()
case DashedUnderline:
b = b.DashedUnderline()
switch u := s.UlStyle; u {
case NoUnderline:
b = b.Underline(false)
default:
b = b.Underline(true)
b = b.UnderlineStyle(u)
}
}
if s.Fg != nil {
@ -268,64 +263,48 @@ func (s Style) DiffSequence(o Style) string {
isNormal bool
)
if s.Attrs != o.Attrs {
if s.Attrs != o.Attrs { //nolint:nestif
if s.Attrs&BoldAttr != o.Attrs&BoldAttr {
if s.Attrs&BoldAttr != 0 {
b = b.Bold()
} else if !isNormal {
isNormal = true
b = b.NormalIntensity()
b = b.Normal()
}
}
if s.Attrs&FaintAttr != o.Attrs&FaintAttr {
if s.Attrs&FaintAttr != 0 {
b = b.Faint()
} else if !isNormal {
b = b.NormalIntensity()
b = b.Normal()
}
}
if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr {
if s.Attrs&ItalicAttr != 0 {
b = b.Italic()
} else {
b = b.NoItalic()
}
b = b.Italic(s.Attrs&ItalicAttr != 0)
}
if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr {
if s.Attrs&SlowBlinkAttr != 0 {
b = b.SlowBlink()
b = b.Blink(true)
} else if !noBlink {
noBlink = true
b = b.NoBlink()
b = b.Blink(false)
}
}
if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr {
if s.Attrs&RapidBlinkAttr != 0 {
b = b.RapidBlink()
b = b.RapidBlink(true)
} else if !noBlink {
b = b.NoBlink()
b = b.Blink(false)
}
}
if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr {
if s.Attrs&ReverseAttr != 0 {
b = b.Reverse()
} else {
b = b.NoReverse()
}
b = b.Reverse(s.Attrs&ReverseAttr != 0)
}
if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr {
if s.Attrs&ConcealAttr != 0 {
b = b.Conceal()
} else {
b = b.NoConceal()
}
b = b.Conceal(s.Attrs&ConcealAttr != 0)
}
if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr {
if s.Attrs&StrikethroughAttr != 0 {
b = b.Strikethrough()
} else {
b = b.NoStrikethrough()
}
b = b.Strikethrough(s.Attrs&StrikethroughAttr != 0)
}
}

View File

@ -12,7 +12,7 @@ func Pos(x, y int) Position {
return image.Pt(x, y)
}
// Rectange represents a rectangle.
// Rectangle represents a rectangle.
type Rectangle = image.Rectangle
// Rect is a shorthand for Rectangle.

View File

@ -75,7 +75,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
)
blank := s.clearBlank()
if n > 0 {
if n > 0 { //nolint:nestif
// Scroll up (forward)
v = s.scrollUp(n, top, bot, 0, maxY, blank)
if !v {
@ -99,7 +99,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
s.move(0, bot-n+1)
s.clearToBottom(nil)
} else {
for i := 0; i < n; i++ {
for i := range n {
s.move(0, bot-i)
s.clearToEnd(nil, false)
}
@ -124,7 +124,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
// Clear newly shifted-in lines.
if v &&
(nonDestScrollRegion || (memoryBelow && top == 0)) {
for i := 0; i < -n; i++ {
for i := range -n {
s.move(0, top+i)
s.clearToEnd(nil, false)
}
@ -133,7 +133,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
}
if !v {
return
return v
}
s.scrollBuffer(s.curbuf, n, top, bot, blank)
@ -193,7 +193,7 @@ func (s *Screen) touchLine(width, height, y, n int, changed bool) {
// scrollUp scrolls the screen up by n lines.
func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
if n == 1 && top == minY && bot == maxY {
if n == 1 && top == minY && bot == maxY { //nolint:nestif
s.move(0, bot)
s.updatePen(blank)
s.buf.WriteByte('\n')
@ -202,13 +202,14 @@ func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
s.updatePen(blank)
s.buf.WriteString(ansi.DeleteLine(1))
} else if top == minY && bot == maxY {
if s.xtermLike {
supportsSU := s.caps.Contains(capSU)
if supportsSU {
s.move(0, bot)
} else {
s.move(0, top)
}
s.updatePen(blank)
if s.xtermLike {
if supportsSU {
s.buf.WriteString(ansi.ScrollUp(n))
} else {
s.buf.WriteString(strings.Repeat("\n", n))
@ -225,7 +226,7 @@ func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
// scrollDown scrolls the screen down by n lines.
func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
if n == 1 && top == minY && bot == maxY {
if n == 1 && top == minY && bot == maxY { //nolint:nestif
s.move(0, top)
s.updatePen(blank)
s.buf.WriteString(ansi.ReverseIndex)
@ -236,7 +237,7 @@ func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
} else if top == minY && bot == maxY {
s.move(0, top)
s.updatePen(blank)
if s.xtermLike {
if s.caps.Contains(capSD) {
s.buf.WriteString(ansi.ScrollDown(n))
} else {
s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n))

View File

@ -15,7 +15,7 @@ func hash(l Line) (h uint64) {
}
h += (h << 5) + uint64(r)
}
return
return h
}
// hashmap represents a single [Line] hash.
@ -33,7 +33,7 @@ func (s *Screen) updateHashmap() {
height := s.newbuf.Height()
if len(s.oldhash) >= height && len(s.newhash) >= height {
// rehash changed lines
for i := 0; i < height; i++ {
for i := range height {
_, ok := s.touch[i]
if ok {
s.oldhash[i] = hash(s.curbuf.Line(i))
@ -48,14 +48,14 @@ func (s *Screen) updateHashmap() {
if len(s.newhash) != height {
s.newhash = make([]uint64, height)
}
for i := 0; i < height; i++ {
for i := range height {
s.oldhash[i] = hash(s.curbuf.Line(i))
s.newhash[i] = hash(s.newbuf.Line(i))
}
}
s.hashtab = make([]hashmap, height*2)
for i := 0; i < height; i++ {
for i := range height {
hashval := s.oldhash[i]
// Find matching hash or empty slot
@ -71,7 +71,7 @@ func (s *Screen) updateHashmap() {
s.hashtab[idx].oldcount++
s.hashtab[idx].oldindex = i
}
for i := 0; i < height; i++ {
for i := range height {
hashval := s.newhash[i]
// Find matching hash or empty slot
@ -130,7 +130,7 @@ func (s *Screen) updateHashmap() {
s.growHunks()
}
// scrollOldhash
// scrollOldhash.
func (s *Screen) scrollOldhash(n, top, bot int) {
if len(s.oldhash) == 0 {
return
@ -287,7 +287,7 @@ func (s *Screen) updateCost(from, to Line) (cost int) {
cost++
}
}
return
return cost
}
func (s *Screen) updateCostBlank(to Line) (cost int) {
@ -297,5 +297,5 @@ func (s *Screen) updateCostBlank(to Line) (cost int) {
cost++
}
}
return
return cost
}

View File

@ -4,7 +4,7 @@ import (
"github.com/charmbracelet/colorprofile"
)
// Convert converts a hyperlink to respect the given color profile.
// ConvertLink converts a hyperlink to respect the given color profile.
func ConvertLink(h Link, p colorprofile.Profile) Link {
if p == colorprofile.NoTTY {
return Link{}

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