diff --git a/apps/cli-e2e/src/tests/telemetry.e2e.test.ts b/apps/cli-e2e/src/tests/telemetry.e2e.test.ts index 83a2e43a68..5d428a2e93 100644 --- a/apps/cli-e2e/src/tests/telemetry.e2e.test.ts +++ b/apps/cli-e2e/src/tests/telemetry.e2e.test.ts @@ -1,4 +1,4 @@ -import { chmodSync, writeFileSync } from "node:fs"; +import { chmodSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect } from "vitest"; import { testBehaviour, testParity } from "./test-context.ts"; @@ -58,9 +58,12 @@ describe("telemetry", () => { }); testBehaviour("handles corrupted config gracefully", async ({ run, workspace }) => { - writeFileSync(join(workspace.path, "telemetry.json"), "{{not valid json}}"); + const telemetryPath = join(workspace.path, "telemetry.json"); + writeFileSync(telemetryPath, "{{not valid json}}"); const result = await run(["telemetry", "status"]); - expect(result.exitCode).not.toBe(0); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/Telemetry is (enabled|disabled)\./); + expect(() => JSON.parse(readFileSync(telemetryPath, "utf8"))).not.toThrow(); }); testParity(["telemetry", "status"]); diff --git a/apps/cli-go/go.mod b/apps/cli-go/go.mod index f162428538..d208a593b3 100644 --- a/apps/cli-go/go.mod +++ b/apps/cli-go/go.mod @@ -17,12 +17,12 @@ require ( github.com/docker/cli v28.5.2+incompatible github.com/docker/compose/v2 v2.40.3 github.com/docker/docker v28.5.2+incompatible - github.com/docker/go-connections v0.6.0 + github.com/docker/go-connections v0.7.0 github.com/docker/go-units v0.5.0 github.com/fsnotify/fsnotify v1.9.0 github.com/getsentry/sentry-go v0.44.1 github.com/go-errors/errors v1.5.1 - github.com/go-git/go-git/v5 v5.17.2 + github.com/go-git/go-git/v5 v5.19.1 github.com/go-playground/validator/v10 v10.30.2 github.com/go-viper/mapstructure/v2 v2.5.0 github.com/go-xmlfmt/xmlfmt v1.1.3 @@ -53,12 +53,12 @@ require ( github.com/tidwall/jsonc v0.3.3 github.com/withfig/autocomplete-tools/packages/cobra v1.2.0 github.com/zalando/go-keyring v0.2.8 - go.opentelemetry.io/otel v1.43.0 - golang.org/x/mod v0.34.0 - golang.org/x/net v0.52.0 + go.opentelemetry.io/otel v1.44.0 + golang.org/x/mod v0.35.0 + golang.org/x/net v0.55.0 golang.org/x/oauth2 v0.36.0 - golang.org/x/term v0.41.0 - google.golang.org/grpc v1.80.0 + golang.org/x/term v0.43.0 + google.golang.org/grpc v1.81.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -78,7 +78,7 @@ require ( github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect - github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/alecthomas/chroma/v2 v2.20.0 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect @@ -91,19 +91,20 @@ require ( github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.2.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect - github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect - github.com/aws/smithy-go v1.20.3 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.7 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.7 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect + github.com/aws/smithy-go v1.24.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -133,17 +134,17 @@ require ( github.com/clipperhouse/uax29/v2 v2.6.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/console v1.0.5 // indirect - github.com/containerd/containerd/api v1.9.0 // indirect - github.com/containerd/containerd/v2 v2.1.5 // indirect + github.com/containerd/containerd/api v1.10.0 // indirect + github.com/containerd/containerd/v2 v2.2.4 // indirect github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v1.0.0-rc.1 // indirect + github.com/containerd/platforms v1.0.0-rc.2 // indirect github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/containers/storage v1.59.1 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/daixiang0/gci v0.13.6 // indirect github.com/danieljoos/wincred v1.2.3 // indirect github.com/dave/dst v0.27.3 // indirect @@ -156,13 +157,13 @@ require ( github.com/docker/buildx v0.29.1 // indirect github.com/docker/cli-docs-tool v0.10.0 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.3 // indirect + github.com/docker/docker-credential-helpers v0.9.5 // indirect github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/ecies/go/v2 v2.0.11 // indirect github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/ethereum/go-ethereum v1.17.0 // indirect @@ -173,21 +174,33 @@ require ( github.com/firefart/nonamedreturns v1.0.6 // indirect github.com/fsnotify/fsevents v0.2.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/getkin/kin-openapi v0.131.0 // indirect github.com/ghostiam/protogetter v0.3.15 // indirect github.com/go-critic/go-critic v0.13.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.8.0 // indirect + github.com/go-git/go-billy/v5 v5.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect @@ -199,7 +212,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect - github.com/gofrs/flock v0.12.1 // indirect + github.com/gofrs/flock v0.13.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -212,19 +225,19 @@ require ( github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect - github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/certificate-transparency-go v1.3.2 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/websocket v1.5.3 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.5.0 // indirect github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -233,7 +246,8 @@ require ( github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect - github.com/in-toto/in-toto-golang v0.9.0 // indirect + github.com/in-toto/attestation v1.1.2 // indirect + github.com/in-toto/in-toto-golang v0.11.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect @@ -254,7 +268,8 @@ require ( github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kisielk/errcheck v1.9.0 // indirect github.com/kkHAIKE/contextcheck v1.1.6 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.5 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kulti/thelper v0.6.3 // indirect @@ -289,12 +304,12 @@ require ( github.com/mithrandie/go-file/v2 v2.1.0 // indirect github.com/mithrandie/go-text v1.6.0 // indirect github.com/mithrandie/ternary v1.1.1 // indirect - github.com/moby/buildkit v0.25.1 // indirect + github.com/moby/buildkit v0.26.3 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.1.0 // indirect github.com/moby/locker v1.0.1 // indirect - github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/spdystream v0.5.1 // indirect github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/capability v0.4.0 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect @@ -305,10 +320,10 @@ require ( github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/moricho/tparallel v0.3.2 // indirect - github.com/morikuni/aec v1.0.0 // indirect + github.com/morikuni/aec v1.1.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect @@ -330,7 +345,7 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -355,12 +370,12 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect - github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect github.com/securego/gosec/v2 v2.22.3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect @@ -381,7 +396,7 @@ require ( github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect - github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect + github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f // indirect github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect @@ -410,34 +425,34 @@ require ( go.augendre.info/fatcontext v0.8.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect - go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.49.0 // indirect + golang.org/x/crypto v0.51.0 // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect - golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.42.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.44.0 // indirect golang.org/x/tools/go/expect v0.1.1-deprecated // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -446,18 +461,19 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gotest.tools/gotestsum v1.12.2 // indirect honnef.co/go/tools v0.6.1 // indirect - k8s.io/api v0.32.3 // indirect - k8s.io/apimachinery v0.32.3 // indirect - k8s.io/client-go v0.32.3 // indirect + k8s.io/api v0.34.1 // indirect + k8s.io/apimachinery v0.34.1 // indirect + k8s.io/client-go v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect mvdan.cc/gofumpt v0.9.1 // indirect mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect - sigs.k8s.io/yaml v1.5.0 // indirect - tags.cncf.io/container-device-interface v1.0.1 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect + tags.cncf.io/container-device-interface v1.1.0 // indirect ) replace github.com/supabase/cli/pkg v1.0.0 => ./pkg diff --git a/apps/cli-go/go.sum b/apps/cli-go/go.sum index 6280921fdc..7db471ece1 100644 --- a/apps/cli-go/go.sum +++ b/apps/cli-go/go.sum @@ -2,8 +2,12 @@ 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= +cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= +cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E= github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= @@ -33,14 +37,14 @@ github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= -github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= +github.com/Microsoft/hcsshim v0.14.1 h1:CMuB3fqQVfPdhyXhUqYdUmPUIOhJkmghCx3dJet8Cqs= +github.com/Microsoft/hcsshim v0.14.1/go.mod h1:VnzvPLyWUhxiPVsJ31P6XadxCcTogTguBFDy/1GR/OM= github.com/Netflix/go-env v0.1.2 h1:0DRoLR9lECQ9Zqvkswuebm3jJ/2enaDX6Ei8/Z+EnK0= github.com/Netflix/go-env v0.1.2/go.mod h1:WlIhYi++8FlKNJtrop1mjXYAJMzv1f43K4MqCoh0yGE= github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d h1:hi6J4K6DKrR4/ljxn6SF6nURyu785wKMuQcjt7H3VCQ= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= @@ -82,32 +86,34 @@ github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9 github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= -github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= -github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= -github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= -github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= -github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= +github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY= +github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY= +github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8= +github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= @@ -202,14 +208,14 @@ github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUo github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/compose-spec/compose-go/v2 v2.9.1 h1:8UwI+ujNU+9Ffkf/YgAm/qM9/eU7Jn8nHzWG721W4rs= github.com/compose-spec/compose-go/v2 v2.9.1/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE= -github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= -github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= +github.com/containerd/cgroups/v3 v3.1.2 h1:OSosXMtkhI6Qove637tg1XgK4q+DhR0mX8Wi8EhrHa4= +github.com/containerd/cgroups/v3 v3.1.2/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= -github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= -github.com/containerd/containerd/v2 v2.1.5 h1:pWSmPxUszaLZKQPvOx27iD4iH+aM6o0BoN9+hg77cro= -github.com/containerd/containerd/v2 v2.1.5/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM= +github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= +github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= +github.com/containerd/containerd/v2 v2.2.4 h1:8x2UdXqww7NYqGNabQ7i1nAgB5LegzjC9KQzO/900iA= +github.com/containerd/containerd/v2 v2.2.4/go.mod h1:YBcTO8D9149QY9zNmUjy04Mhuc4DlrZQ8FIOwKZEM7o= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -220,15 +226,15 @@ github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/nydus-snapshotter v0.15.2 h1:qsHI4M+Wwrf6Jr4eBqhNx8qh+YU0dSiJ+WPmcLFWNcg= -github.com/containerd/nydus-snapshotter v0.15.2/go.mod h1:FfwH2KBkNYoisK/e+KsmNr7xTU53DmnavQHMFOcXwfM= -github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= -github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= +github.com/containerd/nydus-snapshotter v0.15.4 h1:l59kGRVMtwMLDLh322HsWhEsBCkRKMkGWYV5vBeLYCE= +github.com/containerd/nydus-snapshotter v0.15.4/go.mod h1:eRJqnxQDr48HNop15kZdLZpFF5B6vf6Q11Aq1K0E4Ms= +github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= +github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= -github.com/containerd/stargz-snapshotter v0.16.3 h1:zbQMm8dRuPHEOD4OqAYGajJJUwCeUzt4j7w9Iaw58u4= -github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= -github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= +github.com/containerd/stargz-snapshotter v0.17.0 h1:djNS4KU8ztFhLdEDZ1bsfzOiYuVHT6TgSU5qwRk+cNc= +github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE= +github.com/containerd/stargz-snapshotter/estargz v0.17.0/go.mod h1:s06tWAiJcXQo9/8AReBCIo/QxcXFZ2n4qfsRnpl71SM= github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= @@ -246,8 +252,8 @@ github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= -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.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/daixiang0/gci v0.13.6 h1:RKuEOSkGpSadkGbvZ6hJ4ddItT3cVZ9Vn9Rybk6xjl8= github.com/daixiang0/gci v0.13.6/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= @@ -284,13 +290,13 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 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.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.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= +github.com/docker/docker-credential-helpers v0.9.5/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= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= @@ -308,8 +314,8 @@ github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJ github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= @@ -335,8 +341,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= @@ -355,12 +361,12 @@ github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8b github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= -github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= +github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= +github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104= -github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= +github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00= +github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -371,14 +377,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -390,8 +422,8 @@ github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCW github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -428,8 +460,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= -github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= -github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -473,10 +505,11 @@ github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2 github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= -github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A= +github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -486,7 +519,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -495,13 +527,11 @@ github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzU github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -514,8 +544,8 @@ github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= @@ -529,8 +559,8 @@ github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 h1:kEISI/Gx67NzH3nJxAmY/dGac80kKZgZt134u7Y/k1s= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4/go.mod h1:6Nz966r3vQYCqIzWsuEl9d7cf7mRhtDmm++sOxlnfxI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= @@ -557,8 +587,10 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= -github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= +github.com/in-toto/attestation v1.1.2 h1:MBFn6lsMq6dptQZJBhalXTcWMb/aJy3V+GX3VYj/V1E= +github.com/in-toto/attestation v1.1.2/go.mod h1:gYFddHMZj3DiQ0b62ltNi1Vj5rC879bTmBbrv9CRHpM= +github.com/in-toto/in-toto-golang v0.11.0 h1:nfidMYBFx+E0lnmX5KUnN2Pdm8zdNKal1ayjJuzzRoA= +github.com/in-toto/in-toto-golang v0.11.0/go.mod h1:u3PjTnwFKjp5a1YCcw8SJg0G+tMeKfVoWsWeFMDCMtw= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -655,13 +687,14 @@ github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= -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.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +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/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= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -703,7 +736,6 @@ github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/manuelarte/funcorder v0.2.1 h1:7QJsw3qhljoZ5rH0xapIvjw31EcQeFbF31/7kQ/xS34= @@ -756,18 +788,18 @@ github.com/mithrandie/go-text v1.6.0 h1:8gOXTMPbMY8DJbKMTv8kHhADcJlDWXqS/YQH4SyW github.com/mithrandie/go-text v1.6.0/go.mod h1:xCgj1xiNbI/d4xA9sLVvXkjh5B2tNx2ZT2/3rpmh8to= github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QRdC4= github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g= -github.com/moby/buildkit v0.25.1 h1:j7IlVkeNbEo+ZLoxdudYCHpmTsbwKvhgc/6UJ/mY/o8= -github.com/moby/buildkit v0.25.1/go.mod h1:phM8sdqnvgK2y1dPDnbwI6veUCXHOZ6KFSl6E164tkc= +github.com/moby/buildkit v0.26.3 h1:D+ruZVAk/3ipRq5XRxBH9/DIFpRjSlTtMbghT5gQP9g= +github.com/moby/buildkit v0.26.3/go.mod h1:4T4wJzQS4kYWIfFRjsbJry4QoxDBjK+UGOEOs1izL7w= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 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 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -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/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= @@ -791,14 +823,15 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ= +github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -869,10 +902,10 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= -github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= -github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= +github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= +github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= +github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= @@ -894,8 +927,8 @@ github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wB github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= -github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= +github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -975,8 +1008,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84d github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= -github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= +github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14= +github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk= github.com/securego/gosec/v2 v2.22.3 h1:mRrCNmRF2NgZp4RJ8oJ6yPJ7G4x6OCiAXHd8x4trLRc= github.com/securego/gosec/v2 v2.22.3/go.mod h1:42M9Xs0v1WseinaB/BmNGO8AVqG8vRfhC2686ACY48k= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -996,8 +1029,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= @@ -1080,8 +1113,8 @@ github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+ github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4= github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY= -github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI= -github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= +github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f h1:Z4NEQ86qFl1mHuCu9gwcE+EYCwDKfXAYXZbdIXyxmEA= +github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE= github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= @@ -1098,8 +1131,8 @@ github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYR github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= -github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= -github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= +github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= github.com/withfig/autocomplete-tools/packages/cobra v1.2.0 h1:MzD3XeOOSO3mAjOPpF07jFteSKZxsRHvlIcAR9RQzKM= @@ -1160,32 +1193,34 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 h1:0tY123n7CdWMem7MOVdKOt0YfshufLCwfE5Bob+hQuM= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 h1:RuynHbfU8JUEw7DyONgkVYg2SVtsoF28y0LGIr69jgA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0/go.mod h1:qZF+/lBs71APw8mlnEZcqZHMzqrYrsFiJOv83lX1OGo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -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.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= 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/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1230,10 +1265,10 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= -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/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= @@ -1252,8 +1287,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1279,8 +1314,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1341,8 +1376,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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= @@ -1353,8 +1388,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -1367,10 +1402,10 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -1399,8 +1434,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -1413,13 +1448,13 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM= -google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= 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= @@ -1474,28 +1509,29 @@ gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= mvdan.cc/gofumpt v0.9.1 h1:p5YT2NfFWsYyTieYgwcQ8aKV3xRvFH4uuN/zB2gBbMQ= mvdan.cc/gofumpt v0.9.1/go.mod h1:3xYtNemnKiXaTh6R4VtlqDATFwBbdXI8lJvH/4qk7mw= mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8= mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= -sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= -tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc= -tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +tags.cncf.io/container-device-interface v1.1.0 h1:RnxNhxF1JOu6CJUVpetTYvrXHdxw9j9jFYgZpI+anSY= +tags.cncf.io/container-device-interface v1.1.0/go.mod h1:76Oj0Yqp9FwTx/pySDc8Bxjpg+VqXfDb50cKAXVJ34Q= diff --git a/apps/cli-go/internal/status/status.go b/apps/cli-go/internal/status/status.go index d16c8d7b2f..ae0639903f 100644 --- a/apps/cli-go/internal/status/status.go +++ b/apps/cli-go/internal/status/status.go @@ -52,17 +52,20 @@ func (c *CustomName) toValues(exclude ...string) map[string]string { c.DbURL: fmt.Sprintf("postgresql://%s@%s:%d/postgres", url.UserPassword("postgres", utils.Config.Db.Password), utils.Config.Hostname, utils.Config.Db.Port), } - apiEnabled := utils.Config.Api.Enabled && !slices.Contains(exclude, utils.RestId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Api.Image)) + kongEnabled := utils.Config.Api.Enabled && !slices.Contains(exclude, utils.KongId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Api.KongImage)) + postgrestEnabled := kongEnabled && !slices.Contains(exclude, utils.RestId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Api.Image)) studioEnabled := utils.Config.Studio.Enabled && !slices.Contains(exclude, utils.StudioId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Studio.Image)) authEnabled := utils.Config.Auth.Enabled && !slices.Contains(exclude, utils.GotrueId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Auth.Image)) inbucketEnabled := utils.Config.Inbucket.Enabled && !slices.Contains(exclude, utils.InbucketId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Inbucket.Image)) storageEnabled := utils.Config.Storage.Enabled && !slices.Contains(exclude, utils.StorageId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Storage.Image)) functionsEnabled := utils.Config.EdgeRuntime.Enabled && !slices.Contains(exclude, utils.EdgeRuntimeId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.EdgeRuntime.Image)) - if apiEnabled { + if kongEnabled { values[c.ApiURL] = utils.Config.Api.ExternalUrl - values[c.RestURL] = utils.GetApiUrl("/rest/v1") - values[c.GraphqlURL] = utils.GetApiUrl("/graphql/v1") + if postgrestEnabled { + values[c.RestURL] = utils.GetApiUrl("/rest/v1") + values[c.GraphqlURL] = utils.GetApiUrl("/graphql/v1") + } if functionsEnabled { values[c.FunctionsURL] = utils.GetApiUrl("/functions/v1") } diff --git a/apps/cli-go/internal/utils/git.go b/apps/cli-go/internal/utils/git.go index 2c2ad70337..328e7f94ae 100644 --- a/apps/cli-go/internal/utils/git.go +++ b/apps/cli-go/internal/utils/git.go @@ -16,8 +16,9 @@ func GetGitBranchOrDefault(def string, fsys afero.Fs) string { if len(head) > 0 { return head } - opts := &git.PlainOpenOptions{DetectDotGit: true} - if repo, err := git.PlainOpenWithOptions(".", opts); err == nil { + if root, err := findGitRoot("."); err != nil { + return def + } else if repo, err := git.PlainOpen(root); err == nil { if ref, err := repo.Head(); err == nil { return ref.Name().Short() } diff --git a/apps/cli-go/internal/utils/misc.go b/apps/cli-go/internal/utils/misc.go index 6e164acd91..8e9870531b 100644 --- a/apps/cli-go/internal/utils/misc.go +++ b/apps/cli-go/internal/utils/misc.go @@ -159,14 +159,16 @@ func AssertServiceIsRunning(ctx context.Context, containerId string) error { } func IsGitRepo() bool { - opts := &git.PlainOpenOptions{DetectDotGit: true} - _, err := git.PlainOpenWithOptions(".", opts) + _, err := findGitRoot(".") return err == nil } func IsGitIgnored(fp ...string) (bool, error) { - opts := &git.PlainOpenOptions{DetectDotGit: true} - repo, err := git.PlainOpenWithOptions(".", opts) + root, err := findGitRoot(".") + if err != nil { + return false, err + } + repo, err := git.PlainOpen(root) if err != nil { return false, err } @@ -182,6 +184,25 @@ func IsGitIgnored(fp ...string) (bool, error) { return m.Match(fp, false), nil } +func findGitRoot(path string) (string, error) { + cwd, err := filepath.Abs(path) + if err != nil { + return "", err + } + for { + if _, err := os.Stat(filepath.Join(cwd, git.GitDirName)); err == nil { + return cwd, nil + } else if !errors.Is(err, os.ErrNotExist) { + return "", err + } + if parent := filepath.Dir(cwd); parent != cwd { + cwd = parent + } else { + return "", git.ErrRepositoryNotExists + } + } +} + // If the `os.Getwd()` is within a supabase project, this will return // the root of the given project as the current working directory. // Otherwise, the `os.Getwd()` is kept as is. diff --git a/apps/cli/AGENTS.md b/apps/cli/AGENTS.md index dc2d2382b6..c30465cd85 100644 --- a/apps/cli/AGENTS.md +++ b/apps/cli/AGENTS.md @@ -367,6 +367,18 @@ yield * creating.clear(); // dismiss without a message yield * creating.succeed("Branch created"); ``` +### Invariant: `-o json|yaml|toml|env` must suppress the spinner (CLI-1546) + +The Go-compat `-o`/`--output` flag (`LegacyOutputFlag`, values `env|pretty|json|toml|yaml`) is **independent** of `--output-format`. It does not change `output.format`, so a command run with `-o json` (and no `--output-format`) keeps `output.format === "text"` and the spinner gate `output.format === "text"` stays `true`. If the plain `textOutputLayer` is active, clack writes spinner ANSI (e.g. the hide-cursor `\x1b[?25l`) to **stdout** and corrupts the machine payload the handler emits via `output.raw` — exactly the CLI-1546 regression (`branches list -o json` → broken `JSON.parse`). + +`legacy/cli/root.ts` therefore selects **`legacyQuietProgressTextOutputLayer`** (in `legacy/output/`) for any Go machine format (`json|yaml|toml|env`). It is a legacy-only wrapper over the shared `textOutputLayer` that no-ops only `task` and `progress`; everything else — `format: "text"`, `raw`, logs, and error rendering (red text on **stderr**) — delegates unchanged, so Go output parity is preserved exactly. + +Rules: + +- **stdout is payload-only whenever a machine format is requested** (`-o json|yaml|toml|env` or `--output-format json|stream-json`). All progress/diagnostic output goes to stderr. +- **Do not** fix spinner-on-stdout by routing the shared spinner to stderr or otherwise editing `shared/output/output.layer.ts` — that changes `next/` text rendering. Keep the fix legacy-scoped. +- A handler reaching this path still emits its machine payload through the Go encoder (`output.raw(encodeGoJson(...))` etc.), checked **before** the `output.format` branch, so output stays byte-identical to before — minus the spinner. + --- ## Testing diff --git a/apps/cli/docs/go-cli-porting-status.md b/apps/cli/docs/go-cli-porting-status.md index 5cdd1aa2fa..f17de55366 100644 --- a/apps/cli/docs/go-cli-porting-status.md +++ b/apps/cli/docs/go-cli-porting-status.md @@ -254,13 +254,13 @@ Legend: | `network-bans remove` | `ported` | [`../src/legacy/commands/network-bans/remove/remove.command.ts`](../src/legacy/commands/network-bans/remove/remove.command.ts) | | `network-restrictions get` | `ported` | [`../src/legacy/commands/network-restrictions/get/get.command.ts`](../src/legacy/commands/network-restrictions/get/get.command.ts) | | `network-restrictions update` | `ported` | [`../src/legacy/commands/network-restrictions/update/update.command.ts`](../src/legacy/commands/network-restrictions/update/update.command.ts) | -| `encryption get-root-key` | `wrapped` | [`../src/legacy/commands/encryption/get-root-key/get-root-key.command.ts`](../src/legacy/commands/encryption/get-root-key/get-root-key.command.ts) | -| `encryption update-root-key` | `wrapped` | [`../src/legacy/commands/encryption/update-root-key/update-root-key.command.ts`](../src/legacy/commands/encryption/update-root-key/update-root-key.command.ts) | +| `encryption get-root-key` | `ported` | [`../src/legacy/commands/encryption/get-root-key/get-root-key.command.ts`](../src/legacy/commands/encryption/get-root-key/get-root-key.command.ts) | +| `encryption update-root-key` | `ported` | [`../src/legacy/commands/encryption/update-root-key/update-root-key.command.ts`](../src/legacy/commands/encryption/update-root-key/update-root-key.command.ts) | | `ssl-enforcement get` | `ported` | [`../src/legacy/commands/ssl-enforcement/get/get.command.ts`](../src/legacy/commands/ssl-enforcement/get/get.command.ts) | | `ssl-enforcement update` | `ported` | [`../src/legacy/commands/ssl-enforcement/update/update.command.ts`](../src/legacy/commands/ssl-enforcement/update/update.command.ts) | -| `postgres-config get` | `wrapped` | [`../src/legacy/commands/postgres-config/get/get.command.ts`](../src/legacy/commands/postgres-config/get/get.command.ts) | -| `postgres-config update` | `wrapped` | [`../src/legacy/commands/postgres-config/update/update.command.ts`](../src/legacy/commands/postgres-config/update/update.command.ts) | -| `postgres-config delete` | `wrapped` | [`../src/legacy/commands/postgres-config/delete/delete.command.ts`](../src/legacy/commands/postgres-config/delete/delete.command.ts) | +| `postgres-config get` | `ported` | [`../src/legacy/commands/postgres-config/get/get.command.ts`](../src/legacy/commands/postgres-config/get/get.command.ts) | +| `postgres-config update` | `ported` | [`../src/legacy/commands/postgres-config/update/update.command.ts`](../src/legacy/commands/postgres-config/update/update.command.ts) | +| `postgres-config delete` | `ported` | [`../src/legacy/commands/postgres-config/delete/delete.command.ts`](../src/legacy/commands/postgres-config/delete/delete.command.ts) | | `login` | `wrapped` | [`../src/legacy/commands/login/login.command.ts`](../src/legacy/commands/login/login.command.ts) | | `logout` | `wrapped` | [`../src/legacy/commands/logout/logout.command.ts`](../src/legacy/commands/logout/logout.command.ts) | | `link` | `wrapped` | [`../src/legacy/commands/link/link.command.ts`](../src/legacy/commands/link/link.command.ts) | diff --git a/apps/cli/scripts/build.ts b/apps/cli/scripts/build.ts index ca2a67a6da..53adf26f28 100644 --- a/apps/cli/scripts/build.ts +++ b/apps/cli/scripts/build.ts @@ -86,6 +86,10 @@ const root = path.resolve(import.meta.dir, "../../.."); const entrypoint = path.join(root, "apps/cli/src", shell, "main.ts"); const distDir = path.join(root, "dist"); const goSource = path.resolve(root, "apps/cli-go"); +const posthogBuildDefines = [ + `--define=process.env.SUPABASE_CLI_POSTHOG_KEY=${JSON.stringify(process.env.POSTHOG_API_KEY ?? "")}`, + `--define=process.env.SUPABASE_CLI_POSTHOG_HOST=${JSON.stringify(process.env.POSTHOG_ENDPOINT ?? "")}`, +] as const; type BunTarget = (typeof TARGETS)[number]["bunTarget"]; @@ -113,7 +117,7 @@ async function buildTarget(target: (typeof TARGETS)[number]) { const libc = libcForBunTarget(target.bunTarget); console.log(`[${target.pkg}] Compiling Bun CLI...`); - await $`bun build ${entrypoint} --compile --minify --target=${target.bunTarget} --define=process.env.SUPABASE_CLI_VERSION=${JSON.stringify(version)} --define=SUPABASE_LIBC=${JSON.stringify(libc)} --outfile=${outfile}`; + await $`bun build ${entrypoint} --compile --minify --target=${target.bunTarget} --define=process.env.SUPABASE_CLI_VERSION=${JSON.stringify(version)} --define=SUPABASE_LIBC=${JSON.stringify(libc)} ${posthogBuildDefines} --outfile=${outfile}`; console.log(`[${target.pkg}] Done.`); } @@ -184,7 +188,7 @@ async function buildMuslBinaries() { const outfile = path.join(binDir, "supabase"); const libc = libcForBunTarget(target.bunTarget); console.log(`[${target.pkg}] Compiling Bun CLI (musl)...`); - await $`bun build ${entrypoint} --compile --minify --target=${target.bunTarget} --define=process.env.SUPABASE_CLI_VERSION=${JSON.stringify(version)} --define=SUPABASE_LIBC=${JSON.stringify(libc)} --outfile=${outfile}`; + await $`bun build ${entrypoint} --compile --minify --target=${target.bunTarget} --define=process.env.SUPABASE_CLI_VERSION=${JSON.stringify(version)} --define=SUPABASE_LIBC=${JSON.stringify(libc)} ${posthogBuildDefines} --outfile=${outfile}`; if (shell === "legacy") { // Go binary is CGO_ENABLED=0 (fully static), so the glibc Linux build works on diff --git a/apps/cli/src/legacy/cli/root.ts b/apps/cli/src/legacy/cli/root.ts index 792b281d49..6fe2791320 100644 --- a/apps/cli/src/legacy/cli/root.ts +++ b/apps/cli/src/legacy/cli/root.ts @@ -37,6 +37,7 @@ import { legacyUnlinkCommand } from "../commands/unlink/unlink.command.ts"; import { legacyVanitySubdomainsCommand } from "../commands/vanity-subdomains/vanity-subdomains.command.ts"; import { OutputFormatFlag } from "../../shared/cli/global-flags.ts"; import { outputLayerFor } from "../../shared/output/output.layer.ts"; +import { legacyQuietProgressTextOutputLayer } from "../output/legacy-quiet-progress-text-output.layer.ts"; import { makeGoProxyLayer } from "../../shared/legacy/go-proxy.layer.ts"; import { LEGACY_GLOBAL_FLAGS, @@ -125,7 +126,18 @@ export const legacyRoot = Command.make("supabase").pipe( if (createTicket) globalArgs.push("--create-ticket"); if (agent !== "auto") globalArgs.push("--agent", agent); - return Layer.mergeAll(outputLayerFor(outputFormat), makeGoProxyLayer({ globalArgs })); + // Go's `-o {json,yaml,toml,env}` selects a machine encoder the handler + // writes via `output.raw`. Keep the text layer (so errors still render + // as red text on stderr, matching Go), but suppress its progress spinner + // — otherwise clack writes ANSI to stdout and corrupts the payload + // (CLI-1546). `-o pretty` / no `-o` keep the normal text/json layers. + const goFmt = Option.getOrUndefined(goOutput); + const isGoMachineFormat = goFmt !== undefined && goFmt !== "pretty"; + const outputLayer = isGoMachineFormat + ? legacyQuietProgressTextOutputLayer + : outputLayerFor(outputFormat); + + return Layer.mergeAll(outputLayer, makeGoProxyLayer({ globalArgs })); }), ), ), diff --git a/apps/cli/src/legacy/commands/encryption/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/encryption/SIDE_EFFECTS.md new file mode 100644 index 0000000000..458b7aeb86 --- /dev/null +++ b/apps/cli/src/legacy/commands/encryption/SIDE_EFFECTS.md @@ -0,0 +1,88 @@ +# `supabase encryption [get-root-key|update-root-key]` + +Manage a project's pgsodium root encryption key. Each subcommand resolves a +project ref and calls one Management API endpoint. `update-root-key` +additionally reads the new key from stdin. + +## Files Read + +| Path | Format | When | +| ------------------------------------------------ | ------------------------- | ------------------------------------------------------------------ | +| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | +| `~/.supabase//linked-project.json` | JSON | when `--project-ref` / `PROJECT_ID` unset, to resolve linked ref | +| stdin | raw bytes / masked TTY | `update-root-key` only — masked TTY input or piped bytes (the key) | + +## Files Written + +| Path | Format | When | +| ------------------------------------------------ | ------ | ------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | PersistentPostRun, after the project ref resolves | +| `~/.supabase/telemetry.json` | JSON | PersistentPostRun, on success or failure | + +## API Routes + +| Method | Path | Auth | Request body | Response (used fields) | +| ------ | ----------------------------- | ------------ | ------------ | ---------------------- | +| `GET` | `/v1/projects/{ref}/pgsodium` | Bearer token | none | `{root_key}` | +| `PUT` | `/v1/projects/{ref}/pgsodium` | Bearer token | `{root_key}` | `{root_key}` | + +`get-root-key` calls `GET`; `update-root-key` calls `PUT`. + +## Environment Variables + +| Variable | Purpose | Required? | +| ------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------- | +| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring → `~/.supabase/access-token`) | +| `SUPABASE_PROJECT_ID` / `PROJECT_ID` | project ref (fallback when `--project-ref` unset) | no (falls back to linked-project file → prompt) | +| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | +| `SUPABASE_PROFILE` | built-in profile name or YAML file path | no (defaults to `supabase`) | + +## Exit Codes + +| Code | Condition | +| ---- | ----------------------------------------- | +| `0` | success | +| `1` | project ref unresolved / malformed | +| `1` | network / connection failure | +| `1` | non-200 status from the pgsodium endpoint | + +## Telemetry Events Fired + +| Event | When | Notable properties / groups | +| ---------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- | +| `cli_command_executed` | post-run, success or failure (via wrapper) | `exit_code`, `duration_ms`, `flags` (`--project-ref` redacted — not telemetry-safe) | + +No custom `phtelemetry.*` events in `internal/encryption/`. + +## Output + +### `--output-format text` (Go CLI compatible) + +- `get-root-key`: the bare root key followed by a newline, to **stdout** (Go `fmt.Println`). +- `update-root-key`: `Finished supabase root-key update.` followed by a newline, to **stderr** + (Go's `utils.Aqua` color rendered as plain text per the legacy-port convention). + +### `--output-format json` + +A single JSON object emitted to stdout: `{"root_key":"…"}` (both subcommands). + +### `--output-format stream-json` + +One `result` event carrying `{root_key}` (both subcommands). + +```ndjson +{"type":"result","data":{"root_key":"…"}} +``` + +## Notes + +- Requires `--project-ref`, `SUPABASE_PROJECT_ID`/`PROJECT_ID`, or a linked project. +- `update-root-key` reads the key from stdin: a real TTY is read with a masked + prompt; piped stdin is decoded as UTF-8 and whitespace-trimmed. An empty or + whitespace-only key sends an empty `root_key`, matching Go's `io.Copy` + + `strings.TrimSpace` behavior. (The TTY masked prompt also trims, matching Go.) +- **Known divergence:** Go writes the bare prompt `Enter a new root key: ` to + stderr and reads via `term.ReadPassword`. The port uses a clack masked prompt + with the same label text, so the rendered TTY prompt is not byte-identical to + Go (clack adds its own framing). Piped (non-TTY) mode does not print the prompt + at all — it reads stdin directly, as Go's `io.Copy` branch does. diff --git a/apps/cli/src/legacy/commands/encryption/encryption.command.ts b/apps/cli/src/legacy/commands/encryption/encryption.command.ts index 7e4e743704..3a0bd1a8b5 100644 --- a/apps/cli/src/legacy/commands/encryption/encryption.command.ts +++ b/apps/cli/src/legacy/commands/encryption/encryption.command.ts @@ -3,7 +3,7 @@ import { legacyEncryptionGetRootKeyCommand } from "./get-root-key/get-root-key.c import { legacyEncryptionUpdateRootKeyCommand } from "./update-root-key/update-root-key.command.ts"; export const legacyEncryptionCommand = Command.make("encryption").pipe( - Command.withDescription("Manage encryption keys of Supabase projects."), + Command.withDescription("Manage encryption keys of Supabase projects"), Command.withShortDescription("Manage encryption keys"), Command.withSubcommands([ legacyEncryptionGetRootKeyCommand, diff --git a/apps/cli/src/legacy/commands/encryption/encryption.e2e.test.ts b/apps/cli/src/legacy/commands/encryption/encryption.e2e.test.ts new file mode 100644 index 0000000000..528f35dca9 --- /dev/null +++ b/apps/cli/src/legacy/commands/encryption/encryption.e2e.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from "vitest"; +import { runSupabase } from "../../../../tests/helpers/cli.ts"; + +const E2E_TIMEOUT_MS = 30_000; +const TEST_TOKEN = "sbp_" + "a".repeat(40); + +describe("supabase encryption (legacy)", () => { + // Golden-path e2e: validates real subprocess dispatch + ref-resolution error + // wiring for the get path. With an isolated HOME and no --project-ref / + // SUPABASE_PROJECT_ID, the resolver fails before any API call. + test( + "get-root-key without a resolvable project ref exits non-zero with the not-linked message", + { timeout: E2E_TIMEOUT_MS }, + async () => { + const { exitCode, stdout, stderr } = await runSupabase(["encryption", "get-root-key"], { + entrypoint: "legacy", + env: { SUPABASE_ACCESS_TOKEN: TEST_TOKEN }, + }); + expect(exitCode).not.toBe(0); + expect(`${stdout}${stderr}`).toContain("Cannot find project ref"); + }, + ); + + // Validates the piped-stdin read path reaches the resolver in a real + // subprocess — the key is consumed from stdin, then ref resolution fails. + test( + "update-root-key with piped key but no resolvable ref exits non-zero", + { timeout: E2E_TIMEOUT_MS }, + async () => { + const { exitCode, stdout, stderr } = await runSupabase(["encryption", "update-root-key"], { + entrypoint: "legacy", + env: { SUPABASE_ACCESS_TOKEN: TEST_TOKEN }, + stdin: "newkey\n", + }); + expect(exitCode).not.toBe(0); + expect(`${stdout}${stderr}`).toContain("Cannot find project ref"); + }, + ); +}); diff --git a/apps/cli/src/legacy/commands/encryption/encryption.errors.ts b/apps/cli/src/legacy/commands/encryption/encryption.errors.ts new file mode 100644 index 0000000000..cf57170f97 --- /dev/null +++ b/apps/cli/src/legacy/commands/encryption/encryption.errors.ts @@ -0,0 +1,44 @@ +import { Data } from "effect"; + +import { mapLegacyHttpError } from "../../shared/legacy-http-errors.ts"; + +/** + * Transport-level failure talking to the Management API pgsodium endpoints. + * Mirrors Go's `errors.Errorf("failed to pgsodium config: %w", err)` + * (`apps/cli-go/internal/encryption/{get,update}`). + */ +class LegacyEncryptionNetworkError extends Data.TaggedError("LegacyEncryptionNetworkError")<{ + readonly message: string; +}> {} + +/** + * The pgsodium endpoint returned a status the Go CLI does not treat as success + * (it only accepts `JSON200`). Mirrors Go's + * `errors.Errorf("unexpected pgsodium config status %d: %s", code, body)`. + */ +class LegacyEncryptionUnexpectedStatusError extends Data.TaggedError( + "LegacyEncryptionUnexpectedStatusError", +)<{ + readonly status: number; + readonly body: string; + readonly message: string; +}> {} + +/** + * Build the network/status error mapper for an encryption subcommand. Go uses + * different verbs for the network vs status message of the same subcommand + * (get: "retrieve"/"get"; update: "update"/"update"), so the factory takes + * both and shares the dispatch + body-truncation policy from `mapLegacyHttpError`. + */ +export function mapLegacyEncryptionHttpError(verbs: { + readonly networkVerb: string; // "retrieve" | "update" + readonly statusVerb: string; // "get" | "update" +}) { + return mapLegacyHttpError({ + networkError: LegacyEncryptionNetworkError, + statusError: LegacyEncryptionUnexpectedStatusError, + networkMessage: (cause) => `failed to ${verbs.networkVerb} pgsodium config: ${cause}`, + statusMessage: (status, body) => + `unexpected ${verbs.statusVerb} pgsodium config status ${status}: ${body}`, + }); +} diff --git a/apps/cli/src/legacy/commands/encryption/get-root-key/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/encryption/get-root-key/SIDE_EFFECTS.md deleted file mode 100644 index 7b0300ab8c..0000000000 --- a/apps/cli/src/legacy/commands/encryption/get-root-key/SIDE_EFFECTS.md +++ /dev/null @@ -1,57 +0,0 @@ -# `supabase encryption get-root-key` - -## Files Read - -| Path | Format | When | -| -------------------------- | ------------------------- | ---------------------------------------------------------- | -| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | - -## Files Written - -| Path | Format | When | -| ---- | ------ | ---- | -| — | — | — | - -## API Routes - -| Method | Path | Auth | Request body | Response (used fields) | -| ------ | ------------------------------------------ | ------------ | ------------ | ---------------------- | -| `GET` | `/v1/projects/{ref}/config/database/vault` | Bearer token | none | `{root_key}` | - -## Environment Variables - -| Variable | Purpose | Required? | -| ----------------------- | ---------------------------------------------------- | ------------------------------------------------------- | -| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring → `~/.supabase/access-token`) | -| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | - -## Exit Codes - -| Code | Condition | -| ---- | ----------------------------------------------------- | -| `0` | success — root key printed to stdout | -| `1` | authentication error — no valid token found | -| `1` | API error — non-2xx response from encryption endpoint | -| `1` | network / connection failure | - -## Output - -### `--output-format text` (Go CLI compatible) - -Prints root encryption key to stdout. - -### `--output-format json` - -Single JSON object emitted to stdout on success. - -### `--output-format stream-json` - -One `result` event on success. - -```ndjson -{"type":"result","data":{...}} -``` - -## Notes - -- Requires `--project-ref` or a linked project (`.supabase/config.json`). diff --git a/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.command.ts b/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.command.ts index 79ec33e51f..496c2202a6 100644 --- a/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.command.ts +++ b/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.command.ts @@ -1,5 +1,9 @@ import { Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; + +import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts"; +import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; +import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyEncryptionGetRootKey } from "./get-root-key.handler.ts"; const config = { @@ -12,7 +16,14 @@ const config = { export type LegacyEncryptionGetRootKeyFlags = CliCommand.Command.Config.Infer; export const legacyEncryptionGetRootKeyCommand = Command.make("get-root-key", config).pipe( - Command.withDescription("Get the root encryption key of a Supabase project."), + Command.withDescription("Get the root encryption key of a Supabase project"), Command.withShortDescription("Get root encryption key"), - Command.withHandler((flags) => legacyEncryptionGetRootKey(flags)), + Command.withHandler((flags) => + legacyEncryptionGetRootKey(flags).pipe( + // `--project-ref` is not telemetry-safe for encryption (no `markFlagTelemetrySafe`). + withLegacyCommandInstrumentation({ flags }), + withJsonErrorHandling, + ), + ), + Command.provide(legacyManagementApiRuntimeLayer(["encryption", "get-root-key"])), ); diff --git a/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.handler.ts b/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.handler.ts index 71d3832dc2..f8acf29b04 100644 --- a/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.handler.ts +++ b/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.handler.ts @@ -1,12 +1,44 @@ -import { Effect, Option } from "effect"; -import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; +import { Effect } from "effect"; + +import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts"; +import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts"; +import { Output } from "../../../../shared/output/output.service.ts"; +import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts"; +import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; +import { mapLegacyEncryptionHttpError } from "../encryption.errors.ts"; import type { LegacyEncryptionGetRootKeyFlags } from "./get-root-key.command.ts"; +const mapGetError = mapLegacyEncryptionHttpError({ networkVerb: "retrieve", statusVerb: "get" }); + export const legacyEncryptionGetRootKey = Effect.fn("legacy.encryption.get-root-key")(function* ( flags: LegacyEncryptionGetRootKeyFlags, ) { - const proxy = yield* LegacyGoProxy; - const args: string[] = ["encryption", "get-root-key"]; - if (Option.isSome(flags.projectRef)) args.push("--project-ref", flags.projectRef.value); - yield* proxy.exec(args); + const output = yield* Output; + const api = yield* LegacyPlatformApi; + const resolver = yield* LegacyProjectRefResolver; + const linkedProjectCache = yield* LegacyLinkedProjectCache; + const telemetryState = yield* LegacyTelemetryState; + + const ref = yield* resolver.resolve(flags.projectRef); + + // Mirror Go's PersistentPostRun: write the linked-project cache and persist + // the telemetry state file on success and failure. + yield* Effect.gen(function* () { + const fetching = + output.format === "text" ? yield* output.task("Fetching root key...") : undefined; + const { root_key } = yield* api.v1.getPgsodiumConfig({ ref }).pipe( + Effect.tapError(() => fetching?.fail() ?? Effect.void), + Effect.catch(mapGetError), + ); + yield* fetching?.clear() ?? Effect.void; + + if (output.format !== "text") { + // json / stream-json — emit a structured result. + yield* output.success("", { root_key }); + return; + } + + // text — Go prints the bare key + newline to stdout (`fmt.Println`). + yield* output.raw(root_key + "\n", "stdout"); + }).pipe(Effect.ensuring(linkedProjectCache.cache(ref)), Effect.ensuring(telemetryState.flush)); }); diff --git a/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.integration.test.ts b/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.integration.test.ts new file mode 100644 index 0000000000..5e6d17f393 --- /dev/null +++ b/apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.integration.test.ts @@ -0,0 +1,153 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Exit, Option } from "effect"; + +import { mockOutput } from "../../../../../tests/helpers/mocks.ts"; +import { + buildLegacyTestRuntime, + LEGACY_VALID_REF, + mockLegacyCliConfig, + mockLegacyLinkedProjectCacheTracked, + mockLegacyPlatformApi, + mockLegacyTelemetryStateTracked, + useLegacyTempWorkdir, +} from "../../../../../tests/helpers/legacy-mocks.ts"; +import { legacyEncryptionGetRootKey } from "./get-root-key.handler.ts"; + +const ROOT_KEY_RESPONSE = { root_key: "abc123rootkey" }; + +interface SetupOpts { + readonly format?: "text" | "json" | "stream-json"; + readonly status?: number; + readonly network?: "fail"; + readonly projectId?: Option.Option; +} + +const tempRoot = useLegacyTempWorkdir("supabase-encryption-get-int-"); + +function setup(opts: SetupOpts = {}) { + const out = mockOutput({ format: opts.format ?? "text" }); + const api = mockLegacyPlatformApi({ + response: { status: opts.status ?? 200, body: ROOT_KEY_RESPONSE }, + network: opts.network, + }); + const cliConfig = mockLegacyCliConfig({ + workdir: tempRoot.current, + projectId: opts.projectId ?? Option.some(LEGACY_VALID_REF), + }); + const telemetry = mockLegacyTelemetryStateTracked(); + const linkedProjectCache = mockLegacyLinkedProjectCacheTracked(); + const layer = buildLegacyTestRuntime({ + out, + api, + cliConfig, + telemetry: telemetry.layer, + linkedProjectCache: linkedProjectCache.layer, + }); + return { layer, out, api, telemetry, linkedProjectCache }; +} + +const baseFlags = { projectRef: Option.none() }; + +describe("legacy encryption get-root-key integration", () => { + it.live("prints the root key to stdout in text mode", () => { + const { layer, out } = setup(); + return Effect.gen(function* () { + yield* legacyEncryptionGetRootKey(baseFlags); + expect(out.stdoutText).toBe("abc123rootkey\n"); + expect(out.stderrText).toBe(""); + }).pipe(Effect.provide(layer)); + }); + + it.live("emits the root key as a structured result in json mode", () => { + const { layer, out } = setup({ format: "json" }); + return Effect.gen(function* () { + yield* legacyEncryptionGetRootKey(baseFlags); + const success = out.messages.find((m) => m.type === "success"); + expect(success?.message).toBe(""); + expect(success?.data).toEqual({ root_key: "abc123rootkey" }); + }).pipe(Effect.provide(layer)); + }); + + it.live("streams the root key as a result event in stream-json mode", () => { + const { layer, out } = setup({ format: "stream-json" }); + return Effect.gen(function* () { + yield* legacyEncryptionGetRootKey(baseFlags); + const success = out.messages.find((m) => m.type === "success"); + expect(success?.data).toEqual({ root_key: "abc123rootkey" }); + }).pipe(Effect.provide(layer)); + }); + + it.live("resolves the ref from the --project-ref flag", () => { + const flagRef = "zzzzzzzzzzzzzzzzzzzz"; + const { layer, api } = setup(); + return Effect.gen(function* () { + yield* legacyEncryptionGetRootKey({ projectRef: Option.some(flagRef) }); + expect(api.requests[0]?.url).toContain(`/v1/projects/${flagRef}/pgsodium`); + }).pipe(Effect.provide(layer)); + }); + + it.live("fails with a transport error when the network is down", () => { + const { layer } = setup({ network: "fail" }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyEncryptionGetRootKey(baseFlags)); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const json = JSON.stringify(exit.cause); + expect(json).toContain("LegacyEncryptionNetworkError"); + expect(json).toContain("failed to retrieve pgsodium config"); + } + }).pipe(Effect.provide(layer)); + }); + + it.live("fails with an unexpected-status error on a 503", () => { + const { layer } = setup({ status: 503 }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyEncryptionGetRootKey(baseFlags)); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const json = JSON.stringify(exit.cause); + expect(json).toContain("LegacyEncryptionUnexpectedStatusError"); + expect(json).toContain("unexpected get pgsodium config status 503"); + } + }).pipe(Effect.provide(layer)); + }); + + it.live("does not start a spinner in json mode on failure", () => { + const { layer, out } = setup({ format: "json", status: 503 }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyEncryptionGetRootKey(baseFlags)); + expect(Exit.isFailure(exit)).toBe(true); + expect(out.progressEvents).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }); + + it.live("fails when no project ref can be resolved", () => { + const { layer } = setup({ projectId: Option.none() }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyEncryptionGetRootKey(baseFlags)); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + expect(JSON.stringify(exit.cause)).toContain("LegacyProjectNotLinkedError"); + } + }).pipe(Effect.provide(layer)); + }); + + it.live("writes the linked-project cache and flushes telemetry on success", () => { + const { layer, telemetry, linkedProjectCache } = setup(); + return Effect.gen(function* () { + yield* legacyEncryptionGetRootKey(baseFlags); + expect(telemetry.flushed).toBe(true); + expect(linkedProjectCache.cached).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("writes the linked-project cache and flushes telemetry on failure", () => { + const { layer, telemetry, linkedProjectCache } = setup({ status: 503 }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyEncryptionGetRootKey(baseFlags)); + expect(Exit.isFailure(exit)).toBe(true); + expect(telemetry.flushed).toBe(true); + expect(linkedProjectCache.cached).toBe(true); + }).pipe(Effect.provide(layer)); + }); +}); diff --git a/apps/cli/src/legacy/commands/encryption/update-root-key/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/encryption/update-root-key/SIDE_EFFECTS.md deleted file mode 100644 index 59ff29e739..0000000000 --- a/apps/cli/src/legacy/commands/encryption/update-root-key/SIDE_EFFECTS.md +++ /dev/null @@ -1,57 +0,0 @@ -# `supabase encryption update-root-key` - -## Files Read - -| Path | Format | When | -| -------------------------- | ------------------------- | ---------------------------------------------------------- | -| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | - -## Files Written - -| Path | Format | When | -| ---- | ------ | ---- | -| — | — | — | - -## API Routes - -| Method | Path | Auth | Request body | Response (used fields) | -| ------- | ------------------------------------------ | ------------ | ------------ | ---------------------- | -| `PATCH` | `/v1/projects/{ref}/config/database/vault` | Bearer token | none | `{root_key}` | - -## Environment Variables - -| Variable | Purpose | Required? | -| ----------------------- | ---------------------------------------------------- | ------------------------------------------------------- | -| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring → `~/.supabase/access-token`) | -| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | - -## Exit Codes - -| Code | Condition | -| ---- | ----------------------------------------------------- | -| `0` | success — root key updated | -| `1` | authentication error — no valid token found | -| `1` | API error — non-2xx response from encryption endpoint | -| `1` | network / connection failure | - -## Output - -### `--output-format text` (Go CLI compatible) - -Prints updated root encryption key to stdout. - -### `--output-format json` - -Single JSON object emitted to stdout on success. - -### `--output-format stream-json` - -One `result` event on success. - -```ndjson -{"type":"result","data":{...}} -``` - -## Notes - -- Requires `--project-ref` or a linked project (`.supabase/config.json`). diff --git a/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.command.ts b/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.command.ts index 4c92e8499d..8a0896cea7 100644 --- a/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.command.ts +++ b/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.command.ts @@ -1,5 +1,13 @@ +import { BunServices } from "@effect/platform-bun"; +import { Layer } from "effect"; import { Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; + +import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts"; +import { stdinLayer } from "../../../../shared/runtime/stdin.layer.ts"; +import { ttyLayer } from "../../../../shared/runtime/tty.layer.ts"; +import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; +import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyEncryptionUpdateRootKey } from "./update-root-key.handler.ts"; const config = { @@ -11,8 +19,23 @@ const config = { export type LegacyEncryptionUpdateRootKeyFlags = CliCommand.Command.Config.Infer; +// `Stdin` is new production wiring for this command. Provide it explicitly +// (along with its `Tty` + `Stdio` deps) so the command's layer is self-contained +// and does not rely on sibling-layer leakage inside `Layer.mergeAll`. +const updateRuntime = Layer.mergeAll( + legacyManagementApiRuntimeLayer(["encryption", "update-root-key"]), + stdinLayer.pipe(Layer.provide(ttyLayer), Layer.provide(BunServices.layer)), +); + export const legacyEncryptionUpdateRootKeyCommand = Command.make("update-root-key", config).pipe( - Command.withDescription("Update root encryption key of a Supabase project."), + Command.withDescription("Update root encryption key of a Supabase project"), Command.withShortDescription("Update the root encryption key"), - Command.withHandler((flags) => legacyEncryptionUpdateRootKey(flags)), + Command.withHandler((flags) => + legacyEncryptionUpdateRootKey(flags).pipe( + // `--project-ref` is not telemetry-safe for encryption (no `markFlagTelemetrySafe`). + withLegacyCommandInstrumentation({ flags }), + withJsonErrorHandling, + ), + ), + Command.provide(updateRuntime), ); diff --git a/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.handler.ts b/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.handler.ts index 85a56afbe7..2119f0ea52 100644 --- a/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.handler.ts +++ b/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.handler.ts @@ -1,12 +1,67 @@ import { Effect, Option } from "effect"; -import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; + +import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts"; +import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts"; +import { Output } from "../../../../shared/output/output.service.ts"; +import { Stdin } from "../../../../shared/runtime/stdin.service.ts"; +import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts"; +import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; +import { mapLegacyEncryptionHttpError } from "../encryption.errors.ts"; import type { LegacyEncryptionUpdateRootKeyFlags } from "./update-root-key.command.ts"; +const mapUpdateError = mapLegacyEncryptionHttpError({ + networkVerb: "update", + statusVerb: "update", +}); + export const legacyEncryptionUpdateRootKey = Effect.fn("legacy.encryption.update-root-key")( function* (flags: LegacyEncryptionUpdateRootKeyFlags) { - const proxy = yield* LegacyGoProxy; - const args: string[] = ["encryption", "update-root-key"]; - if (Option.isSome(flags.projectRef)) args.push("--project-ref", flags.projectRef.value); - yield* proxy.exec(args); + const output = yield* Output; + const api = yield* LegacyPlatformApi; + const resolver = yield* LegacyProjectRefResolver; + const stdin = yield* Stdin; + const linkedProjectCache = yield* LegacyLinkedProjectCache; + const telemetryState = yield* LegacyTelemetryState; + + const ref = yield* resolver.resolve(flags.projectRef); + + // Faithful port of Go's `update.Run` + `credentials.PromptMasked(os.Stdin)`. + // Go unconditionally writes the prompt to stderr, reads the key (masked on a + // TTY, `io.Copy` of all stdin when piped), then prints a trailing newline to + // stdout (`defer fmt.Println()`) — even when stdin is piped. Both read paths + // trim, matching Go's `strings.TrimSpace(input)`. The stderr prompt + stdout + // newline are reproduced only in text mode; json / stream-json reserve stdout + // for the structured result. On a TTY the masked prompt uses clack framing, so + // the rendered prompt is not byte-identical to Go (see SIDE_EFFECTS.md). + let rootKey: string; + if (stdin.isTTY) { + rootKey = yield* output.promptPassword("Enter a new root key: "); + } else { + if (output.format === "text") yield* output.raw("Enter a new root key: ", "stderr"); + rootKey = Option.getOrElse(yield* stdin.readPipedText, () => ""); + if (output.format === "text") yield* output.raw("\n", "stdout"); + } + + // Mirror Go's PersistentPostRun: write the linked-project cache and persist + // the telemetry state file on success and failure. + yield* Effect.gen(function* () { + const updating = + output.format === "text" ? yield* output.task("Updating root key...") : undefined; + const response = yield* api.v1.updatePgsodiumConfig({ ref, root_key: rootKey }).pipe( + Effect.tapError(() => updating?.fail() ?? Effect.void), + Effect.catch(mapUpdateError), + ); + yield* updating?.clear() ?? Effect.void; + + if (output.format !== "text") { + // json / stream-json — emit a structured result. + yield* output.success("", { root_key: response.root_key }); + return; + } + + // text — Go prints a plain finished notice to stderr (`fmt.Fprintln`, + // `utils.Aqua` rendered as plain text per the legacy-port convention). + yield* output.raw("Finished supabase root-key update.\n", "stderr"); + }).pipe(Effect.ensuring(linkedProjectCache.cache(ref)), Effect.ensuring(telemetryState.flush)); }, ); diff --git a/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.integration.test.ts b/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.integration.test.ts new file mode 100644 index 0000000000..ab312812d7 --- /dev/null +++ b/apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.integration.test.ts @@ -0,0 +1,187 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Exit, Layer, Option } from "effect"; + +import { mockOutput, mockStdin } from "../../../../../tests/helpers/mocks.ts"; +import { + buildLegacyTestRuntime, + LEGACY_VALID_REF, + mockLegacyCliConfig, + mockLegacyLinkedProjectCacheTracked, + mockLegacyPlatformApi, + mockLegacyTelemetryStateTracked, + useLegacyTempWorkdir, +} from "../../../../../tests/helpers/legacy-mocks.ts"; +import { legacyEncryptionUpdateRootKey } from "./update-root-key.handler.ts"; + +const ROOT_KEY_RESPONSE = { root_key: "new-key" }; + +interface SetupOpts { + readonly format?: "text" | "json" | "stream-json"; + readonly status?: number; + readonly network?: "fail"; + readonly projectId?: Option.Option; + // stdin + readonly stdinIsTty?: boolean; + readonly pipedInput?: string; + readonly promptPasswordResponses?: ReadonlyArray; +} + +const tempRoot = useLegacyTempWorkdir("supabase-encryption-update-int-"); + +function setup(opts: SetupOpts = {}) { + const out = mockOutput({ + format: opts.format ?? "text", + promptPasswordResponses: opts.promptPasswordResponses, + }); + const api = mockLegacyPlatformApi({ + response: { status: opts.status ?? 200, body: ROOT_KEY_RESPONSE }, + network: opts.network, + }); + const cliConfig = mockLegacyCliConfig({ + workdir: tempRoot.current, + projectId: opts.projectId ?? Option.some(LEGACY_VALID_REF), + }); + const telemetry = mockLegacyTelemetryStateTracked(); + const linkedProjectCache = mockLegacyLinkedProjectCacheTracked(); + const layer = Layer.mergeAll( + buildLegacyTestRuntime({ + out, + api, + cliConfig, + telemetry: telemetry.layer, + linkedProjectCache: linkedProjectCache.layer, + }), + mockStdin(opts.stdinIsTty ?? false, opts.pipedInput), + ); + return { layer, out, api, telemetry, linkedProjectCache }; +} + +const baseFlags = { projectRef: Option.none() }; + +describe("legacy encryption update-root-key integration", () => { + it.live("reads a piped root key and PUTs it, printing the finished message to stderr", () => { + const { layer, out, api } = setup({ pipedInput: "new-key" }); + return Effect.gen(function* () { + yield* legacyEncryptionUpdateRootKey(baseFlags); + const put = api.requests.find((r) => r.method === "PUT"); + expect(put?.url).toContain(`/v1/projects/${LEGACY_VALID_REF}/pgsodium`); + expect(put?.body).toEqual({ root_key: "new-key" }); + // Go parity: prompt to stderr, trailing newline to stdout (defer Println), + // finished notice to stderr. + expect(out.stderrText).toContain("Enter a new root key: "); + expect(out.stderrText).toContain("Finished supabase root-key update."); + expect(out.stdoutText).toBe("\n"); + }).pipe(Effect.provide(layer)); + }); + + it.live("prompts for a masked root key when stdin is a TTY", () => { + const { layer, api } = setup({ + stdinIsTty: true, + promptPasswordResponses: ["tty-key"], + }); + return Effect.gen(function* () { + yield* legacyEncryptionUpdateRootKey(baseFlags); + const put = api.requests.find((r) => r.method === "PUT"); + expect(put?.body).toEqual({ root_key: "tty-key" }); + }).pipe(Effect.provide(layer)); + }); + + it.live("sends an empty root key when piped stdin is empty", () => { + const { layer, api } = setup(); + return Effect.gen(function* () { + yield* legacyEncryptionUpdateRootKey(baseFlags); + const put = api.requests.find((r) => r.method === "PUT"); + expect(put?.body).toEqual({ root_key: "" }); + }).pipe(Effect.provide(layer)); + }); + + it.live("emits the updated config as a structured result in json mode", () => { + const { layer, out } = setup({ format: "json", pipedInput: "new-key" }); + return Effect.gen(function* () { + yield* legacyEncryptionUpdateRootKey(baseFlags); + const success = out.messages.find((m) => m.type === "success"); + expect(success?.message).toBe(""); + expect(success?.data).toEqual({ root_key: "new-key" }); + // json mode reserves stdout for the structured result — no prompt newline. + expect(out.stdoutText).toBe(""); + expect(out.stderrText).toBe(""); + }).pipe(Effect.provide(layer)); + }); + + it.live("emits a result event in stream-json mode", () => { + const { layer, out } = setup({ format: "stream-json", pipedInput: "new-key" }); + return Effect.gen(function* () { + yield* legacyEncryptionUpdateRootKey(baseFlags); + const success = out.messages.find((m) => m.type === "success"); + expect(success?.data).toEqual({ root_key: "new-key" }); + }).pipe(Effect.provide(layer)); + }); + + it.live("fails with a transport error when the network is down", () => { + const { layer } = setup({ network: "fail", pipedInput: "new-key" }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyEncryptionUpdateRootKey(baseFlags)); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const json = JSON.stringify(exit.cause); + expect(json).toContain("LegacyEncryptionNetworkError"); + expect(json).toContain("failed to update pgsodium config"); + } + }).pipe(Effect.provide(layer)); + }); + + it.live("fails with an unexpected-status error on a 503", () => { + const { layer } = setup({ status: 503, pipedInput: "new-key" }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyEncryptionUpdateRootKey(baseFlags)); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const json = JSON.stringify(exit.cause); + expect(json).toContain("LegacyEncryptionUnexpectedStatusError"); + expect(json).toContain("unexpected update pgsodium config status 503"); + } + }).pipe(Effect.provide(layer)); + }); + + it.live("does not start a spinner in json mode on failure", () => { + const { layer, out } = setup({ format: "json", status: 503, pipedInput: "new-key" }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyEncryptionUpdateRootKey(baseFlags)); + expect(Exit.isFailure(exit)).toBe(true); + expect(out.progressEvents).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }); + + it.live("fails when no project ref can be resolved", () => { + const { layer } = setup({ projectId: Option.none(), pipedInput: "new-key" }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyEncryptionUpdateRootKey(baseFlags)); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + expect(JSON.stringify(exit.cause)).toContain("LegacyProjectNotLinkedError"); + } + }).pipe(Effect.provide(layer)); + }); + + it.live("writes the linked-project cache and flushes telemetry on success", () => { + const { layer, telemetry, linkedProjectCache } = setup({ pipedInput: "new-key" }); + return Effect.gen(function* () { + yield* legacyEncryptionUpdateRootKey(baseFlags); + expect(telemetry.flushed).toBe(true); + expect(linkedProjectCache.cached).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("writes the linked-project cache and flushes telemetry on failure", () => { + const { layer, telemetry, linkedProjectCache } = setup({ + status: 503, + pipedInput: "new-key", + }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyEncryptionUpdateRootKey(baseFlags)); + expect(Exit.isFailure(exit)).toBe(true); + expect(telemetry.flushed).toBe(true); + expect(linkedProjectCache.cached).toBe(true); + }).pipe(Effect.provide(layer)); + }); +}); diff --git a/apps/cli/src/legacy/commands/postgres-config/delete/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/postgres-config/delete/SIDE_EFFECTS.md index c895254bf4..892ae2b29c 100644 --- a/apps/cli/src/legacy/commands/postgres-config/delete/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/postgres-config/delete/SIDE_EFFECTS.md @@ -2,48 +2,85 @@ ## Files Read -| Path | Format | When | -| -------------------------- | ------------------------- | ---------------------------------------------------------- | -| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | +| Path | Format | When | +| -------------------------------------- | ------------------------- | ------------------------------------------------------------- | +| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | +| `/supabase/.temp/project-ref` | plain text (project ref) | when `--project-ref` flag and `PROJECT_ID` env are both unset | ## Files Written -| Path | Format | When | -| ---- | ------ | ---- | -| — | — | — | +| Path | Format | When | +| ------------------------------------------------ | ------ | ----------------------------------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | always (after ref resolution), via `Effect.ensuring` - on success and failure | +| `~/.supabase/telemetry.json` | JSON | always, via `Effect.ensuring` - on success and failure | ## API Routes -| Method | Path | Auth | Request body | Response (used fields) | -| -------- | --------------------------------------------- | ------------ | ---------------------------------- | ---------------------- | -| `DELETE` | `/v1/projects/{ref}/config/database/postgres` | Bearer token | `{config_override_keys: string[]}` | `{config_overrides}` | +| Method | Path | Auth | Request body | Response (used fields) | +| ------ | --------------------------------------------- | ------------ | --------------------------------------------------------------- | ---------------------- | +| `GET` | `/v1/projects/{ref}/config/database/postgres` | Bearer token | none | full JSON object | +| `PUT` | `/v1/projects/{ref}/config/database/postgres` | Bearer token | current config minus deleted keys (`restart_database` optional) | full JSON object | + +This command does not call a delete endpoint. It mirrors Go: fetch current config, remove the specified keys locally, then send the remaining object back via `PUT`. ## Environment Variables -| Variable | Purpose | Required? | -| ----------------------- | ---------------------------------------------------- | ------------------------------------------------------- | -| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring → `~/.supabase/access-token`) | -| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | +| Variable | Purpose | Required? | +| ----------------------- | ---------------------------------------------------- | --------------------------------------------------------- | +| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring -> `~/.supabase/access-token`) | +| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | +| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` -> prompt) | ## Exit Codes -| Code | Condition | -| ---- | ---------------------------------------------------------- | -| `0` | success — Postgres config overrides deleted | -| `1` | authentication error — no valid token found | -| `1` | API error — non-2xx response from Postgres config endpoint | -| `1` | invalid config key | -| `1` | network / connection failure | +| Code | Condition | +| ---- | ------------------------------------------------------------------------------------------------------------ | +| `0` | success - Postgres config updated with the deleted keys removed | +| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | +| `1` | initial GET non-2xx (`LegacyPostgresConfigGetUnexpectedStatusError`) | +| `1` | initial GET transport failure (`LegacyPostgresConfigGetNetworkError`) | +| `1` | PUT non-2xx (`LegacyPostgresConfigDeleteUnexpectedStatusError`) | +| `1` | PUT transport failure (`LegacyPostgresConfigDeleteNetworkError`) | +| `1` | request serialization failure (`LegacyPostgresConfigDeleteSerializeError`) | +| `1` | invalid JSON response (`LegacyPostgresConfigGetUnmarshalError` / `LegacyPostgresConfigDeleteUnmarshalError`) | + +## Telemetry Events Fired + +| Event | When | Notable properties / groups | +| ---------------------- | ------------------------------------------ | --------------------------------------------------------------------- | +| `cli_command_executed` | post-run, success or failure (via wrapper) | `exit_code`, `duration_ms`, `flags` (`--project-ref` -> ``) | ## Output -### `--output-format text` (Go CLI compatible) +Matches `get` on success: stderr headings plus the Glamour-rendered table for the remaining config. + +### `--output-format text` (default) - Go CLI compatible + +Renders the remaining config map as a Glamour ASCII table. + +### Go `--output pretty` + +Identical to text mode. + +### Go `--output json` + +Go-compatible indented JSON of the remaining config object. + +### Go `--output yaml` + +YAML representation of the remaining config object. + +### Go `--output toml` + +TOML representation of the remaining config object. + +### Go `--output env` -Prints remaining Postgres configuration overrides after deletion to stdout. +Flat `KEY="value"` lines for the remaining config object. ### `--output-format json` -Single JSON object emitted to stdout on success. +Single `success` event whose data is the remaining config object. ### `--output-format stream-json` @@ -55,6 +92,10 @@ One `result` event on success. ## Notes +- The Go `--output` flag wins over the TS `--output-format` flag when both are provided. - Flags: `--config` (repeatable, config keys to delete), `--no-restart`. - Requires `--project-ref` or a linked project (`.supabase/config.json`). -- Deletes specific config overrides, reverting them to their default values. +- Each config key is trimmed with `strings.TrimSpace` before deletion, matching Go. +- `--no-restart` injects `restart_database = false` into the final `PUT` body. +- `linked-project.json` is written after the project ref resolves, regardless of whether the fetch or update succeeds. +- `telemetry.json` is written on every invocation, including failures. diff --git a/apps/cli/src/legacy/commands/postgres-config/delete/delete.command.ts b/apps/cli/src/legacy/commands/postgres-config/delete/delete.command.ts index 1f5ac31369..b757be3ccb 100644 --- a/apps/cli/src/legacy/commands/postgres-config/delete/delete.command.ts +++ b/apps/cli/src/legacy/commands/postgres-config/delete/delete.command.ts @@ -1,5 +1,9 @@ import { Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; + +import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts"; +import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; +import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyPostgresConfigDelete } from "./delete.handler.ts"; const config = { @@ -21,5 +25,11 @@ export type LegacyPostgresConfigDeleteFlags = CliCommand.Command.Config.Infer legacyPostgresConfigDelete(flags)), + Command.withHandler((flags) => + legacyPostgresConfigDelete(flags).pipe( + withLegacyCommandInstrumentation({ flags }), + withJsonErrorHandling, + ), + ), + Command.provide(legacyManagementApiRuntimeLayer(["postgres-config", "delete"])), ); diff --git a/apps/cli/src/legacy/commands/postgres-config/delete/delete.handler.ts b/apps/cli/src/legacy/commands/postgres-config/delete/delete.handler.ts index 5006109082..2967166b74 100644 --- a/apps/cli/src/legacy/commands/postgres-config/delete/delete.handler.ts +++ b/apps/cli/src/legacy/commands/postgres-config/delete/delete.handler.ts @@ -1,16 +1,61 @@ -import { Effect, Option } from "effect"; -import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; +import { Effect } from "effect"; + +import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts"; +import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts"; +import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; +import { Output } from "../../../../shared/output/output.service.ts"; +import { + LegacyPostgresConfigDeleteNetworkError, + LegacyPostgresConfigDeleteSerializeError, + LegacyPostgresConfigDeleteUnexpectedStatusError, + LegacyPostgresConfigDeleteUnmarshalError, +} from "../postgres-config.errors.ts"; +import { + fetchCurrentPostgresConfig, + putPostgresConfig, + writePostgresConfigOutput, +} from "../postgres-config.shared.ts"; import type { LegacyPostgresConfigDeleteFlags } from "./delete.command.ts"; export const legacyPostgresConfigDelete = Effect.fn("legacy.postgres-config.delete")(function* ( flags: LegacyPostgresConfigDeleteFlags, ) { - const proxy = yield* LegacyGoProxy; - const args: string[] = ["postgres-config", "delete"]; - if (Option.isSome(flags.projectRef)) args.push("--project-ref", flags.projectRef.value); - for (const key of flags.config) { - args.push("--config", key); - } - if (flags.noRestart) args.push("--no-restart"); - yield* proxy.exec(args); + const output = yield* Output; + const resolver = yield* LegacyProjectRefResolver; + const linkedProjectCache = yield* LegacyLinkedProjectCache; + const telemetryState = yield* LegacyTelemetryState; + + yield* Effect.gen(function* () { + const ref = yield* resolver.resolve(flags.projectRef); + + yield* Effect.gen(function* () { + const deleting = + output.format === "text" ? yield* output.task("Deleting Postgres config...") : undefined; + const currentConfig = yield* fetchCurrentPostgresConfig(ref).pipe( + Effect.tapError(() => deleting?.fail() ?? Effect.void), + ); + + for (const key of flags.config) { + delete currentConfig[key.trim()]; + } + + if (flags.noRestart) { + currentConfig["restart_database"] = false; + } + + const updated = yield* putPostgresConfig(ref, currentConfig, { + serializeError: (args) => new LegacyPostgresConfigDeleteSerializeError(args), + networkError: (args) => new LegacyPostgresConfigDeleteNetworkError(args), + statusError: (args) => new LegacyPostgresConfigDeleteUnexpectedStatusError(args), + unmarshalError: (args) => new LegacyPostgresConfigDeleteUnmarshalError(args), + networkMessage: (description) => `failed to delete config overrides: ${description}`, + statusMessage: (status, body) => + `unexpected delete config overrides status ${status}: ${body}`, + unmarshalMessage: (description) => `failed to unmarshal delete response: ${description}`, + }).pipe(Effect.tapError(() => deleting?.fail() ?? Effect.void)); + + yield* deleting?.clear() ?? Effect.void; + yield* writePostgresConfigOutput(updated); + }).pipe(Effect.ensuring(linkedProjectCache.cache(ref))); + }).pipe(Effect.ensuring(telemetryState.flush)); }); diff --git a/apps/cli/src/legacy/commands/postgres-config/get/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/postgres-config/get/SIDE_EFFECTS.md index f1670931c9..94e0953515 100644 --- a/apps/cli/src/legacy/commands/postgres-config/get/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/postgres-config/get/SIDE_EFFECTS.md @@ -2,47 +2,79 @@ ## Files Read -| Path | Format | When | -| -------------------------- | ------------------------- | ---------------------------------------------------------- | -| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | +| Path | Format | When | +| -------------------------------------- | ------------------------- | ------------------------------------------------------------- | +| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | +| `/supabase/.temp/project-ref` | plain text (project ref) | when `--project-ref` flag and `PROJECT_ID` env are both unset | ## Files Written -| Path | Format | When | -| ---- | ------ | ---- | -| — | — | — | +| Path | Format | When | +| ------------------------------------------------ | ------ | ----------------------------------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | always (after ref resolution), via `Effect.ensuring` - on success and failure | +| `~/.supabase/telemetry.json` | JSON | always, via `Effect.ensuring` - on success and failure | ## API Routes | Method | Path | Auth | Request body | Response (used fields) | | ------ | --------------------------------------------- | ------------ | ------------ | ---------------------- | -| `GET` | `/v1/projects/{ref}/config/database/postgres` | Bearer token | none | `{config_overrides}` | +| `GET` | `/v1/projects/{ref}/config/database/postgres` | Bearer token | none | full JSON object | ## Environment Variables -| Variable | Purpose | Required? | -| ----------------------- | ---------------------------------------------------- | ------------------------------------------------------- | -| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring → `~/.supabase/access-token`) | -| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | +| Variable | Purpose | Required? | +| ----------------------- | ---------------------------------------------------- | --------------------------------------------------------- | +| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring -> `~/.supabase/access-token`) | +| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | +| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` -> prompt) | ## Exit Codes -| Code | Condition | -| ---- | ---------------------------------------------------------- | -| `0` | success — Postgres config overrides printed to stdout | -| `1` | authentication error — no valid token found | -| `1` | API error — non-2xx response from Postgres config endpoint | -| `1` | network / connection failure | +| Code | Condition | +| ---- | --------------------------------------------------------------------------------------- | +| `0` | success - Postgres config printed | +| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | +| `1` | API non-2xx (`LegacyPostgresConfigGetUnexpectedStatusError`) | +| `1` | transport failure (`LegacyPostgresConfigGetNetworkError`) | +| `1` | invalid JSON response (`LegacyPostgresConfigGetUnmarshalError`) | + +## Telemetry Events Fired + +| Event | When | Notable properties / groups | +| ---------------------- | ------------------------------------------ | --------------------------------------------------------------------- | +| `cli_command_executed` | post-run, success or failure (via wrapper) | `exit_code`, `duration_ms`, `flags` (`--project-ref` -> ``) | ## Output -### `--output-format text` (Go CLI compatible) +Text mode mirrors Go's pretty output path: a `- Custom Postgres Config -` heading is written to stderr, the config table is rendered to stdout, then `- End of Custom Postgres Config -` is written to stderr. + +### `--output-format text` (default) - Go CLI compatible + +Renders the config map as a Glamour ASCII table with `Parameter` / `Value` columns. + +### Go `--output pretty` + +Identical to text mode. + +### Go `--output json` + +Go-compatible indented JSON with alphabetical key order and a trailing newline. + +### Go `--output yaml` + +YAML representation of the config map. + +### Go `--output toml` + +TOML representation of the config map. Numeric values arrive through `json.Unmarshal` as Go `float64`, so integral numbers render with `.0` (for example `max_connections = 100.0`). + +### Go `--output env` -Prints current Postgres configuration overrides to stdout. +Flat `KEY="value"` lines for each config entry. ### `--output-format json` -Single JSON object emitted to stdout on success. +Single `success` event whose data is the full config object. ### `--output-format stream-json` @@ -54,4 +86,7 @@ One `result` event on success. ## Notes +- The Go `--output` flag wins over the TS `--output-format` flag when both are provided. - Requires `--project-ref` or a linked project (`.supabase/config.json`). +- `linked-project.json` is written after the project ref resolves, regardless of whether the fetch succeeds. +- `telemetry.json` is written on every invocation, including failures. diff --git a/apps/cli/src/legacy/commands/postgres-config/get/get.command.ts b/apps/cli/src/legacy/commands/postgres-config/get/get.command.ts index b0a6aa393b..845f0641bb 100644 --- a/apps/cli/src/legacy/commands/postgres-config/get/get.command.ts +++ b/apps/cli/src/legacy/commands/postgres-config/get/get.command.ts @@ -1,5 +1,9 @@ import { Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; + +import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts"; +import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; +import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyPostgresConfigGet } from "./get.handler.ts"; const config = { @@ -14,5 +18,11 @@ export type LegacyPostgresConfigGetFlags = CliCommand.Command.Config.Infer legacyPostgresConfigGet(flags)), + Command.withHandler((flags) => + legacyPostgresConfigGet(flags).pipe( + withLegacyCommandInstrumentation({ flags }), + withJsonErrorHandling, + ), + ), + Command.provide(legacyManagementApiRuntimeLayer(["postgres-config", "get"])), ); diff --git a/apps/cli/src/legacy/commands/postgres-config/get/get.handler.ts b/apps/cli/src/legacy/commands/postgres-config/get/get.handler.ts index 0ae424c478..43b9b3e719 100644 --- a/apps/cli/src/legacy/commands/postgres-config/get/get.handler.ts +++ b/apps/cli/src/legacy/commands/postgres-config/get/get.handler.ts @@ -1,12 +1,34 @@ -import { Effect, Option } from "effect"; -import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; +import { Effect } from "effect"; + +import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts"; +import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts"; +import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; +import { Output } from "../../../../shared/output/output.service.ts"; +import { + fetchCurrentPostgresConfig, + writePostgresConfigOutput, +} from "../postgres-config.shared.ts"; import type { LegacyPostgresConfigGetFlags } from "./get.command.ts"; export const legacyPostgresConfigGet = Effect.fn("legacy.postgres-config.get")(function* ( flags: LegacyPostgresConfigGetFlags, ) { - const proxy = yield* LegacyGoProxy; - const args: string[] = ["postgres-config", "get"]; - if (Option.isSome(flags.projectRef)) args.push("--project-ref", flags.projectRef.value); - yield* proxy.exec(args); + const output = yield* Output; + const resolver = yield* LegacyProjectRefResolver; + const linkedProjectCache = yield* LegacyLinkedProjectCache; + const telemetryState = yield* LegacyTelemetryState; + + yield* Effect.gen(function* () { + const ref = yield* resolver.resolve(flags.projectRef); + + yield* Effect.gen(function* () { + const fetching = + output.format === "text" ? yield* output.task("Fetching Postgres config...") : undefined; + const config = yield* fetchCurrentPostgresConfig(ref).pipe( + Effect.tapError(() => fetching?.fail() ?? Effect.void), + ); + yield* fetching?.clear() ?? Effect.void; + yield* writePostgresConfigOutput(config); + }).pipe(Effect.ensuring(linkedProjectCache.cache(ref))); + }).pipe(Effect.ensuring(telemetryState.flush)); }); diff --git a/apps/cli/src/legacy/commands/postgres-config/postgres-config.errors.ts b/apps/cli/src/legacy/commands/postgres-config/postgres-config.errors.ts new file mode 100644 index 0000000000..a15a8c4c5f --- /dev/null +++ b/apps/cli/src/legacy/commands/postgres-config/postgres-config.errors.ts @@ -0,0 +1,87 @@ +import { Data } from "effect"; + +export class LegacyPostgresConfigGetNetworkError extends Data.TaggedError( + "LegacyPostgresConfigGetNetworkError", +)<{ + readonly message: string; +}> {} + +export class LegacyPostgresConfigGetUnexpectedStatusError extends Data.TaggedError( + "LegacyPostgresConfigGetUnexpectedStatusError", +)<{ + readonly status: number; + readonly body: string; + readonly message: string; +}> {} + +export class LegacyPostgresConfigGetUnmarshalError extends Data.TaggedError( + "LegacyPostgresConfigGetUnmarshalError", +)<{ + readonly message: string; +}> {} + +export class LegacyPostgresConfigUpdateNetworkError extends Data.TaggedError( + "LegacyPostgresConfigUpdateNetworkError", +)<{ + readonly message: string; +}> {} + +export class LegacyPostgresConfigUpdateUnexpectedStatusError extends Data.TaggedError( + "LegacyPostgresConfigUpdateUnexpectedStatusError", +)<{ + readonly status: number; + readonly body: string; + readonly message: string; +}> {} + +export class LegacyPostgresConfigUpdateUnmarshalError extends Data.TaggedError( + "LegacyPostgresConfigUpdateUnmarshalError", +)<{ + readonly message: string; +}> {} + +export class LegacyPostgresConfigUpdateSerializeError extends Data.TaggedError( + "LegacyPostgresConfigUpdateSerializeError", +)<{ + readonly message: string; +}> {} + +export class LegacyPostgresConfigDeleteNetworkError extends Data.TaggedError( + "LegacyPostgresConfigDeleteNetworkError", +)<{ + readonly message: string; +}> {} + +export class LegacyPostgresConfigDeleteUnexpectedStatusError extends Data.TaggedError( + "LegacyPostgresConfigDeleteUnexpectedStatusError", +)<{ + readonly status: number; + readonly body: string; + readonly message: string; +}> {} + +export class LegacyPostgresConfigDeleteUnmarshalError extends Data.TaggedError( + "LegacyPostgresConfigDeleteUnmarshalError", +)<{ + readonly message: string; +}> {} + +export class LegacyPostgresConfigDeleteSerializeError extends Data.TaggedError( + "LegacyPostgresConfigDeleteSerializeError", +)<{ + readonly message: string; +}> {} + +export class LegacyPostgresConfigInvalidConfigValueError extends Data.TaggedError( + "LegacyPostgresConfigInvalidConfigValueError", +)<{ + readonly input: string; + readonly message: string; +}> { + constructor(args: { readonly input: string }) { + super({ + input: args.input, + message: `expected config value in key:value format, received: '${args.input}'`, + }); + } +} diff --git a/apps/cli/src/legacy/commands/postgres-config/postgres-config.integration.test.ts b/apps/cli/src/legacy/commands/postgres-config/postgres-config.integration.test.ts new file mode 100644 index 0000000000..60536380f6 --- /dev/null +++ b/apps/cli/src/legacy/commands/postgres-config/postgres-config.integration.test.ts @@ -0,0 +1,639 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Exit, Option } from "effect"; + +import { mockOutput } from "../../../../tests/helpers/mocks.ts"; +import { + LEGACY_VALID_REF, + buildLegacyTestRuntime, + legacyJsonResponse, + legacyTransportFailure, + mockLegacyCliConfig, + mockLegacyLinkedProjectCacheTracked, + mockLegacyPlatformApi, + mockLegacyTelemetryStateTracked, + useLegacyTempWorkdir, +} from "../../../../tests/helpers/legacy-mocks.ts"; +import { legacyPostgresConfigDelete } from "./delete/delete.handler.ts"; +import { legacyPostgresConfigGet } from "./get/get.handler.ts"; +import { legacyPostgresConfigUpdate } from "./update/update.handler.ts"; + +type LegacyOutput = "env" | "pretty" | "json" | "toml" | "yaml"; + +const tempRoot = useLegacyTempWorkdir("supabase-postgres-config-int-"); + +function runtimeWith(opts: { + readonly out: ReturnType; + readonly api: ReturnType; + readonly telemetry?: ReturnType["layer"]; + readonly linkedProjectCache?: ReturnType["layer"]; + readonly legacyOutput?: LegacyOutput; +}) { + return buildLegacyTestRuntime({ + out: opts.out, + api: opts.api, + cliConfig: mockLegacyCliConfig({ workdir: tempRoot.current }), + telemetry: opts.telemetry, + linkedProjectCache: opts.linkedProjectCache, + goOutput: opts.legacyOutput === undefined ? Option.none() : Option.some(opts.legacyOutput), + }); +} + +describe("legacy postgres-config get", () => { + it.live("prints the Glamour table with stderr headings in text mode", () => { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + response: { status: 200, body: { max_connections: 100 } }, + }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + yield* legacyPostgresConfigGet({ projectRef: Option.none() }); + expect(out.progressEvents[0]?.message).toBe("Fetching Postgres config..."); + expect(out.stderrText).toBe( + "- Custom Postgres Config -\n- End of Custom Postgres Config -\n", + ); + expect(out.stdoutText).toContain("Parameter"); + expect(out.stdoutText).toContain("max_connections"); + expect(out.stdoutText).toContain("100"); + expect(api.requests[0]?.method).toBe("GET"); + expect(api.requests[0]?.url).toContain( + `/v1/projects/${LEGACY_VALID_REF}/config/database/postgres`, + ); + }).pipe(Effect.provide(layer)); + }); + + it.live("emits TOML bytes for --output toml", () => { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + response: { status: 200, body: { max_connections: 100 } }, + }); + const layer = runtimeWith({ out, api, legacyOutput: "toml" }); + + return Effect.gen(function* () { + yield* legacyPostgresConfigGet({ projectRef: Option.none() }); + expect(out.stdoutText).toBe("max_connections = 100.0\n"); + expect(out.stderrText).toBe(""); + }).pipe(Effect.provide(layer)); + }); + + it.live("keeps multi-key pretty and TOML output deterministic", () => { + const responseBody = { + wal_keep_size: "1GB", + max_connections: 100, + shared_buffers: "128MB", + }; + + return Effect.gen(function* () { + const prettyOut = mockOutput({ format: "text" }); + const prettyApi = mockLegacyPlatformApi({ + response: { status: 200, body: responseBody }, + }); + yield* legacyPostgresConfigGet({ projectRef: Option.none() }).pipe( + Effect.provide(runtimeWith({ out: prettyOut, api: prettyApi })), + ); + + expect(prettyOut.stdoutText.indexOf("max_connections")).toBeLessThan( + prettyOut.stdoutText.indexOf("shared_buffers"), + ); + expect(prettyOut.stdoutText.indexOf("shared_buffers")).toBeLessThan( + prettyOut.stdoutText.indexOf("wal_keep_size"), + ); + + const tomlOut = mockOutput({ format: "text" }); + const tomlApi = mockLegacyPlatformApi({ + response: { status: 200, body: responseBody }, + }); + yield* legacyPostgresConfigGet({ projectRef: Option.none() }).pipe( + Effect.provide(runtimeWith({ out: tomlOut, api: tomlApi, legacyOutput: "toml" })), + ); + + expect(tomlOut.stdoutText).toBe( + 'max_connections = 100.0\nshared_buffers = "128MB"\nwal_keep_size = "1GB"\n', + ); + }); + }); + + it.live("emits env output for --output env", () => { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + response: { status: 200, body: { track_commit_timestamp: true } }, + }); + const layer = runtimeWith({ out, api, legacyOutput: "env" }); + + return Effect.gen(function* () { + yield* legacyPostgresConfigGet({ projectRef: Option.none() }); + expect(out.stdoutText).toBe('TRACK_COMMIT_TIMESTAMP="true"\n'); + }).pipe(Effect.provide(layer)); + }); + + it.live("emits legacy JSON and YAML bytes", () => + Effect.gen(function* () { + for (const [legacyOutput, expected] of [ + ["json", '"max_connections": 100'], + ["yaml", "max_connections: 100"], + ] as const) { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + response: { status: 200, body: { max_connections: 100 } }, + }); + const layer = runtimeWith({ out, api, legacyOutput }); + yield* legacyPostgresConfigGet({ projectRef: Option.none() }).pipe(Effect.provide(layer)); + expect(out.stdoutText).toContain(expected); + expect(out.stderrText).toBe(""); + } + }), + ); + + it.live("emits a JSON success payload for --output-format json", () => { + const out = mockOutput({ format: "json" }); + const api = mockLegacyPlatformApi({ + response: { status: 200, body: { max_connections: 100 } }, + }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + yield* legacyPostgresConfigGet({ projectRef: Option.none() }); + const success = out.messages.find((message) => message.type === "success"); + expect(success?.data).toEqual({ max_connections: 100 }); + }).pipe(Effect.provide(layer)); + }); + + it.live("lets the Go --output flag win over --output-format json", () => { + const out = mockOutput({ format: "json" }); + const api = mockLegacyPlatformApi({ + response: { status: 200, body: { max_connections: 100 } }, + }); + const layer = runtimeWith({ out, api, legacyOutput: "toml" }); + + return Effect.gen(function* () { + yield* legacyPostgresConfigGet({ projectRef: Option.none() }); + expect(out.stdoutText).toBe("max_connections = 100.0\n"); + expect(out.messages.some((message) => message.type === "success")).toBe(false); + }).pipe(Effect.provide(layer)); + }); + + it.live("treats --output pretty as the human-readable table", () => { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + response: { status: 200, body: { max_connections: 100 } }, + }); + const layer = runtimeWith({ out, api, legacyOutput: "pretty" }); + + return Effect.gen(function* () { + yield* legacyPostgresConfigGet({ projectRef: Option.none() }); + expect(out.stderrText).toBe( + "- Custom Postgres Config -\n- End of Custom Postgres Config -\n", + ); + expect(out.stdoutText).toContain("Parameter"); + expect(out.stdoutText).toContain("max_connections"); + }).pipe(Effect.provide(layer)); + }); + + it.live("maps HTTP 503 to the get unexpected-status error", () => { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ response: { status: 503, body: {} } }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyPostgresConfigGet({ projectRef: Option.none() })); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const errorJson = JSON.stringify(exit.cause); + expect(errorJson).toContain("LegacyPostgresConfigGetUnexpectedStatusError"); + expect(errorJson).toContain("unexpected config overrides status 503"); + } + }).pipe(Effect.provide(layer)); + }); + + it.live("maps transport failures to the get network error", () => { + const out = mockOutput({ format: "json" }); + const api = mockLegacyPlatformApi({ network: "fail" }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyPostgresConfigGet({ projectRef: Option.none() })); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const errorJson = JSON.stringify(exit.cause); + expect(errorJson).toContain("LegacyPostgresConfigGetNetworkError"); + expect(errorJson).toContain("failed to retrieve Postgres config overrides"); + } + }).pipe(Effect.provide(layer)); + }); +}); + +describe("legacy postgres-config update", () => { + it.live( + "merges current overrides, coerces values, and PUTs arbitrary keys through raw HTTP", + () => { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + byMethod: { + GET: { status: 200, body: { wal_keep_size: "1GB" } }, + PUT: { + status: 200, + body: { + wal_keep_size: "1GB", + max_connections: 100, + track_commit_timestamp: true, + statement_timeout: "600", + custom_key: "alpha", + restart_database: false, + }, + }, + }, + }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + yield* legacyPostgresConfigUpdate({ + projectRef: Option.none(), + config: [ + "max_connections=100", + "track_commit_timestamp=true", + "statement_timeout=600", + "custom_key=alpha", + ], + replaceExistingOverrides: false, + noRestart: true, + }); + expect(out.progressEvents[0]?.message).toBe("Updating Postgres config..."); + expect(api.requests).toHaveLength(2); + expect(api.requests[1]?.method).toBe("PUT"); + expect(api.requests[1]?.body).toEqual({ + wal_keep_size: "1GB", + max_connections: 100, + track_commit_timestamp: true, + statement_timeout: "600", + custom_key: "alpha", + restart_database: false, + }); + expect(out.stderrText).toBe( + "- Custom Postgres Config -\n- End of Custom Postgres Config -\n", + ); + }).pipe(Effect.provide(layer)); + }, + ); + + it.live("skips the initial GET in replace mode", () => { + const out = mockOutput({ format: "json" }); + const api = mockLegacyPlatformApi({ + byMethod: { + PUT: { status: 200, body: { max_connections: 100 } }, + }, + }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + yield* legacyPostgresConfigUpdate({ + projectRef: Option.none(), + config: ["max_connections=100"], + replaceExistingOverrides: true, + noRestart: false, + }); + expect(api.requests).toHaveLength(1); + expect(api.requests[0]?.method).toBe("PUT"); + expect(api.requests[0]?.body).toEqual({ max_connections: 100 }); + const success = out.messages.find((message) => message.type === "success"); + expect(success?.data).toEqual({ max_connections: 100 }); + }).pipe(Effect.provide(layer)); + }); + + it.live("emits legacy output modes in replace mode", () => + Effect.gen(function* () { + const cases = [ + { + legacyOutput: "json" as const, + expected: '"max_connections": 100', + }, + { + legacyOutput: "yaml" as const, + expected: "max_connections: 100", + }, + { + legacyOutput: "toml" as const, + expected: "max_connections = 100.0\n", + }, + { + legacyOutput: "env" as const, + expected: "MAX_CONNECTIONS=100", + }, + ]; + + for (const testCase of cases) { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + byMethod: { + PUT: { status: 200, body: { max_connections: 100 } }, + }, + }); + const layer = runtimeWith({ out, api, legacyOutput: testCase.legacyOutput }); + yield* legacyPostgresConfigUpdate({ + projectRef: Option.none(), + config: ["max_connections=100"], + replaceExistingOverrides: true, + noRestart: false, + }).pipe(Effect.provide(layer)); + expect(out.stdoutText).toContain(testCase.expected); + expect(out.stderrText).toBe(""); + } + }), + ); + + it.live("fails before ref resolution on malformed config input", () => { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi(); + const telemetry = mockLegacyTelemetryStateTracked(); + const cache = mockLegacyLinkedProjectCacheTracked(); + const layer = runtimeWith({ + out, + api, + telemetry: telemetry.layer, + linkedProjectCache: cache.layer, + }); + + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyPostgresConfigUpdate({ + projectRef: Option.none(), + config: ["broken=value=again"], + replaceExistingOverrides: false, + noRestart: false, + }), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const errorJson = JSON.stringify(exit.cause); + expect(errorJson).toContain("LegacyPostgresConfigInvalidConfigValueError"); + expect(errorJson).toContain("expected config value in key:value format"); + } + expect(telemetry.flushed).toBe(true); + expect(cache.cached).toBe(false); + expect(api.requests).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }); + + it.live("maps PUT failures to the update unexpected-status error", () => { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + byMethod: { + GET: { status: 200, body: { wal_keep_size: "1GB" } }, + PUT: { status: 503, body: {} }, + }, + }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyPostgresConfigUpdate({ + projectRef: Option.none(), + config: ["max_connections=100"], + replaceExistingOverrides: false, + noRestart: false, + }), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const errorJson = JSON.stringify(exit.cause); + expect(errorJson).toContain("LegacyPostgresConfigUpdateUnexpectedStatusError"); + expect(errorJson).toContain("unexpected update config overrides status 503"); + } + }).pipe(Effect.provide(layer)); + }); + + it.live("maps initial GET transport failures to the shared get network error", () => { + const out = mockOutput({ format: "json" }); + const api = mockLegacyPlatformApi({ network: "fail" }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyPostgresConfigUpdate({ + projectRef: Option.none(), + config: [], + replaceExistingOverrides: false, + noRestart: false, + }), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const errorJson = JSON.stringify(exit.cause); + expect(errorJson).toContain("LegacyPostgresConfigGetNetworkError"); + expect(errorJson).toContain("failed to retrieve Postgres config overrides"); + } + }).pipe(Effect.provide(layer)); + }); + + it.live("maps PUT transport failures to the update network error", () => { + const out = mockOutput({ format: "json" }); + const api = mockLegacyPlatformApi({ + byMethod: { + GET: { status: 200, body: { max_connections: 100 } }, + }, + handler: (request) => + request.method === "PUT" + ? Effect.fail(legacyTransportFailure(request)) + : Effect.sync(() => legacyJsonResponse(request, 200, { max_connections: 100 })), + }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyPostgresConfigUpdate({ + projectRef: Option.none(), + config: [], + replaceExistingOverrides: false, + noRestart: false, + }), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const errorJson = JSON.stringify(exit.cause); + expect(errorJson).toContain("LegacyPostgresConfigUpdateNetworkError"); + expect(errorJson).toContain("failed to update config overrides"); + } + }).pipe(Effect.provide(layer)); + }); +}); + +describe("legacy postgres-config delete", () => { + it.live("uses GET plus PUT and trims keys before deleting", () => { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + byMethod: { + GET: { status: 200, body: { max_connections: 100, shared_buffers: "1GB" } }, + PUT: { status: 200, body: { shared_buffers: "1GB", restart_database: false } }, + }, + }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + yield* legacyPostgresConfigDelete({ + projectRef: Option.none(), + config: [" max_connections "], + noRestart: true, + }); + expect(out.progressEvents[0]?.message).toBe("Deleting Postgres config..."); + expect(api.requests).toHaveLength(2); + expect(api.requests[0]?.method).toBe("GET"); + expect(api.requests[1]?.method).toBe("PUT"); + expect(api.requests[1]?.body).toEqual({ + shared_buffers: "1GB", + restart_database: false, + }); + expect(out.stdoutText).toContain("shared_buffers"); + expect(out.stderrText).toBe( + "- Custom Postgres Config -\n- End of Custom Postgres Config -\n", + ); + }).pipe(Effect.provide(layer)); + }); + + it.live("emits a stream-json success payload", () => { + const out = mockOutput({ format: "stream-json" }); + const api = mockLegacyPlatformApi({ + byMethod: { + GET: { status: 200, body: { max_connections: 100 } }, + PUT: { status: 200, body: {} }, + }, + }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + yield* legacyPostgresConfigDelete({ + projectRef: Option.none(), + config: ["max_connections"], + noRestart: false, + }); + const success = out.messages.find((message) => message.type === "success"); + expect(success?.data).toEqual({}); + }).pipe(Effect.provide(layer)); + }); + + it.live("emits legacy output modes after the GET plus PUT flow", () => + Effect.gen(function* () { + const cases = [ + { + legacyOutput: "json" as const, + expected: '"shared_buffers": "1GB"', + }, + { + legacyOutput: "yaml" as const, + expected: "shared_buffers: 1GB", + }, + { + legacyOutput: "toml" as const, + expected: 'shared_buffers = "1GB"\n', + }, + { + legacyOutput: "env" as const, + expected: 'SHARED_BUFFERS="1GB"', + }, + ]; + + for (const testCase of cases) { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + byMethod: { + GET: { status: 200, body: { max_connections: 100, shared_buffers: "1GB" } }, + PUT: { status: 200, body: { shared_buffers: "1GB" } }, + }, + }); + const layer = runtimeWith({ out, api, legacyOutput: testCase.legacyOutput }); + yield* legacyPostgresConfigDelete({ + projectRef: Option.none(), + config: ["max_connections"], + noRestart: false, + }).pipe(Effect.provide(layer)); + expect(out.stdoutText).toContain(testCase.expected); + expect(out.stderrText).toBe(""); + } + }), + ); + + it.live("flushes telemetry and caches the ref when delete fails after resolution", () => { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + byMethod: { + GET: { status: 200, body: { max_connections: 100 } }, + PUT: { status: 503, body: {} }, + }, + }); + const telemetry = mockLegacyTelemetryStateTracked(); + const cache = mockLegacyLinkedProjectCacheTracked(); + const layer = runtimeWith({ + out, + api, + telemetry: telemetry.layer, + linkedProjectCache: cache.layer, + }); + + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyPostgresConfigDelete({ + projectRef: Option.none(), + config: ["max_connections"], + noRestart: false, + }), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const errorJson = JSON.stringify(exit.cause); + expect(errorJson).toContain("LegacyPostgresConfigDeleteUnexpectedStatusError"); + } + expect(telemetry.flushed).toBe(true); + expect(cache.cached).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("surfaces the shared get error when the initial fetch fails", () => { + const out = mockOutput({ format: "json" }); + const api = mockLegacyPlatformApi({ + handler: (request) => Effect.sync(() => legacyJsonResponse(request, 503, {})), + }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyPostgresConfigDelete({ + projectRef: Option.none(), + config: ["max_connections"], + noRestart: false, + }), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const errorJson = JSON.stringify(exit.cause); + expect(errorJson).toContain("LegacyPostgresConfigGetUnexpectedStatusError"); + } + expect(api.requests).toHaveLength(1); + expect(api.requests[0]?.method).toBe("GET"); + }).pipe(Effect.provide(layer)); + }); + + it.live("maps PUT transport failures to the delete network error", () => { + const out = mockOutput({ format: "json" }); + const api = mockLegacyPlatformApi({ + handler: (request) => + request.method === "GET" + ? Effect.sync(() => legacyJsonResponse(request, 200, { max_connections: 100 })) + : Effect.fail(legacyTransportFailure(request)), + }); + const layer = runtimeWith({ out, api }); + + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyPostgresConfigDelete({ + projectRef: Option.none(), + config: ["max_connections"], + noRestart: false, + }), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const errorJson = JSON.stringify(exit.cause); + expect(errorJson).toContain("LegacyPostgresConfigDeleteNetworkError"); + expect(errorJson).toContain("failed to delete config overrides"); + } + }).pipe(Effect.provide(layer)); + }); +}); diff --git a/apps/cli/src/legacy/commands/postgres-config/postgres-config.shared.ts b/apps/cli/src/legacy/commands/postgres-config/postgres-config.shared.ts new file mode 100644 index 0000000000..6913c79237 --- /dev/null +++ b/apps/cli/src/legacy/commands/postgres-config/postgres-config.shared.ts @@ -0,0 +1,276 @@ +import { Effect, Option, type Redacted } from "effect"; +import * as HttpClient from "effect/unstable/http/HttpClient"; +import * as HttpClientError from "effect/unstable/http/HttpClientError"; +import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"; + +import { LegacyCliConfig } from "../../config/legacy-cli-config.service.ts"; +import { LegacyOutputFlag } from "../../../shared/legacy/global-flags.ts"; +import { Output } from "../../../shared/output/output.service.ts"; +import { renderGlamourTable } from "../../output/legacy-glamour-table.ts"; +import { + encodeEnv, + encodeGoJson, + encodeGoStructJsonBody, + encodeYaml, +} from "../../shared/legacy-go-output.encoders.ts"; +import { sanitizeLegacyErrorBody } from "../../shared/legacy-http-errors.ts"; +import { resolveLegacyAccessToken } from "../../shared/legacy-resolve-token.ts"; +import { + LegacyPostgresConfigGetNetworkError, + LegacyPostgresConfigGetUnexpectedStatusError, + LegacyPostgresConfigGetUnmarshalError, +} from "./postgres-config.errors.ts"; + +export type LegacyPostgresConfigMap = Record; + +function sortConfigEntries(config: LegacyPostgresConfigMap): Array<[string, unknown]> { + return Object.entries(config).sort(([a], [b]) => a.localeCompare(b)); +} + +function formatPrettyValue(value: unknown): string { + if (typeof value === "string") return value; + if (typeof value === "number" || typeof value === "boolean") return String(value); + if (value === null) return ""; + return JSON.stringify(value); +} + +function renderPostgresConfigTable(config: LegacyPostgresConfigMap): string { + return renderGlamourTable( + ["Parameter", "Value"], + sortConfigEntries(config).map(([key, value]) => [key, formatPrettyValue(value)]), + ); +} + +function encodeTomlScalar(value: unknown): string { + if (typeof value === "string") return JSON.stringify(value); + if (typeof value === "boolean") return value ? "true" : "false"; + if (typeof value === "number") { + // Go decodes the API response with `json.Unmarshal` into `map[string]any`, + // so every JSON number becomes a `float64`. Go's TOML marshaller then prints + // integral floats with a `.0` suffix (e.g. `max_connections = 100.0`). The + // shared `encodeToml` (smol-toml) would emit `100` instead, so this command + // cannot use it without breaking byte-for-byte parity with the Go CLI. + return Number.isInteger(value) ? `${value}.0` : String(value); + } + if (value === null) return JSON.stringify(""); + return JSON.stringify(JSON.stringify(value)); +} + +// Hand-rolled to reproduce Go's `float64` TOML rendering (see `encodeTomlScalar`). +// Intentionally does not delegate to the shared `encodeToml`/smol-toml encoder. +function encodePostgresConfigToml(config: LegacyPostgresConfigMap): string { + const lines = sortConfigEntries(config).map( + ([key, value]) => `${key} = ${encodeTomlScalar(value)}`, + ); + return lines.length === 0 ? "" : lines.join("\n") + "\n"; +} + +export function parseConfigValue(value: string): string | number | boolean { + if (/^[+-]?\d+$/.test(value)) return Number.parseInt(value, 10); + const lower = value.toLowerCase(); + if (["1", "t", "true"].includes(lower)) return true; + if (["0", "f", "false"].includes(lower)) return false; + return value; +} + +export function normalizeTimeoutConfig(config: LegacyPostgresConfigMap): void { + for (const [key, value] of Object.entries(config)) { + if (key.endsWith("_timeout") && typeof value !== "string") { + config[key] = String(value); + } + } +} + +function mapTransportMessage( + cause: unknown, + message: (description: string) => string, + wrap: (args: { readonly message: string }) => E, +): E { + if (HttpClientError.isHttpClientError(cause)) { + const description = cause.reason.description ?? cause.reason._tag; + return wrap({ message: message(description) }); + } + return wrap({ message: message(String(cause)) }); +} + +function requestWithAuth( + request: HttpClientRequest.HttpClientRequest, + tokenOpt: Option.Option>, + userAgent: string, +) { + return request.pipe( + Option.isSome(tokenOpt) ? HttpClientRequest.bearerToken(tokenOpt.value) : (req) => req, + HttpClientRequest.setHeader("User-Agent", userAgent), + ); +} + +function parseJsonObject( + rawBody: string, + errorMessage: (description: string) => string, + wrap: (args: { readonly message: string }) => E, +): Effect.Effect { + return Effect.try({ + try: () => { + const parsed = JSON.parse(rawBody) as unknown; + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new Error("unexpected non-object JSON response"); + } + return parsed as LegacyPostgresConfigMap; + }, + catch: (cause) => wrap({ message: errorMessage(String(cause)) }), + }); +} + +export const fetchCurrentPostgresConfig = Effect.fn("legacy.postgres-config.fetch-current")( + function* (ref: string) { + const httpClient = yield* HttpClient.HttpClient; + const cliConfig = yield* LegacyCliConfig; + const tokenOpt = yield* resolveLegacyAccessToken; + + const request = requestWithAuth( + HttpClientRequest.get(`${cliConfig.apiUrl}/v1/projects/${ref}/config/database/postgres`), + tokenOpt, + cliConfig.userAgent, + ); + + const response = yield* httpClient.execute(request).pipe( + Effect.mapError((cause) => + mapTransportMessage( + cause, + (description) => `failed to retrieve Postgres config overrides: ${description}`, + (args) => new LegacyPostgresConfigGetNetworkError(args), + ), + ), + ); + + if (response.status !== 200) { + const rawBody = yield* response.text.pipe(Effect.orElseSucceed(() => "")); + const body = sanitizeLegacyErrorBody(rawBody); + return yield* Effect.fail( + new LegacyPostgresConfigGetUnexpectedStatusError({ + status: response.status, + body, + message: `unexpected config overrides status ${response.status}: ${body}`, + }), + ); + } + + const rawBody = yield* response.text; + return yield* parseJsonObject( + rawBody, + (description) => `failed to unmarshal response body: ${description}`, + (args) => new LegacyPostgresConfigGetUnmarshalError(args), + ); + }, +); + +/** + * Per-operation error wiring for {@link putPostgresConfig}. Both `update` and + * `delete` issue the same PUT, but tag failures with their own error types and + * Go-parity message verbs. Passing the constructors and message templates as + * arguments (mirroring `mapLegacyHttpError`) keeps each call site's error + * channel precise instead of widening it to the union of both operations. + */ +export interface PutPostgresConfigErrors { + readonly serializeError: (args: { readonly message: string }) => SerErr; + readonly networkError: (args: { readonly message: string }) => NetErr; + readonly statusError: (args: { + readonly status: number; + readonly body: string; + readonly message: string; + }) => StatErr; + readonly unmarshalError: (args: { readonly message: string }) => UnmErr; + readonly networkMessage: (description: string) => string; + readonly statusMessage: (status: number, body: string) => string; + readonly unmarshalMessage: (description: string) => string; +} + +export const putPostgresConfig = ( + ref: string, + config: LegacyPostgresConfigMap, + errors: PutPostgresConfigErrors, +) => + Effect.gen(function* () { + const httpClient = yield* HttpClient.HttpClient; + const cliConfig = yield* LegacyCliConfig; + const tokenOpt = yield* resolveLegacyAccessToken; + + // Use raw HTTP instead of the generated input schema: Go accepts arbitrary + // config keys from repeated `--config key=value`, while the typed client + // only models the currently known OpenAPI fields. + const encodedBody = yield* Effect.try({ + try: () => encodeGoStructJsonBody(config), + catch: (cause) => + errors.serializeError({ + message: `failed to serialize config overrides: ${String(cause)}`, + }), + }); + + const request = requestWithAuth( + HttpClientRequest.put(`${cliConfig.apiUrl}/v1/projects/${ref}/config/database/postgres`).pipe( + HttpClientRequest.bodyText(encodedBody, "application/json"), + ), + tokenOpt, + cliConfig.userAgent, + ); + + const response = yield* httpClient + .execute(request) + .pipe( + Effect.mapError((cause) => + mapTransportMessage(cause, errors.networkMessage, errors.networkError), + ), + ); + + if (response.status !== 200) { + const rawBody = yield* response.text.pipe(Effect.orElseSucceed(() => "")); + const body = sanitizeLegacyErrorBody(rawBody); + return yield* Effect.fail( + errors.statusError({ + status: response.status, + body, + message: errors.statusMessage(response.status, body), + }), + ); + } + + const rawBody = yield* response.text; + return yield* parseJsonObject(rawBody, errors.unmarshalMessage, errors.unmarshalError); + }).pipe(Effect.withSpan("legacy.postgres-config.put")); + +export const writePostgresConfigOutput = Effect.fn("legacy.postgres-config.write-output")( + function* (config: LegacyPostgresConfigMap) { + const output = yield* Output; + const legacyOutputFlag = yield* LegacyOutputFlag; + const legacyOutput = Option.getOrUndefined(legacyOutputFlag); + + // Go's `--output` flag takes priority over the TS `--output-format` flag. + // `pretty` (and an unset flag) fall through to the human-readable table / + // structured-success path below. + if (legacyOutput === "json") { + yield* output.raw(encodeGoJson(config)); + return; + } + if (legacyOutput === "yaml") { + yield* output.raw(encodeYaml(config)); + return; + } + if (legacyOutput === "toml") { + yield* output.raw(encodePostgresConfigToml(config)); + return; + } + if (legacyOutput === "env") { + yield* output.raw(encodeEnv(config) + "\n"); + return; + } + + if (output.format === "json" || output.format === "stream-json") { + yield* output.success("", config); + return; + } + + yield* output.raw("- Custom Postgres Config -\n", "stderr"); + yield* output.raw(renderPostgresConfigTable(config)); + yield* output.raw("- End of Custom Postgres Config -\n", "stderr"); + }, +); diff --git a/apps/cli/src/legacy/commands/postgres-config/update/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/postgres-config/update/SIDE_EFFECTS.md index 2b25a2629a..030a6a322d 100644 --- a/apps/cli/src/legacy/commands/postgres-config/update/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/postgres-config/update/SIDE_EFFECTS.md @@ -2,48 +2,86 @@ ## Files Read -| Path | Format | When | -| -------------------------- | ------------------------- | ---------------------------------------------------------- | -| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | +| Path | Format | When | +| -------------------------------------- | ------------------------- | ------------------------------------------------------------- | +| `~/.supabase/access-token` | plain text (token string) | when `SUPABASE_ACCESS_TOKEN` unset and keyring unavailable | +| `/supabase/.temp/project-ref` | plain text (project ref) | when `--project-ref` flag and `PROJECT_ID` env are both unset | ## Files Written -| Path | Format | When | -| ---- | ------ | ---- | -| — | — | — | +| Path | Format | When | +| ------------------------------------------------ | ------ | ----------------------------------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | always (after ref resolution), via `Effect.ensuring` - on success and failure | +| `~/.supabase/telemetry.json` | JSON | always, via `Effect.ensuring` - on success and failure | ## API Routes -| Method | Path | Auth | Request body | Response (used fields) | -| ------ | --------------------------------------------- | ------------ | -------------------------------------------- | ---------------------- | -| `PUT` | `/v1/projects/{ref}/config/database/postgres` | Bearer token | `{config_overrides: Record}` | `{config_overrides}` | +| Method | Path | Auth | Request body | Response (used fields) | +| ------ | --------------------------------------------- | ------------ | -------------------------------------------------------------- | ---------------------- | +| `GET` | `/v1/projects/{ref}/config/database/postgres` | Bearer token | none | full JSON object | +| `PUT` | `/v1/projects/{ref}/config/database/postgres` | Bearer token | full config object (conditional GET merge unless replace mode) | full JSON object | + +The initial `GET` is skipped when `--replace-existing-overrides` is set. Otherwise the command fetches current overrides first, merges the new values locally, then sends the final merged object back via `PUT`. ## Environment Variables -| Variable | Purpose | Required? | -| ----------------------- | ---------------------------------------------------- | ------------------------------------------------------- | -| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring → `~/.supabase/access-token`) | -| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | +| Variable | Purpose | Required? | +| ----------------------- | ---------------------------------------------------- | --------------------------------------------------------- | +| `SUPABASE_ACCESS_TOKEN` | auth token (bypasses credential file/keyring lookup) | no (falls back to keyring -> `~/.supabase/access-token`) | +| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | +| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` -> prompt) | ## Exit Codes -| Code | Condition | -| ---- | ---------------------------------------------------------- | -| `0` | success — Postgres config overrides updated | -| `1` | authentication error — no valid token found | -| `1` | API error — non-2xx response from Postgres config endpoint | -| `1` | invalid config key or value | -| `1` | network / connection failure | +| Code | Condition | +| ---- | ------------------------------------------------------------------------------------------------------------ | +| `0` | success - Postgres config updated | +| `1` | malformed `--config` (`LegacyPostgresConfigInvalidConfigValueError`) | +| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | +| `1` | initial GET non-2xx (`LegacyPostgresConfigGetUnexpectedStatusError`) | +| `1` | initial GET transport failure (`LegacyPostgresConfigGetNetworkError`) | +| `1` | PUT non-2xx (`LegacyPostgresConfigUpdateUnexpectedStatusError`) | +| `1` | PUT transport failure (`LegacyPostgresConfigUpdateNetworkError`) | +| `1` | request serialization failure (`LegacyPostgresConfigUpdateSerializeError`) | +| `1` | invalid JSON response (`LegacyPostgresConfigGetUnmarshalError` / `LegacyPostgresConfigUpdateUnmarshalError`) | + +## Telemetry Events Fired + +| Event | When | Notable properties / groups | +| ---------------------- | ------------------------------------------ | --------------------------------------------------------------------- | +| `cli_command_executed` | post-run, success or failure (via wrapper) | `exit_code`, `duration_ms`, `flags` (`--project-ref` -> ``) | ## Output -### `--output-format text` (Go CLI compatible) +Matches `get` on success: stderr headings plus the Glamour-rendered table. + +### `--output-format text` (default) - Go CLI compatible + +Renders the updated config map as a Glamour ASCII table. + +### Go `--output pretty` + +Identical to text mode. + +### Go `--output json` + +Go-compatible indented JSON of the updated config object. + +### Go `--output yaml` + +YAML representation of the updated config object. + +### Go `--output toml` + +TOML representation of the updated config object. + +### Go `--output env` -Prints updated Postgres configuration overrides to stdout. +Flat `KEY="value"` lines for the updated config object. ### `--output-format json` -Single JSON object emitted to stdout on success. +Single `success` event whose data is the updated config object. ### `--output-format stream-json` @@ -55,6 +93,11 @@ One `result` event on success. ## Notes -- Flags: `--config` (repeatable, `key=value` format), `--replace-existing-overrides`, `--no-restart`. +- The Go `--output` flag wins over the TS `--output-format` flag when both are provided. +- Flags: `--config` (repeatable, parsed with the same `strings.Split(value, "=")` rule as Go), `--replace-existing-overrides`, `--no-restart`. - Requires `--project-ref` or a linked project (`.supabase/config.json`). -- Custom configuration overrides the optimizations generated based on compute add-ons in use. +- Integer-like values are coerced to integers, boolean-like values are coerced to booleans, and everything else stays stringly typed before the final JSON body is serialized. +- Keys ending in `_timeout` are always stringified before the `PUT`, matching the Go timeout-normalization branch. +- `--no-restart` injects `restart_database = false` into the final request body. +- `linked-project.json` is written after the project ref resolves, regardless of whether the fetch or update succeeds. +- `telemetry.json` is written on every invocation, including failures. diff --git a/apps/cli/src/legacy/commands/postgres-config/update/update.command.ts b/apps/cli/src/legacy/commands/postgres-config/update/update.command.ts index cae978227c..09b6be8303 100644 --- a/apps/cli/src/legacy/commands/postgres-config/update/update.command.ts +++ b/apps/cli/src/legacy/commands/postgres-config/update/update.command.ts @@ -1,5 +1,9 @@ import { Command, Flag } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; + +import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts"; +import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; +import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyPostgresConfigUpdate } from "./update.handler.ts"; const config = { @@ -26,5 +30,11 @@ export type LegacyPostgresConfigUpdateFlags = CliCommand.Command.Config.Infer legacyPostgresConfigUpdate(flags)), + Command.withHandler((flags) => + legacyPostgresConfigUpdate(flags).pipe( + withLegacyCommandInstrumentation({ flags }), + withJsonErrorHandling, + ), + ), + Command.provide(legacyManagementApiRuntimeLayer(["postgres-config", "update"])), ); diff --git a/apps/cli/src/legacy/commands/postgres-config/update/update.handler.ts b/apps/cli/src/legacy/commands/postgres-config/update/update.handler.ts index d42be5bdb1..83433ae2ef 100644 --- a/apps/cli/src/legacy/commands/postgres-config/update/update.handler.ts +++ b/apps/cli/src/legacy/commands/postgres-config/update/update.handler.ts @@ -1,17 +1,78 @@ -import { Effect, Option } from "effect"; -import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; +import { Effect } from "effect"; + +import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts"; +import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts"; +import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; +import { Output } from "../../../../shared/output/output.service.ts"; +import { + LegacyPostgresConfigInvalidConfigValueError, + LegacyPostgresConfigUpdateNetworkError, + LegacyPostgresConfigUpdateSerializeError, + LegacyPostgresConfigUpdateUnexpectedStatusError, + LegacyPostgresConfigUpdateUnmarshalError, +} from "../postgres-config.errors.ts"; +import { + fetchCurrentPostgresConfig, + normalizeTimeoutConfig, + parseConfigValue, + putPostgresConfig, + writePostgresConfigOutput, +} from "../postgres-config.shared.ts"; import type { LegacyPostgresConfigUpdateFlags } from "./update.command.ts"; export const legacyPostgresConfigUpdate = Effect.fn("legacy.postgres-config.update")(function* ( flags: LegacyPostgresConfigUpdateFlags, ) { - const proxy = yield* LegacyGoProxy; - const args: string[] = ["postgres-config", "update"]; - if (Option.isSome(flags.projectRef)) args.push("--project-ref", flags.projectRef.value); - for (const value of flags.config) { - args.push("--config", value); - } - if (flags.replaceExistingOverrides) args.push("--replace-existing-overrides"); - if (flags.noRestart) args.push("--no-restart"); - yield* proxy.exec(args); + const output = yield* Output; + const resolver = yield* LegacyProjectRefResolver; + const linkedProjectCache = yield* LegacyLinkedProjectCache; + const telemetryState = yield* LegacyTelemetryState; + + yield* Effect.gen(function* () { + const nextOverrides: Record = {}; + for (const config of flags.config) { + const splits = config.split("="); + if (splits.length !== 2) { + return yield* new LegacyPostgresConfigInvalidConfigValueError({ input: config }); + } + nextOverrides[splits[0] ?? ""] = splits[1] ?? ""; + } + + const ref = yield* resolver.resolve(flags.projectRef); + + yield* Effect.gen(function* () { + const updating = + output.format === "text" ? yield* output.task("Updating Postgres config...") : undefined; + + const finalOverrides = flags.replaceExistingOverrides + ? {} + : yield* fetchCurrentPostgresConfig(ref).pipe( + Effect.tapError(() => updating?.fail() ?? Effect.void), + ); + + for (const [key, value] of Object.entries(nextOverrides)) { + finalOverrides[key] = parseConfigValue(value); + } + + if (flags.noRestart) { + finalOverrides["restart_database"] = false; + } + + normalizeTimeoutConfig(finalOverrides); + + const updated = yield* putPostgresConfig(ref, finalOverrides, { + serializeError: (args) => new LegacyPostgresConfigUpdateSerializeError(args), + networkError: (args) => new LegacyPostgresConfigUpdateNetworkError(args), + statusError: (args) => new LegacyPostgresConfigUpdateUnexpectedStatusError(args), + unmarshalError: (args) => new LegacyPostgresConfigUpdateUnmarshalError(args), + networkMessage: (description) => `failed to update config overrides: ${description}`, + statusMessage: (status, body) => + `unexpected update config overrides status ${status}: ${body}`, + unmarshalMessage: (description) => `failed to unmarshal update response: ${description}`, + }).pipe(Effect.tapError(() => updating?.fail() ?? Effect.void)); + + yield* updating?.clear() ?? Effect.void; + yield* writePostgresConfigOutput(updated); + }).pipe(Effect.ensuring(linkedProjectCache.cache(ref))); + }).pipe(Effect.ensuring(telemetryState.flush)); }); diff --git a/apps/cli/src/legacy/output/legacy-quiet-progress-text-output.layer.ts b/apps/cli/src/legacy/output/legacy-quiet-progress-text-output.layer.ts new file mode 100644 index 0000000000..f05f93ef7d --- /dev/null +++ b/apps/cli/src/legacy/output/legacy-quiet-progress-text-output.layer.ts @@ -0,0 +1,45 @@ +import { Effect, Layer } from "effect"; + +import { textOutputLayer } from "../../shared/output/output.layer.ts"; +import { Output } from "../../shared/output/output.service.ts"; + +/** + * Legacy wrapper over the shared text output layer for Go machine-format + * requests (`-o json|yaml|toml|env`). + * + * Go's `--output` selects a machine encoder that the handler writes via + * `output.raw`. If the text layer stays fully active, its progress spinner + * writes ANSI escape sequences to stdout and corrupts that payload — see + * CLI-1546, where `branches list -o json` emitted a hide-cursor sequence ahead + * of the JSON and broke `JSON.parse`. + * + * This layer suppresses ONLY the transient progress UI (`task`/`progress`). + * Everything else (errors -> red text on stderr, `raw`, logs, `format: "text"`) + * delegates to the text layer unchanged, so Go output parity is preserved + * exactly while stdout stays parseable. + */ +export const legacyQuietProgressTextOutputLayer = Layer.effect( + Output, + Effect.gen(function* () { + const base = yield* Output; + return Output.of({ + ...base, + task: () => + Effect.succeed({ + message: () => Effect.void, + succeed: () => Effect.void, + fail: () => Effect.void, + info: () => Effect.void, + cancel: () => Effect.void, + clear: () => Effect.void, + }), + progress: () => + Effect.succeed({ + start: () => Effect.void, + advance: () => Effect.void, + message: () => Effect.void, + stop: () => Effect.void, + }), + }); + }), +).pipe(Layer.provide(textOutputLayer)); diff --git a/apps/cli/src/legacy/output/legacy-quiet-progress-text-output.layer.unit.test.ts b/apps/cli/src/legacy/output/legacy-quiet-progress-text-output.layer.unit.test.ts new file mode 100644 index 0000000000..cbab687f78 --- /dev/null +++ b/apps/cli/src/legacy/output/legacy-quiet-progress-text-output.layer.unit.test.ts @@ -0,0 +1,116 @@ +import { describe, expect, it } from "@effect/vitest"; +import { beforeEach, vi } from "vitest"; +import { Effect, Layer } from "effect"; + +import { mockTty } from "../../../tests/helpers/mocks.ts"; +import { Output } from "../../shared/output/output.service.ts"; +import { legacyQuietProgressTextOutputLayer } from "./legacy-quiet-progress-text-output.layer.ts"; + +// Mirror the shared text-layer test so the wrapped textOutputLayer resolves the +// clack spinner through a spy we can assert was never created. +const mockClack = vi.hoisted(() => ({ + intro: vi.fn(), + outro: vi.fn(), + spinnerFactory: vi.fn(), + progressFactory: vi.fn(), + spinnerHandle: { + start: vi.fn(), + stop: vi.fn(), + cancel: vi.fn(), + error: vi.fn(), + message: vi.fn(), + clear: vi.fn(), + isCancelled: false, + }, + log: { + message: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + success: vi.fn(), + step: vi.fn(), + }, + text: vi.fn(), + password: vi.fn(), + confirm: vi.fn(), + select: vi.fn(), + autocomplete: vi.fn(), + multiselect: vi.fn(), + cancel: vi.fn(), + isCancel: vi.fn((_v: unknown) => false), +})); + +vi.mock("@clack/prompts", () => ({ + intro: (a: unknown) => mockClack.intro(a), + outro: (a: unknown) => mockClack.outro(a), + log: mockClack.log, + spinner: () => mockClack.spinnerFactory(), + progress: () => mockClack.progressFactory(), + text: (a: unknown) => mockClack.text(a), + password: (a: unknown) => mockClack.password(a), + confirm: (a: unknown) => mockClack.confirm(a), + select: (a: unknown) => mockClack.select(a), + autocomplete: (a: unknown) => mockClack.autocomplete(a), + multiselect: (a: unknown) => mockClack.multiselect(a), + cancel: (a: unknown) => mockClack.cancel(a), + isCancel: (a: unknown) => mockClack.isCancel(a), +})); + +beforeEach(() => { + vi.resetAllMocks(); + vi.useRealTimers(); + mockClack.isCancel.mockReturnValue(false); + mockClack.spinnerFactory.mockReturnValue(mockClack.spinnerHandle); +}); + +describe("legacyQuietProgressTextOutputLayer", () => { + const layer = legacyQuietProgressTextOutputLayer.pipe( + Layer.provide(mockTty({ stdoutIsTty: true })), + ); + + it.effect("never starts a spinner, even after the spinner delay elapses", () => + Effect.gen(function* () { + vi.useFakeTimers(); + const out = yield* Output; + const task = yield* out.task("Fetching branches..."); + yield* task.message("Still fetching..."); + // Past TASK_SPINNER_DELAY_MS (200ms) — the text layer would have shown a + // spinner by now; the quiet wrapper must not. + vi.advanceTimersByTime(500); + yield* task.clear(); + + expect(mockClack.spinnerFactory).not.toHaveBeenCalled(); + expect(mockClack.spinnerHandle.start).not.toHaveBeenCalled(); + }).pipe(Effect.provide(layer)), + ); + + it.effect("never starts a progress bar", () => + Effect.gen(function* () { + const out = yield* Output; + const bar = yield* out.progress({ max: 3 }); + yield* bar.start("Working..."); + yield* bar.advance(1); + yield* bar.stop("Done."); + + expect(mockClack.progressFactory).not.toHaveBeenCalled(); + }).pipe(Effect.provide(layer)), + ); + + it.effect("stays on the text layer so errors keep Go parity (red text on stderr)", () => + Effect.gen(function* () { + const out = yield* Output; + // `format === "text"` is what routes withJsonErrorHandling back to the + // top-level text `output.fail` (red text on stderr) instead of a JSON + // envelope on stdout — i.e. it preserves Go error-output parity. + expect(out.format).toBe("text"); + }).pipe(Effect.provide(layer)), + ); + + it.effect("delegates non-progress output to the text layer", () => + Effect.gen(function* () { + const out = yield* Output; + yield* out.info("hello"); + expect(mockClack.log.info).toHaveBeenCalledWith("hello"); + }).pipe(Effect.provide(layer)), + ); +}); diff --git a/apps/cli/src/legacy/telemetry/legacy-analytics.layer.ts b/apps/cli/src/legacy/telemetry/legacy-analytics.layer.ts index adc324a7e5..da0062158d 100644 --- a/apps/cli/src/legacy/telemetry/legacy-analytics.layer.ts +++ b/apps/cli/src/legacy/telemetry/legacy-analytics.layer.ts @@ -26,7 +26,7 @@ import { PropSchemaVersion, PropSessionId, } from "../../shared/telemetry/event-catalog.ts"; -import { posthogConfig } from "../../shared/telemetry/posthog-config.ts"; +import { resolvePosthogConfig } from "../../shared/telemetry/posthog-config.ts"; import { telemetryRuntimeLayer } from "../../shared/telemetry/runtime.layer.ts"; import { TelemetryRuntime } from "../../shared/telemetry/runtime.service.ts"; @@ -139,8 +139,9 @@ export const legacyAnalyticsLayer = Layer.effect( const aiTool = yield* AiTool; const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; + const posthogConfig = resolvePosthogConfig(process.env); - if (runtime.consent !== "granted") { + if (runtime.consent !== "granted" || Option.isNone(posthogConfig.key)) { return Analytics.of({ capture: () => Effect.void, identify: () => Effect.void, @@ -149,7 +150,7 @@ export const legacyAnalyticsLayer = Layer.effect( }); } - const client = new PostHog(posthogConfig.key, { + const client = new PostHog(posthogConfig.key.value, { host: posthogConfig.host, flushAt: 1, flushInterval: 0, diff --git a/apps/cli/src/next/auth/platform-api.layer.unit.test.ts b/apps/cli/src/next/auth/platform-api.layer.unit.test.ts index 5292ac1957..ec8a0bfde6 100644 --- a/apps/cli/src/next/auth/platform-api.layer.unit.test.ts +++ b/apps/cli/src/next/auth/platform-api.layer.unit.test.ts @@ -49,7 +49,7 @@ function cliConfigLayer(token = Option.none>()) { dashboardUrl: "https://supabase.com/dashboard", projectHost: "supabase.co", telemetryPosthogHost: "https://us.i.posthog.com", - telemetryPosthogKey: "phc_test_key", + telemetryPosthogKey: Option.some("phc_test_key"), accessToken: token, noKeyring: Option.none(), supabaseHome: "/tmp/supabase-cli-test-home", diff --git a/apps/cli/src/next/commands/functions/list/list.integration.test.ts b/apps/cli/src/next/commands/functions/list/list.integration.test.ts index 95565e12cb..3e76bc6197 100644 --- a/apps/cli/src/next/commands/functions/list/list.integration.test.ts +++ b/apps/cli/src/next/commands/functions/list/list.integration.test.ts @@ -79,7 +79,7 @@ function cliConfigLayer() { dashboardUrl: "https://supabase.com/dashboard", projectHost: "supabase.co", telemetryPosthogHost: "https://us.i.posthog.com", - telemetryPosthogKey: "phc_test_key", + telemetryPosthogKey: Option.some("phc_test_key"), accessToken: Option.none(), noKeyring: Option.none(), supabaseHome: "/tmp/supabase-cli-test-home", diff --git a/apps/cli/src/next/commands/functions/new/new.integration.test.ts b/apps/cli/src/next/commands/functions/new/new.integration.test.ts index cb0fd30b3c..7396813785 100644 --- a/apps/cli/src/next/commands/functions/new/new.integration.test.ts +++ b/apps/cli/src/next/commands/functions/new/new.integration.test.ts @@ -44,7 +44,7 @@ function commandTreeSupportLayer(cwd: string) { dashboardUrl: "https://supabase.com/dashboard", projectHost: "supabase.co", telemetryPosthogHost: "https://us.i.posthog.com", - telemetryPosthogKey: "phc_test_key", + telemetryPosthogKey: Option.some("phc_test_key"), accessToken: Option.none(), noKeyring: Option.none(), supabaseHome: join(cwd, ".cache", "supabase"), diff --git a/apps/cli/src/next/commands/telemetry/telemetry.command.ts b/apps/cli/src/next/commands/telemetry/telemetry.command.ts index 6b4a908fcb..48f07b80cd 100644 --- a/apps/cli/src/next/commands/telemetry/telemetry.command.ts +++ b/apps/cli/src/next/commands/telemetry/telemetry.command.ts @@ -1,4 +1,4 @@ -import { Effect } from "effect"; +import { Effect, Option } from "effect"; import { Command } from "effect/unstable/cli"; import { Output } from "../../../shared/output/output.service.ts"; import { withJsonErrorHandling } from "../../../shared/output/json-error-handling.ts"; @@ -57,7 +57,10 @@ const telemetryStatus = Effect.gen(function* () { yield* output.success(`Telemetry is ${effectiveConsent}.`, { consent: effectiveConsent, config_path: `${configDir}/telemetry.json`, - persisted_consent: config?.consent ?? null, + persisted_consent: Option.match(config, { + onNone: () => null, + onSome: (value) => value.consent, + }), }); }); diff --git a/apps/cli/src/next/config/cli-config.layer.ts b/apps/cli/src/next/config/cli-config.layer.ts index e4032fe6a7..07b9264306 100644 --- a/apps/cli/src/next/config/cli-config.layer.ts +++ b/apps/cli/src/next/config/cli-config.layer.ts @@ -1,13 +1,12 @@ import { Effect, Layer, Option, Redacted } from "effect"; import { RuntimeInfo } from "../../shared/runtime/runtime-info.service.ts"; +import { resolvePosthogConfig } from "../../shared/telemetry/posthog-config.ts"; import { CliConfig } from "./cli-config.service.ts"; import { ProjectContext } from "./project-context.service.ts"; const SUPABASE_API_URL = "https://api.supabase.com"; const SUPABASE_DASHBOARD_URL = "https://supabase.com/dashboard"; const SUPABASE_PROJECT_HOST = "supabase.co"; -const SUPABASE_TELEMETRY_POSTHOG_HOST = "https://eu.i.posthog.com"; -const SUPABASE_TELEMETRY_POSTHOG_KEY = "phc_ihjC3EeB2wXCt87yccX5idgIgeZsub7WG0XR5hGFhJz"; function readEnv( env: Readonly>, @@ -24,6 +23,7 @@ const makeCliConfig = Effect.gen(function* () { onNone: () => process.env, onSome: (projectEnv) => projectEnv.values, }); + const posthogConfig = resolvePosthogConfig(effectiveEnv); return CliConfig.of({ apiUrl: Option.getOrElse(readEnv(effectiveEnv, "SUPABASE_API_URL"), () => SUPABASE_API_URL), @@ -35,14 +35,8 @@ const makeCliConfig = Effect.gen(function* () { readEnv(effectiveEnv, "SUPABASE_PROJECT_HOST"), () => SUPABASE_PROJECT_HOST, ), - telemetryPosthogHost: Option.getOrElse( - readEnv(effectiveEnv, "SUPABASE_TELEMETRY_POSTHOG_HOST"), - () => SUPABASE_TELEMETRY_POSTHOG_HOST, - ), - telemetryPosthogKey: Option.getOrElse( - readEnv(effectiveEnv, "SUPABASE_TELEMETRY_POSTHOG_KEY"), - () => SUPABASE_TELEMETRY_POSTHOG_KEY, - ), + telemetryPosthogHost: posthogConfig.host, + telemetryPosthogKey: posthogConfig.key, accessToken: Option.map(readEnv(effectiveEnv, "SUPABASE_ACCESS_TOKEN"), (token) => Redacted.make(token, { label: "SUPABASE_ACCESS_TOKEN" }), ), diff --git a/apps/cli/src/next/config/cli-config.layer.unit.test.ts b/apps/cli/src/next/config/cli-config.layer.unit.test.ts index f4c3fcbb7c..7d34137e64 100644 --- a/apps/cli/src/next/config/cli-config.layer.unit.test.ts +++ b/apps/cli/src/next/config/cli-config.layer.unit.test.ts @@ -158,12 +158,12 @@ describe("cliConfigLayer", () => { ); }); - it.live("falls back to the shipped PostHog key when no env override is set", () => { + it.live("has no PostHog key when nothing is injected or overridden", () => { const tempDir = makeTempDir(); return Effect.gen(function* () { const cliConfig = yield* CliConfig; - expect(cliConfig.telemetryPosthogKey).toMatch(/^phc_/); + expect(Option.isNone(cliConfig.telemetryPosthogKey)).toBe(true); }).pipe( Effect.provide(buildLayer({ cwd: tempDir })), Effect.ensuring(Effect.tryPromise(() => rm(tempDir, { recursive: true, force: true }))), @@ -175,7 +175,7 @@ describe("cliConfigLayer", () => { return Effect.gen(function* () { const cliConfig = yield* CliConfig; - expect(cliConfig.telemetryPosthogKey).toBe("phc_env_override"); + expect(cliConfig.telemetryPosthogKey).toEqual(Option.some("phc_env_override")); }).pipe( Effect.provide( buildLayer({ @@ -188,4 +188,25 @@ describe("cliConfigLayer", () => { Effect.ensuring(Effect.tryPromise(() => rm(tempDir, { recursive: true, force: true }))), ); }); + + it.live("uses the build-injected PostHog key and host when no runtime override is set", () => { + const tempDir = makeTempDir(); + return Effect.gen(function* () { + const cliConfig = yield* CliConfig; + + expect(cliConfig.telemetryPosthogHost).toBe("https://build-posthog.example"); + expect(cliConfig.telemetryPosthogKey).toEqual(Option.some("phc_build_key")); + }).pipe( + Effect.provide( + buildLayer({ + cwd: tempDir, + env: { + SUPABASE_CLI_POSTHOG_HOST: "https://build-posthog.example", + SUPABASE_CLI_POSTHOG_KEY: "phc_build_key", + }, + }), + ), + Effect.ensuring(Effect.tryPromise(() => rm(tempDir, { recursive: true, force: true }))), + ); + }); }); diff --git a/apps/cli/src/next/config/cli-config.service.ts b/apps/cli/src/next/config/cli-config.service.ts index 62fc3803f3..56cadaa0a0 100644 --- a/apps/cli/src/next/config/cli-config.service.ts +++ b/apps/cli/src/next/config/cli-config.service.ts @@ -6,7 +6,7 @@ interface CliConfigShape { readonly dashboardUrl: string; readonly projectHost: string; readonly telemetryPosthogHost: string; - readonly telemetryPosthogKey: string; + readonly telemetryPosthogKey: Option.Option; readonly accessToken: Option.Option>; readonly noKeyring: Option.Option; readonly supabaseHome: string; diff --git a/apps/cli/src/shared/telemetry/analytics.layer.ts b/apps/cli/src/shared/telemetry/analytics.layer.ts index f42ccd284e..5499e63e2d 100644 --- a/apps/cli/src/shared/telemetry/analytics.layer.ts +++ b/apps/cli/src/shared/telemetry/analytics.layer.ts @@ -49,9 +49,8 @@ export const analyticsLayer = Layer.effect( const runtime = yield* TelemetryRuntime; const cliConfig = yield* CliConfig; const aiTool = yield* AiTool; - const posthogKey = cliConfig.telemetryPosthogKey; - if (runtime.consent !== "granted") { + if (runtime.consent !== "granted" || Option.isNone(cliConfig.telemetryPosthogKey)) { return Analytics.of({ capture: () => Effect.void, identify: () => Effect.void, @@ -60,7 +59,7 @@ export const analyticsLayer = Layer.effect( }); } - const client = new PostHog(posthogKey, { + const client = new PostHog(cliConfig.telemetryPosthogKey.value, { host: cliConfig.telemetryPosthogHost, flushAt: 1, flushInterval: 0, diff --git a/apps/cli/src/shared/telemetry/consent.ts b/apps/cli/src/shared/telemetry/consent.ts index b3114ae489..1f1bd235f1 100644 --- a/apps/cli/src/shared/telemetry/consent.ts +++ b/apps/cli/src/shared/telemetry/consent.ts @@ -1,20 +1,29 @@ -import { Effect, FileSystem, Option, Path } from "effect"; +import { Effect, FileSystem, Option, Path, Schema } from "effect"; import { CliConfig } from "../../next/config/cli-config.service.ts"; -import type { ConsentState, TelemetryConfig } from "./types.ts"; +import { TelemetryConfigSchema, type TelemetryConfig } from "./types.ts"; export const getConfigDir = CliConfig.useSync((cliConfig) => cliConfig.supabaseHome); +const TelemetryConfigFileSchema = Schema.fromJsonString(TelemetryConfigSchema); +const decodeTelemetryConfigFile = Schema.decodeUnknownEffect(TelemetryConfigFileSchema); +const encodeTelemetryConfig = Schema.encodeUnknownSync(TelemetryConfigSchema); + +function encodePrettyJson(value: unknown): string { + return `${JSON.stringify(value, null, 2)}\n`; +} + export const readTelemetryConfig = Effect.fnUntraced( function* (configDir: string) { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const configPath = path.join(configDir, "telemetry.json"); const exists = yield* fs.exists(configPath); - if (!exists) return null; + if (!exists) return Option.none(); const content = yield* fs.readFileString(configPath); - return JSON.parse(content) as TelemetryConfig; + const config = yield* decodeTelemetryConfigFile(content); + return Option.some(config); }, - (effect) => Effect.orElseSucceed(effect, () => null), + (effect) => Effect.orElseSucceed(effect, () => Option.none()), ); export const writeTelemetryConfig = Effect.fnUntraced(function* ( @@ -24,22 +33,28 @@ export const writeTelemetryConfig = Effect.fnUntraced(function* ( const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; yield* fs.makeDirectory(configDir, { recursive: true, mode: 0o700 }); - yield* fs.writeFileString( - path.join(configDir, "telemetry.json"), - JSON.stringify(config, null, 2), - { mode: 0o600 }, - ); + const configPath = path.join(configDir, "telemetry.json"); + const tmpPath = `${configPath}.tmp.${Date.now()}`; + yield* fs.writeFileString(tmpPath, encodePrettyJson(encodeTelemetryConfig(config)), { + mode: 0o600, + }); + yield* fs.rename(tmpPath, configPath); }, Effect.orDie); -export const getEffectiveConsent = Effect.fnUntraced(function* (config: TelemetryConfig | null) { +export const getEffectiveConsent = Effect.fnUntraced(function* ( + config: Option.Option, +) { const cliConfig = yield* CliConfig; const telemetryDisabled = cliConfig.telemetryDisabled; if (Option.isSome(telemetryDisabled) && telemetryDisabled.value === "1") { - return "denied" as ConsentState; + return "denied" as const; } const doNotTrack = cliConfig.doNotTrack; - if (Option.isSome(doNotTrack) && doNotTrack.value === "1") return "denied" as ConsentState; + if (Option.isSome(doNotTrack) && doNotTrack.value === "1") return "denied" as const; - return (config?.consent ?? "granted") as ConsentState; + return Option.match(config, { + onNone: () => "granted" as const, + onSome: (value) => value.consent, + }); }); diff --git a/apps/cli/src/shared/telemetry/consent.unit.test.ts b/apps/cli/src/shared/telemetry/consent.unit.test.ts index bf0391678a..adb4d63d30 100644 --- a/apps/cli/src/shared/telemetry/consent.unit.test.ts +++ b/apps/cli/src/shared/telemetry/consent.unit.test.ts @@ -1,12 +1,16 @@ import { describe, expect, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import { BunServices } from "@effect/platform-bun"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { Effect, Layer, Option } from "effect"; import { cliConfigLayer } from "../../next/config/cli-config.layer.ts"; import { mockProjectContext, mockRuntimeInfo, processEnvLayer, } from "../../../tests/helpers/mocks.ts"; -import { getEffectiveConsent } from "./consent.ts"; +import { getEffectiveConsent, readTelemetryConfig } from "./consent.ts"; import type { TelemetryConfig } from "./types.ts"; function makeConfig(consent: TelemetryConfig["consent"]): TelemetryConfig { @@ -40,53 +44,103 @@ function emptyEnv() { ); } +function makeTempDir(): string { + return mkdtempSync(path.join(tmpdir(), "supabase-consent-test-")); +} + +function writeTelemetryFile(dir: string, content: string): void { + writeFileSync(path.join(dir, "telemetry.json"), content); +} + describe("getEffectiveConsent", () => { it.live("returns denied when DO_NOT_TRACK=1", () => Effect.gen(function* () { - const consent = yield* getEffectiveConsent(makeConfig("granted")); + const consent = yield* getEffectiveConsent(Option.some(makeConfig("granted"))); expect(consent).toBe("denied"); }).pipe(Effect.provide(withEnv({ DO_NOT_TRACK: "1" }))), ); it.live("returns denied when SUPABASE_TELEMETRY_DISABLED=1", () => Effect.gen(function* () { - const consent = yield* getEffectiveConsent(makeConfig("granted")); + const consent = yield* getEffectiveConsent(Option.some(makeConfig("granted"))); expect(consent).toBe("denied"); }).pipe(Effect.provide(withEnv({ SUPABASE_TELEMETRY_DISABLED: "1" }))), ); it.live("SUPABASE_TELEMETRY_DISABLED=1 takes precedence over persisted granted consent", () => Effect.gen(function* () { - const consent = yield* getEffectiveConsent(null); + const consent = yield* getEffectiveConsent(Option.none()); expect(consent).toBe("denied"); }).pipe(Effect.provide(withEnv({ SUPABASE_TELEMETRY_DISABLED: "1" }))), ); it.live("DO_NOT_TRACK=1 takes precedence over persisted granted consent", () => Effect.gen(function* () { - const consent = yield* getEffectiveConsent(makeConfig("granted")); + const consent = yield* getEffectiveConsent(Option.some(makeConfig("granted"))); expect(consent).toBe("denied"); }).pipe(Effect.provide(withEnv({ DO_NOT_TRACK: "1" }))), ); it.live("SUPABASE_TELEMETRY_DISABLED=1 takes precedence over DO_NOT_TRACK=1", () => Effect.gen(function* () { - const consent = yield* getEffectiveConsent(makeConfig("granted")); + const consent = yield* getEffectiveConsent(Option.some(makeConfig("granted"))); expect(consent).toBe("denied"); }).pipe(Effect.provide(withEnv({ SUPABASE_TELEMETRY_DISABLED: "1", DO_NOT_TRACK: "1" }))), ); it.live("returns config consent value when set", () => Effect.gen(function* () { - expect(yield* getEffectiveConsent(makeConfig("granted"))).toBe("granted"); - expect(yield* getEffectiveConsent(makeConfig("denied"))).toBe("denied"); + expect(yield* getEffectiveConsent(Option.some(makeConfig("granted")))).toBe("granted"); + expect(yield* getEffectiveConsent(Option.some(makeConfig("denied")))).toBe("denied"); }).pipe(Effect.provide(emptyEnv())), ); it.live("defaults to granted when no config (opt-out model)", () => Effect.gen(function* () { - const consent = yield* getEffectiveConsent(null); + const consent = yield* getEffectiveConsent(Option.none()); expect(consent).toBe("granted"); }).pipe(Effect.provide(emptyEnv())), ); }); + +describe("readTelemetryConfig", () => { + it.live("decodes a valid telemetry config", () => { + const dir = makeTempDir(); + const expected = makeConfig("denied"); + writeTelemetryFile(dir, JSON.stringify(expected)); + + return Effect.gen(function* () { + const config = yield* readTelemetryConfig(dir); + expect(config).toEqual(Option.some(expected)); + }).pipe( + Effect.provide(BunServices.layer), + Effect.ensuring(Effect.sync(() => rmSync(dir, { recursive: true, force: true }))), + ); + }); + + it.live("returns none for malformed JSON instead of throwing", () => { + const dir = makeTempDir(); + writeTelemetryFile(dir, ""); + + return Effect.gen(function* () { + const config = yield* readTelemetryConfig(dir); + expect(config).toEqual(Option.none()); + }).pipe( + Effect.provide(BunServices.layer), + Effect.ensuring(Effect.sync(() => rmSync(dir, { recursive: true, force: true }))), + ); + }); + + it.live("returns none for structurally invalid telemetry config", () => { + const dir = makeTempDir(); + writeTelemetryFile(dir, JSON.stringify({ consent: "granted" })); + + return Effect.gen(function* () { + const config = yield* readTelemetryConfig(dir); + expect(config).toEqual(Option.none()); + }).pipe( + Effect.provide(BunServices.layer), + Effect.ensuring(Effect.sync(() => rmSync(dir, { recursive: true, force: true }))), + ); + }); +}); diff --git a/apps/cli/src/shared/telemetry/identity.ts b/apps/cli/src/shared/telemetry/identity.ts index 6a74eae775..7edef25dd3 100644 --- a/apps/cli/src/shared/telemetry/identity.ts +++ b/apps/cli/src/shared/telemetry/identity.ts @@ -1,4 +1,4 @@ -import { Effect } from "effect"; +import { Effect, Option } from "effect"; import { readTelemetryConfig, writeTelemetryConfig } from "./consent.ts"; import type { TelemetryConfig } from "./types.ts"; @@ -8,7 +8,7 @@ export const resolveIdentity = Effect.fnUntraced(function* (configDir: string) { const config = yield* readTelemetryConfig(configDir); const now = Date.now(); - if (!config) { + if (Option.isNone(config)) { const newConfig: TelemetryConfig = { consent: "granted", device_id: crypto.randomUUID(), @@ -24,17 +24,18 @@ export const resolveIdentity = Effect.fnUntraced(function* (configDir: string) { }; } - const isSessionExpired = now - config.session_last_active > SESSION_TIMEOUT_MS; - const sessionId = isSessionExpired ? crypto.randomUUID() : config.session_id; + const currentConfig = config.value; + const isSessionExpired = now - currentConfig.session_last_active > SESSION_TIMEOUT_MS; + const sessionId = isSessionExpired ? crypto.randomUUID() : currentConfig.session_id; yield* writeTelemetryConfig( - { ...config, session_id: sessionId, session_last_active: now }, + { ...currentConfig, session_id: sessionId, session_last_active: now }, configDir, ); return { - deviceId: config.device_id, + deviceId: currentConfig.device_id, sessionId, - distinctId: config.distinct_id, + distinctId: currentConfig.distinct_id, isFirstRun: false, }; }); @@ -43,7 +44,10 @@ export const saveDistinctId = Effect.fnUntraced(function* (configDir: string, di const identity = yield* resolveIdentity(configDir); const config = yield* readTelemetryConfig(configDir); const nextConfig: TelemetryConfig = { - consent: config?.consent ?? "granted", + consent: Option.match(config, { + onNone: () => "granted", + onSome: (value) => value.consent, + }), device_id: identity.deviceId, session_id: identity.sessionId, session_last_active: Date.now(), @@ -56,7 +60,10 @@ export const clearDistinctId = Effect.fnUntraced(function* (configDir: string) { const identity = yield* resolveIdentity(configDir); const config = yield* readTelemetryConfig(configDir); const nextConfig: TelemetryConfig = { - consent: config?.consent ?? "granted", + consent: Option.match(config, { + onNone: () => "granted", + onSome: (value) => value.consent, + }), device_id: identity.deviceId, session_id: identity.sessionId, session_last_active: Date.now(), diff --git a/apps/cli/src/shared/telemetry/posthog-config.ts b/apps/cli/src/shared/telemetry/posthog-config.ts index 5b7ef1b8d0..a37365d69e 100644 --- a/apps/cli/src/shared/telemetry/posthog-config.ts +++ b/apps/cli/src/shared/telemetry/posthog-config.ts @@ -1,14 +1,43 @@ // PostHog connection config shared by both shells' analytics layers. -// Defaults match apps/cli-go/internal/utils/misc.go PostHogAPIKey / PostHogEndpoint -// (set via Go's -ldflags at build time, hard-coded here for the TS build). +// Release builds inject the shipped host/key via apps/cli/scripts/build.ts. +import { Option } from "effect"; const DEFAULT_HOST = "https://eu.i.posthog.com"; -const DEFAULT_KEY = "phc_ihjC3EeB2wXCt87yccX5idgIgeZsub7WG0XR5hGFhJz"; -export const posthogConfig: { +export interface PosthogConfig { readonly host: string; - readonly key: string; -} = { - host: process.env.SUPABASE_TELEMETRY_POSTHOG_HOST ?? DEFAULT_HOST, - key: process.env.SUPABASE_TELEMETRY_POSTHOG_KEY ?? DEFAULT_KEY, -}; + readonly key: Option.Option; +} + +function nonEmptyString(value: string | undefined): Option.Option { + return value === undefined || value === "" ? Option.none() : Option.some(value); +} + +function readNonEmptyEnv( + env: Readonly>, + key: string, +): Option.Option { + return nonEmptyString(env[key]); +} + +function shippedPosthogHost(): Option.Option { + return nonEmptyString(process.env.SUPABASE_CLI_POSTHOG_HOST); +} + +function shippedPosthogKey(): Option.Option { + return nonEmptyString(process.env.SUPABASE_CLI_POSTHOG_KEY); +} + +export function resolvePosthogConfig( + env: Readonly>, +): PosthogConfig { + return { + host: readNonEmptyEnv(env, "SUPABASE_TELEMETRY_POSTHOG_HOST").pipe( + Option.orElse(shippedPosthogHost), + Option.getOrElse(() => DEFAULT_HOST), + ), + key: readNonEmptyEnv(env, "SUPABASE_TELEMETRY_POSTHOG_KEY").pipe( + Option.orElse(shippedPosthogKey), + ), + }; +} diff --git a/apps/cli/src/shared/telemetry/posthog-config.unit.test.ts b/apps/cli/src/shared/telemetry/posthog-config.unit.test.ts new file mode 100644 index 0000000000..8428d27c59 --- /dev/null +++ b/apps/cli/src/shared/telemetry/posthog-config.unit.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Option } from "effect"; +import { processEnvLayer } from "../../../tests/helpers/mocks.ts"; +import { resolvePosthogConfig } from "./posthog-config.ts"; + +describe("resolvePosthogConfig", () => { + it.live("uses no key when nothing is injected or overridden", () => + Effect.sync(() => { + const config = resolvePosthogConfig({}); + + expect(config.host).toBe("https://eu.i.posthog.com"); + expect(Option.isNone(config.key)).toBe(true); + }).pipe(Effect.provide(processEnvLayer())), + ); + + it.live("uses the build-injected key and host by default", () => + Effect.sync(() => { + const config = resolvePosthogConfig({}); + + expect(config.host).toBe("https://build-posthog.example"); + expect(config.key).toEqual(Option.some("phc_build_key")); + }).pipe( + Effect.provide( + processEnvLayer({ + SUPABASE_CLI_POSTHOG_HOST: "https://build-posthog.example", + SUPABASE_CLI_POSTHOG_KEY: "phc_build_key", + }), + ), + ), + ); + + it.live("prefers runtime overrides over build-injected values", () => + Effect.sync(() => { + const config = resolvePosthogConfig({ + SUPABASE_TELEMETRY_POSTHOG_HOST: "https://runtime-posthog.example", + SUPABASE_TELEMETRY_POSTHOG_KEY: "phc_runtime_key", + }); + + expect(config.host).toBe("https://runtime-posthog.example"); + expect(config.key).toEqual(Option.some("phc_runtime_key")); + }).pipe( + Effect.provide( + processEnvLayer({ + SUPABASE_CLI_POSTHOG_HOST: "https://build-posthog.example", + SUPABASE_CLI_POSTHOG_KEY: "phc_build_key", + }), + ), + ), + ); +}); diff --git a/apps/cli/src/shared/telemetry/runtime.layer.ts b/apps/cli/src/shared/telemetry/runtime.layer.ts index 0728b847ac..59f2690e6d 100644 --- a/apps/cli/src/shared/telemetry/runtime.layer.ts +++ b/apps/cli/src/shared/telemetry/runtime.layer.ts @@ -11,12 +11,12 @@ import { TelemetryRuntime } from "./runtime.service.ts"; const CI_ENV_VARS = ["CI", "GITHUB_ACTIONS", "GITLAB_CI", "CIRCLECI", "JENKINS_URL", "BUILDKITE"]; -function identityFromConfig(config: TelemetryConfig | null) { - if (config !== null) { +function identityFromConfig(config: Option.Option) { + if (Option.isSome(config)) { return { - deviceId: config.device_id, - sessionId: config.session_id, - distinctId: config.distinct_id, + deviceId: config.value.device_id, + sessionId: config.value.session_id, + distinctId: config.value.distinct_id, isFirstRun: false, } as const; } @@ -45,7 +45,7 @@ export const telemetryRuntimeLayer = Layer.effect( let identity; if (consent === "granted") { - if (config === null && isTty) { + if (Option.isNone(config) && isTty) { yield* Effect.sync(() => note( "Supabase collects anonymous usage data to improve the CLI.\nYou can opt out at any time:\n\n supabase telemetry disable\n\nLearn more: https://supabase.com/docs/guides/local-development/cli/getting-started#telemetry", diff --git a/apps/cli/src/shared/telemetry/runtime.layer.unit.test.ts b/apps/cli/src/shared/telemetry/runtime.layer.unit.test.ts index c5fa963685..580cd9fb4b 100644 --- a/apps/cli/src/shared/telemetry/runtime.layer.unit.test.ts +++ b/apps/cli/src/shared/telemetry/runtime.layer.unit.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "@effect/vitest"; import { BunServices } from "@effect/platform-bun"; -import { existsSync, mkdtempSync, rmSync } from "node:fs"; +import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { Effect, Layer } from "effect"; @@ -79,4 +79,36 @@ describe("telemetryRuntimeLayer", () => { Effect.ensuring(Effect.sync(() => rmSync(homeDir, { recursive: true, force: true }))), ); }); + + it.live("treats a malformed telemetry.json as a fresh first run instead of crashing", () => { + const homeDir = makeTempDir(); + const configPath = path.join(homeDir, "telemetry.json"); + writeFileSync(configPath, ""); + + return Effect.gen(function* () { + const runtime = yield* TelemetryRuntime; + expect(runtime.consent).toBe("granted"); + expect(runtime.isFirstRun).toBe(true); + expect(existsSync(configPath)).toBe(true); + }).pipe( + Effect.provide(buildLayer({ homeDir })), + Effect.ensuring(Effect.sync(() => rmSync(homeDir, { recursive: true, force: true }))), + ); + }); + + it.live("silently ignores structurally invalid telemetry.json instead of crashing", () => { + const homeDir = makeTempDir(); + const configPath = path.join(homeDir, "telemetry.json"); + writeFileSync(configPath, JSON.stringify({ consent: "granted" })); + + return Effect.gen(function* () { + const runtime = yield* TelemetryRuntime; + expect(runtime.consent).toBe("granted"); + expect(runtime.isFirstRun).toBe(true); + expect(existsSync(configPath)).toBe(true); + }).pipe( + Effect.provide(buildLayer({ homeDir })), + Effect.ensuring(Effect.sync(() => rmSync(homeDir, { recursive: true, force: true }))), + ); + }); }); diff --git a/apps/cli/src/shared/telemetry/types.ts b/apps/cli/src/shared/telemetry/types.ts index eeb12e0e02..44d1823536 100644 --- a/apps/cli/src/shared/telemetry/types.ts +++ b/apps/cli/src/shared/telemetry/types.ts @@ -1,9 +1,13 @@ -export type ConsentState = "granted" | "denied"; +import { Schema } from "effect"; -export type TelemetryConfig = { - consent: ConsentState; - device_id: string; - session_id: string; - session_last_active: number; - distinct_id?: string; -}; +const ConsentStateSchema = Schema.Literals(["granted", "denied"] as const); +export type ConsentState = Schema.Schema.Type; + +export const TelemetryConfigSchema = Schema.Struct({ + consent: ConsentStateSchema, + device_id: Schema.String, + session_id: Schema.String, + session_last_active: Schema.Number, + distinct_id: Schema.optionalKey(Schema.String), +}); +export type TelemetryConfig = Schema.Schema.Type; diff --git a/apps/cli/tests/helpers/running-stack.ts b/apps/cli/tests/helpers/running-stack.ts index aa08313a21..261c512eee 100644 --- a/apps/cli/tests/helpers/running-stack.ts +++ b/apps/cli/tests/helpers/running-stack.ts @@ -357,7 +357,7 @@ export async function makeStackFixture( dashboardUrl: "https://supabase.com/dashboard", projectHost: "supabase.co", telemetryPosthogHost: "https://us.i.posthog.com", - telemetryPosthogKey: "phc_test_key", + telemetryPosthogKey: Option.some("phc_test_key"), accessToken: Option.none(), noKeyring: Option.none(), supabaseHome: homeDir, diff --git a/packages/cli-test-helpers/src/normalize.ts b/packages/cli-test-helpers/src/normalize.ts index 12435a0dba..8851844b6e 100644 --- a/packages/cli-test-helpers/src/normalize.ts +++ b/packages/cli-test-helpers/src/normalize.ts @@ -78,6 +78,13 @@ export function normalize(output: string): string { // The TS port intentionally doesn't reconstruct these — strip the // frame block plus the trailing blank line so parity comparisons ignore them. .replace(/(?:^ \(0xADDR\)\n\t[^\n]+\n)+\n?/gm, "") + // 12c. A go-errors frame glued to a preceding prompt on the same line, e.g. + // `Enter a new root key: (0xADDR)\n\t: …`. Rule 12b + // only strips frames that begin at line start, so when a command writes + // a prompt to stderr without a trailing newline (`encryption update-root-key`), + // the first frame stays glued to the prompt and survives. Strip that + // residual frame too, leaving just the prompt text. + .replace(/ \(0xADDR\)\n\t[^\n]+\n/g, "") // 13. Node/Bun stack trace lines (one or more consecutive " at …" lines) .replace(/(?:^[ \t]+at [^\n]+\n?)+/gm, "\n") // 14. File reference line numbers (file.ts:123 or file.ts:123:45)