From 856339ab790f61c85599044377ec7e47d85b0f7f Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Mon, 1 Jun 2026 12:09:54 +0100 Subject: [PATCH 1/8] fix(cli): suppress spinner on stdout for legacy -o machine formats (#5410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What changed `supabase branches list -o json` (and any legacy command run with `-o json|yaml|toml|env`) wrote a clack progress-spinner ANSI sequence — the hide-cursor `\x1b[?25l` plus a spinner frame — to **stdout** ahead of the machine payload, so `JSON.parse` of the captured output threw. Regressed in v2.102.0 when `branches` was ported to TypeScript. ### Why it happened The legacy shell has two independent output-format flags: - `--output-format` (TS-native) — drives **output-layer selection**. - `-o`/`--output` (Go-compat: `env|pretty|json|toml|yaml`) — consumed inside handlers. `legacy/cli/root.ts` selected the output layer using only `--output-format`, ignoring `-o`. So `-o json` (with no `--output-format`) left `output.format === "text"` and the spinner-emitting `textOutputLayer` active. The handler's `output.task(...)` gate (`output.format === "text"`) was `true`, so clack rendered a spinner to stdout, and the Go JSON encoder (`output.raw(encodeGoJson(...))`) appended the JSON — corrupting it. This affected every legacy command that wraps API calls in `output.task` (~32 commands), not just `branches list`. ### The fix Added a legacy-only `legacyQuietProgressTextOutputLayer` (`legacy/output/`) that wraps the shared `textOutputLayer` and no-ops **only** `task`/`progress`. `legacy/cli/root.ts` selects it for any Go machine format (`-o json|yaml|toml|env`). Because the text layer stays active, everything else delegates unchanged: - errors still render as red text on **stderr** (`withJsonErrorHandling` re-fails on `format === "text"`), - the handler still emits its Go-byte-exact payload via `output.raw` (checked before the `output.format` branch). So stdout output is **byte-identical to before, minus the spinner** — Go output parity is preserved exactly. No `shared/` or `next/` changes, so the `next/` shell's text rendering is untouched. Also documents the `-o` vs `--output-format` layer-selection invariant in `apps/cli/AGENTS.md`. Fixes #5397 --- apps/cli/AGENTS.md | 12 ++ apps/cli/src/legacy/cli/root.ts | 14 ++- ...legacy-quiet-progress-text-output.layer.ts | 45 +++++++ ...et-progress-text-output.layer.unit.test.ts | 116 ++++++++++++++++++ 4 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 apps/cli/src/legacy/output/legacy-quiet-progress-text-output.layer.ts create mode 100644 apps/cli/src/legacy/output/legacy-quiet-progress-text-output.layer.unit.test.ts 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/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/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)), + ); +}); From 94882bc07f473e049795efb909a2a7a93c538465 Mon Sep 17 00:00:00 2001 From: Vaibhav <117663341+7ttp@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:57:30 +0530 Subject: [PATCH 2/8] feat(cli): port postgres config (#5404) ## TL;DR ports `supabase postgres-config get`, `update`, and `delete` from the legacy go proxy to ts ## What's Introduced here This replaces the `LegacyGoProxy` path for `postgres-config` with native handlers in the existing legacy cli flow keeps the current behavior for reading config, handling repeated `--config key=value` flags, and running `delete` through the same `get` then `put` flow cause `update` can accept arbitrary config keys, it uses raw HTTP here instead of the generated typed client..... and also added tests around this! :D ## ref: closes CLI-1298 --------- Co-authored-by: Colum Ferry --- apps/cli/docs/go-cli-porting-status.md | 6 +- .../postgres-config/delete/SIDE_EFFECTS.md | 89 ++- .../postgres-config/delete/delete.command.ts | 12 +- .../postgres-config/delete/delete.handler.ts | 65 +- .../postgres-config/get/SIDE_EFFECTS.md | 75 +- .../postgres-config/get/get.command.ts | 12 +- .../postgres-config/get/get.handler.ts | 34 +- .../postgres-config/postgres-config.errors.ts | 87 +++ .../postgres-config.integration.test.ts | 639 ++++++++++++++++++ .../postgres-config/postgres-config.shared.ts | 276 ++++++++ .../postgres-config/update/SIDE_EFFECTS.md | 93 ++- .../postgres-config/update/update.command.ts | 12 +- .../postgres-config/update/update.handler.ts | 83 ++- 13 files changed, 1381 insertions(+), 102 deletions(-) create mode 100644 apps/cli/src/legacy/commands/postgres-config/postgres-config.errors.ts create mode 100644 apps/cli/src/legacy/commands/postgres-config/postgres-config.integration.test.ts create mode 100644 apps/cli/src/legacy/commands/postgres-config/postgres-config.shared.ts diff --git a/apps/cli/docs/go-cli-porting-status.md b/apps/cli/docs/go-cli-porting-status.md index 5cdd1aa2fa..4c26e200a9 100644 --- a/apps/cli/docs/go-cli-porting-status.md +++ b/apps/cli/docs/go-cli-porting-status.md @@ -258,9 +258,9 @@ Legend: | `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) | | `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/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)); }); From 50908ba28396ade33cd57eedfbb95cca058b8f92 Mon Sep 17 00:00:00 2001 From: Vaibhav <117663341+7ttp@users.noreply.github.com> Date: Mon, 1 Jun 2026 17:32:01 +0530 Subject: [PATCH 3/8] fix(cli): telemetry json parse crash (#5405) fixes `supabase start` crashing with `JSON Parse error: Unexpected EOF` from a malformed `telemetry.json` by recovering and regenerating unreadable telemetry state - closes https://github.com/supabase/cli/issues/5395 --------- Co-authored-by: Colum Ferry --- apps/cli-e2e/src/tests/telemetry.e2e.test.ts | 9 ++++-- apps/cli/src/shared/telemetry/consent.ts | 19 ++++++++---- .../src/shared/telemetry/consent.unit.test.ts | 29 ++++++++++++++++++- .../telemetry/runtime.layer.unit.test.ts | 18 +++++++++++- 4 files changed, 64 insertions(+), 11 deletions(-) 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/src/shared/telemetry/consent.ts b/apps/cli/src/shared/telemetry/consent.ts index b3114ae489..3efe7f4582 100644 --- a/apps/cli/src/shared/telemetry/consent.ts +++ b/apps/cli/src/shared/telemetry/consent.ts @@ -4,6 +4,14 @@ import type { ConsentState, TelemetryConfig } from "./types.ts"; export const getConfigDir = CliConfig.useSync((cliConfig) => cliConfig.supabaseHome); +function parseTelemetryConfig(content: string): TelemetryConfig | null { + try { + return JSON.parse(content) as TelemetryConfig; + } catch { + return null; + } +} + export const readTelemetryConfig = Effect.fnUntraced( function* (configDir: string) { const fs = yield* FileSystem.FileSystem; @@ -12,7 +20,7 @@ export const readTelemetryConfig = Effect.fnUntraced( const exists = yield* fs.exists(configPath); if (!exists) return null; const content = yield* fs.readFileString(configPath); - return JSON.parse(content) as TelemetryConfig; + return parseTelemetryConfig(content); }, (effect) => Effect.orElseSucceed(effect, () => null), ); @@ -24,11 +32,10 @@ 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, JSON.stringify(config, null, 2), { mode: 0o600 }); + yield* fs.rename(tmpPath, configPath); }, Effect.orDie); export const getEffectiveConsent = Effect.fnUntraced(function* (config: TelemetryConfig | null) { diff --git a/apps/cli/src/shared/telemetry/consent.unit.test.ts b/apps/cli/src/shared/telemetry/consent.unit.test.ts index bf0391678a..1ab9dbe5b3 100644 --- a/apps/cli/src/shared/telemetry/consent.unit.test.ts +++ b/apps/cli/src/shared/telemetry/consent.unit.test.ts @@ -1,4 +1,8 @@ import { describe, expect, it } from "@effect/vitest"; +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 } from "effect"; import { cliConfigLayer } from "../../next/config/cli-config.layer.ts"; import { @@ -6,7 +10,7 @@ import { 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,6 +44,14 @@ 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* () { @@ -90,3 +102,18 @@ describe("getEffectiveConsent", () => { }).pipe(Effect.provide(emptyEnv())), ); }); + +describe("readTelemetryConfig", () => { + it.live("returns null for malformed JSON instead of throwing", () => { + const dir = makeTempDir(); + writeTelemetryFile(dir, ""); + + return Effect.gen(function* () { + const config = yield* readTelemetryConfig(dir); + expect(config).toBeNull(); + }).pipe( + Effect.provide(BunServices.layer), + Effect.ensuring(Effect.sync(() => rmSync(dir, { recursive: true, force: true }))), + ); + }); +}); 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..1e69610853 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,20 @@ 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 }))), + ); + }); }); From 935e578ca0f45b4c74f3e2db5f670447cb0f13f0 Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Mon, 1 Jun 2026 20:56:19 +0800 Subject: [PATCH 4/8] fix(cli): route TS telemetry to production PostHog (#5411) ## Summary Both TypeScript CLI shells defaulted their PostHog write key to a hard-coded staging-project key, so released binaries sent `cli_command_executed` (and other CLI events) to the staging PostHog project instead of production. As commands were ported from the Go binary to native TypeScript handlers, their telemetry silently moved off production. This injects the PostHog key at build time from the existing `POSTHOG_API_KEY` release secret (the same one the Go binary already consumes via `-ldflags`), so released TS binaries report to production. Outside a release build the key is empty and telemetry no-ops, mirroring the Go binary. ## Changes - `posthog-config.ts` (legacy shell) and `cli-config.layer.ts` (next shell): the PostHog key default now reads a build-injected `process.env.SUPABASE_CLI_POSTHOG_KEY`, empty when not injected. The runtime `SUPABASE_TELEMETRY_POSTHOG_KEY` override is unchanged. - `build.ts`: inject the key via `bun build --define` on both the standard and musl compile paths, sourced from `POSTHOG_API_KEY` (already present in the release build env). - `analytics.layer.ts` and `legacy-analytics.layer.ts`: return a no-op analytics client when the key is empty, so local and source builds emit nothing. - Updated the cli-config unit test to expect the empty no-op default. ## Linear - fixes GROWTH-895 --------- Co-authored-by: Julien Goux --- apps/cli/scripts/build.ts | 8 ++- .../telemetry/legacy-analytics.layer.ts | 7 +-- .../next/auth/platform-api.layer.unit.test.ts | 2 +- .../functions/list/list.integration.test.ts | 2 +- .../functions/new/new.integration.test.ts | 2 +- apps/cli/src/next/config/cli-config.layer.ts | 14 ++---- .../next/config/cli-config.layer.unit.test.ts | 27 ++++++++-- .../cli/src/next/config/cli-config.service.ts | 2 +- .../src/shared/telemetry/analytics.layer.ts | 5 +- .../src/shared/telemetry/posthog-config.ts | 47 +++++++++++++---- .../telemetry/posthog-config.unit.test.ts | 50 +++++++++++++++++++ apps/cli/tests/helpers/running-stack.ts | 2 +- 12 files changed, 133 insertions(+), 35 deletions(-) create mode 100644 apps/cli/src/shared/telemetry/posthog-config.unit.test.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/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/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/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/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, From 88be432434af7eda91f39a91aa98462717c0e25f Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Mon, 1 Jun 2026 15:04:42 +0200 Subject: [PATCH 5/8] fix(cli): schema-decode telemetry config (#5412) ## Summary Telemetry config loading now uses an Effect Schema boundary instead of a raw `JSON.parse` cast. Invalid, unreadable, or malformed `telemetry.json` is treated as absent local telemetry state, so telemetry state cannot crash unrelated CLI commands. The telemetry config type is now derived from the runtime schema, and internal absence is represented with `Option` rather than `null`. `telemetry status` still serializes missing persisted consent as `null` at the output boundary. ## Context PR #5405 fixed the immediate empty JSON crash by catching parse errors, but structurally invalid JSON could still be accepted as `TelemetryConfig`. This follows the existing repo pattern of `Schema.fromJsonString(...)` for local state files and keeps malformed telemetry state best-effort and silent. --- .../commands/telemetry/telemetry.command.ts | 7 ++- apps/cli/src/shared/telemetry/consent.ts | 40 +++++++++------ .../src/shared/telemetry/consent.unit.test.ts | 49 ++++++++++++++----- apps/cli/src/shared/telemetry/identity.ts | 25 ++++++---- .../cli/src/shared/telemetry/runtime.layer.ts | 12 ++--- .../telemetry/runtime.layer.unit.test.ts | 16 ++++++ apps/cli/src/shared/telemetry/types.ts | 20 +++++--- 7 files changed, 117 insertions(+), 52 deletions(-) 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/shared/telemetry/consent.ts b/apps/cli/src/shared/telemetry/consent.ts index 3efe7f4582..1f1bd235f1 100644 --- a/apps/cli/src/shared/telemetry/consent.ts +++ b/apps/cli/src/shared/telemetry/consent.ts @@ -1,15 +1,15 @@ -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); -function parseTelemetryConfig(content: string): TelemetryConfig | null { - try { - return JSON.parse(content) as TelemetryConfig; - } catch { - return null; - } +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( @@ -18,11 +18,12 @@ export const readTelemetryConfig = Effect.fnUntraced( 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 parseTelemetryConfig(content); + 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* ( @@ -34,19 +35,26 @@ export const writeTelemetryConfig = Effect.fnUntraced(function* ( yield* fs.makeDirectory(configDir, { recursive: true, mode: 0o700 }); const configPath = path.join(configDir, "telemetry.json"); const tmpPath = `${configPath}.tmp.${Date.now()}`; - yield* fs.writeFileString(tmpPath, JSON.stringify(config, null, 2), { mode: 0o600 }); + 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 1ab9dbe5b3..adb4d63d30 100644 --- a/apps/cli/src/shared/telemetry/consent.unit.test.ts +++ b/apps/cli/src/shared/telemetry/consent.unit.test.ts @@ -3,7 +3,7 @@ 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 } from "effect"; +import { Effect, Layer, Option } from "effect"; import { cliConfigLayer } from "../../next/config/cli-config.layer.ts"; import { mockProjectContext, @@ -55,62 +55,89 @@ function writeTelemetryFile(dir: string, content: string): void { 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("returns null for malformed JSON instead of throwing", () => { + 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).toBeNull(); + 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/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 1e69610853..580cd9fb4b 100644 --- a/apps/cli/src/shared/telemetry/runtime.layer.unit.test.ts +++ b/apps/cli/src/shared/telemetry/runtime.layer.unit.test.ts @@ -95,4 +95,20 @@ describe("telemetryRuntimeLayer", () => { 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; From 6410a038df5fc61602efb85a5025925c8925e4f6 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Mon, 1 Jun 2026 14:09:28 +0100 Subject: [PATCH 6/8] feat(cli): port encryption commands to native TypeScript (#5409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promotes `supabase encryption get-root-key` and `encryption update-root-key` from Go-proxy handlers to native Phase 1+ TypeScript Effect handlers in the legacy shell. The command is still exposed in the Go CLI (`cmd/encryption.go`, `GroupID: groupManagementAPI`), so this is an in-scope strict 1:1 port. Closes CLI-1300. ## What changed - **`get-root-key`** — resolves the project ref, calls `GET /v1/projects/{ref}/pgsodium`, and prints the bare root key + `\n` to **stdout** (Go `fmt.Println` parity). JSON/stream-json modes emit a structured `result`. - **`update-root-key`** — reads the new key from stdin (masked clack prompt on a TTY, trimmed piped bytes otherwise; empty/whitespace → `""`, matching Go's `io.Copy` + `strings.TrimSpace`), calls `PUT /v1/projects/{ref}/pgsodium`, and prints `Finished supabase root-key update.` to **stderr** (Go's `utils.Aqua` rendered as plain text per the legacy convention). - **`encryption.errors.ts`** — new family-root error module with `mapLegacyEncryptionHttpError({ networkVerb, statusVerb })` over the shared `mapLegacyHttpError`. Go uses different verbs for the network vs status message of each subcommand, so the factory takes both. - **`SIDE_EFFECTS.md`** — consolidates the two per-subcommand docs into one group-level file and **corrects the API route**: the old proxy docs listed `/v1/projects/{ref}/config/database/vault`; the real route is `/v1/projects/{ref}/pgsodium`. - **Telemetry** — both commands wrap with `withLegacyCommandInstrumentation`; `--project-ref` is intentionally **not** telemetry-safe here (no `markFlagTelemetrySafe` on the Go `encryption` group), so it is redacted. PersistentPostRun parity (linked-project cache + telemetry flush on success **and** failure) via the `Effect.ensuring` pair. - **Status tracker** — both subcommands flipped `wrapped` → `ported`. ## Reviewer notes - `update-root-key` is the first legacy command to wire the `Stdin` service. Because `Layer.provide` does not share to siblings inside `Layer.mergeAll`, the command composes `stdinLayer` with its `Tty` + `Stdio` deps explicitly so the layer is self-contained. Verified against the bundled binary (`./dist/supabase-legacy …`) to rule out a `Service not found` panic that in-process tests miss. - Known divergence (documented in `SIDE_EFFECTS.md`): the TTY masked prompt uses clack's framing rather than Go's bare `Enter a new root key: ` stderr write; the label text matches. Piped mode reads stdin directly without printing the prompt, mirroring Go's `io.Copy` branch. --- apps/cli/docs/go-cli-porting-status.md | 4 +- .../commands/encryption/SIDE_EFFECTS.md | 88 +++++++++ .../commands/encryption/encryption.command.ts | 2 +- .../encryption/encryption.e2e.test.ts | 39 ++++ .../commands/encryption/encryption.errors.ts | 44 +++++ .../encryption/get-root-key/SIDE_EFFECTS.md | 57 ------ .../get-root-key/get-root-key.command.ts | 15 +- .../get-root-key/get-root-key.handler.ts | 44 ++++- .../get-root-key.integration.test.ts | 153 ++++++++++++++ .../update-root-key/SIDE_EFFECTS.md | 57 ------ .../update-root-key.command.ts | 27 ++- .../update-root-key.handler.ts | 65 +++++- .../update-root-key.integration.test.ts | 187 ++++++++++++++++++ packages/cli-test-helpers/src/normalize.ts | 7 + 14 files changed, 657 insertions(+), 132 deletions(-) create mode 100644 apps/cli/src/legacy/commands/encryption/SIDE_EFFECTS.md create mode 100644 apps/cli/src/legacy/commands/encryption/encryption.e2e.test.ts create mode 100644 apps/cli/src/legacy/commands/encryption/encryption.errors.ts delete mode 100644 apps/cli/src/legacy/commands/encryption/get-root-key/SIDE_EFFECTS.md create mode 100644 apps/cli/src/legacy/commands/encryption/get-root-key/get-root-key.integration.test.ts delete mode 100644 apps/cli/src/legacy/commands/encryption/update-root-key/SIDE_EFFECTS.md create mode 100644 apps/cli/src/legacy/commands/encryption/update-root-key/update-root-key.integration.test.ts diff --git a/apps/cli/docs/go-cli-porting-status.md b/apps/cli/docs/go-cli-porting-status.md index 4c26e200a9..f17de55366 100644 --- a/apps/cli/docs/go-cli-porting-status.md +++ b/apps/cli/docs/go-cli-porting-status.md @@ -254,8 +254,8 @@ 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` | `ported` | [`../src/legacy/commands/postgres-config/get/get.command.ts`](../src/legacy/commands/postgres-config/get/get.command.ts) | 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/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) From 8603361473f223f865ad545208780f6713a294ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 15:42:45 +0200 Subject: [PATCH 7/8] fix(deps): bump the go_modules group across 1 directory with 9 updates (#5398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the go_modules group with 6 updates in the /apps/cli-go directory: | Package | From | To | | --- | --- | --- | | [github.com/docker/cli](https://github.com/docker/cli) | `28.5.2+incompatible` | `29.2.0+incompatible` | | [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) | `5.17.2` | `5.19.1` | | [github.com/containerd/containerd/v2](https://github.com/containerd/containerd) | `2.1.5` | `2.2.4` | | [github.com/in-toto/in-toto-golang](https://github.com/in-toto/in-toto-golang) | `0.9.0` | `0.11.0` | | [github.com/moby/buildkit](https://github.com/moby/buildkit) | `0.25.1` | `0.28.1` | | [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp](https://github.com/open-telemetry/opentelemetry-go) | `1.39.0` | `1.43.0` | Updates `github.com/docker/cli` from 28.5.2+incompatible to 29.2.0+incompatible
Commits
  • 0b9d198 Merge pull request #6764 from vvoland/update-docker
  • 9c9ec73 vendor: github.com/moby/moby/client v0.2.2
  • bab3e81 vendor: github.com/moby/moby/api v1.53.0
  • 2e64fc1 Merge pull request #6367 from thaJeztah/template_slicejoin
  • 1f2ba2a Merge pull request #6760 from thaJeztah/container_create_fix_error
  • e34a342 templates: make "join" work with non-string slices and map values
  • a86356d Merge pull request #6763 from thaJeztah/bump_mapstructure
  • 771660a vendor: github.com/go-viper/mapstructure/v2 v2.5.0
  • 9cff36b Merge pull request #6762 from thaJeztah/bump_x_deps
  • 08ed2bc cli/command/container: make injecting config.json failures a warning
  • Additional commits viewable in compare view

Updates `github.com/go-git/go-git/v5` from 5.17.2 to 5.19.1
Release notes

Sourced from github.com/go-git/go-git/v5's releases.

v5.19.1

What's Changed

Full Changelog: https://github.com/go-git/go-git/compare/v5.19.0...v5.19.1

v5.19.0

What's Changed

Full Changelog: https://github.com/go-git/go-git/compare/v5.18.0...v5.19.0

v5.18.0

What's Changed

Full Changelog: https://github.com/go-git/go-git/compare/v5.17.2...v5.18.0

Commits
  • 3c3be60 Merge pull request #2137 from go-git/validate-v5
  • 3fba897 plumbing: format/packfile, cap delta chain depth in parser
  • a97d660 Merge pull request #2125 from hiddeco/v5/format-input-bounds
  • aeaa125 plumbing: format/objfile, require Header before Read
  • 1f38e17 plumbing: format/packfile, bound inflate size
  • f7545a0 plumbing: format/idxfile, bound nr by file size
  • 170b881 Merge pull request #2116 from pjbgf/symlink-v5
  • 7b6d994 Merge pull request #2117 from hiddeco/v5/worktree-fs-mkdirall-root-noop
  • f0709b3 git: Stop validating symlink target paths
  • 776d00f git: Allow MkdirAll on worktree-root paths
  • Additional commits viewable in compare view

Updates `github.com/containerd/containerd/v2` from 2.1.5 to 2.2.4
Release notes

Sourced from github.com/containerd/containerd/v2's releases.

containerd 2.2.4

Welcome to the v2.2.4 release of containerd!

The fourth patch release for containerd 2.2 contains various fixes and updates including security patches.

  • containerd

  • go-jose

  • Use mount manager during image volume processing to support snapshotters that require writable block volumes (e.g., EROFS) (#13242)

  • Fix handling of out-of-range USER values in OCI spec to avoid unexpected username/group lookups (#13448)

  • Apply hardening to block AF_ALG in default socket policy (#13408)

  • Fix bugs in sandbox service affecting sandbox creation configuration and event publishing (#13266)

  • Set AppArmor abi conditionally to support versions < 3.0 (#13275)

  • Disable overlay "rebase" capability when running in a user namespace to fix layer extraction failures (#13393)

  • Support both "volatile" and "fsync=volatile" mount options for volatile snapshotter (#13296)

Please try out the release binaries and report any issues at https://github.com/containerd/containerd/issues.

  • Wei Fu
  • Akihiro Suda
  • Chris Henzie
  • Paweł Gronowski
  • Samuel Karp
  • Brian Goff
  • Champ-Goblem
  • Chris Chang
  • LEI WANG
  • Phil Estes
  • William Myers
  • oci: return explicit error for out-of-range USER values (#13448)
    • d20c6267b oci: return explicit error for out-of-range USER values
  • seccomp: Block AF_ALG in default socket policy (#13408)
    • db34dc4b4 seccomp: Block AF_ALG in default socket policy
    • 214b141ee seccomp: Document socket rule scope and socketcall limitation
  • update Go to 1.25.10, 1.26.3 (#13375)
  • overlay: disable "rebase" capability when running in UserNS (#13393)
    • 63874d262 overlay: disable "rebase" capability when running in UserNS
  • Support both styles of volatile mount option (#13296)

... (truncated)

Commits
  • 193637f Merge pull request #13457 from samuelkarp/prepare-release-2.2.4
  • 05e97b4 Prepare release notes for v2.2.4
  • 0a8f65b Merge pull request #13448 from samuelkarp/oci-withuser-errrange-2.2
  • 7287de2 Merge pull request #13408 from k8s-infra-cherrypick-robot/cherry-pick-13327-t...
  • db34dc4 seccomp: Block AF_ALG in default socket policy
  • 214b141 seccomp: Document socket rule scope and socketcall limitation
  • 105ca97 Merge pull request #13375 from AkihiroSuda/release-2.2-go-1.25.10
  • ba7b585 Merge pull request #13393 from k8s-infra-cherrypick-robot/cherry-pick-13389-t...
  • 63874d2 overlay: disable "rebase" capability when running in UserNS
  • c2b1856 update Go to 1.25.10, 1.26.3
  • Additional commits viewable in compare view

Updates `github.com/go-git/go-billy/v5` from 5.8.0 to 5.9.0
Release notes

Sourced from github.com/go-git/go-billy/v5's releases.

v5.9.0

What's Changed

Full Changelog: https://github.com/go-git/go-billy/compare/v5.8.0...v5.9.0

Commits
  • 237e529 Merge pull request #206 from pjbgf/v5-improvements
  • 04edb39 build: Add go-git integration test
  • d8efefd osfs: preserve empty ChrootOS base
  • 07f2a0b Merge pull request #205 from pjbgf/v5-improvements
  • 25207c8 build: Bump Go versions in workflows
  • 2fda229 osfs: ChrootOS eval baseDir on creation
  • 427b27f Merge pull request #203 from pjbgf/v5-improvements
  • 7d5a23e chroot: Reject symlink loops
  • 2c2287a util: avoid following symlinks in RemoveAll fallback
  • cbd88e9 Fix mount path handling
  • Additional commits viewable in compare view

Updates `github.com/in-toto/in-toto-golang` from 0.9.0 to 0.11.0
Release notes

Sourced from github.com/in-toto/in-toto-golang's releases.

v0.11.0

What's Changed

Full Changelog: https://github.com/in-toto/in-toto-golang/compare/v0.10.0...v0.11.0

v0.10.0

What's Changed

... (truncated)

Commits
  • 36d782f Merge pull request #462 from in-toto/fix-negation-character
  • 4a09e3b match: Replace ^ with ! for negation in character classes
  • c3302e8 Merge pull request #459 from in-toto/dependabot/go_modules/github.com/go-jose...
  • 016e87e chore(deps): bump github.com/go-jose/go-jose/v4 from 4.1.3 to 4.1.4
  • 5b9df76 Merge pull request #457 from in-toto/dependabot/go_modules/google.golang.org/...
  • 595b3fe chore(deps): bump google.golang.org/grpc from 1.79.1 to 1.79.3
  • e396d24 Merge pull request #452 from in-toto/dependabot/github_actions/all-502588e1ca
  • 142b779 Merge pull request #453 from in-toto/dependabot/go_modules/all-d8ef5820aa
  • f741bcc chore(deps): bump the all group with 2 updates
  • c374dc9 chore(deps): bump the all group across 1 directory with 2 updates
  • Additional commits viewable in compare view

Updates `github.com/moby/buildkit` from 0.25.1 to 0.28.1
Release notes

Sourced from github.com/moby/buildkit's releases.

v0.28.1

Welcome to the v0.28.1 release of buildkit!

Please try out the release binaries and report any issues at https://github.com/moby/buildkit/issues.

Contributors

  • Tõnis Tiigi
  • CrazyMax
  • Sebastiaan van Stijn

Notable Changes

  • Fix insufficient validation of Git URL #ref:subdir fragments that could allow access to restricted files outside the checked-out repository root. GHSA-4vrq-3vrq-g6gg
  • Fix a vulnerability where an untrusted custom frontend could cause files to be written outside the BuildKit state directory. GHSA-4c29-8rgm-jvjj
  • Fix a panic when processing invalid .dockerignore patterns during COPY. #6610 moby/patternmatcher#9

Dependency Changes

  • github.com/moby/patternmatcher v0.6.0 -> v0.6.1

Previous release can be found at v0.28.0

v0.28.0

buildkit 0.28.0

Welcome to the v0.28.0 release of buildkit!

Please try out the release binaries and report any issues at https://github.com/moby/buildkit/issues.

Contributors

  • Tõnis Tiigi
  • CrazyMax
  • Sebastiaan van Stijn
  • Jonathan A. Sternberg
  • Akihiro Suda
  • Amr Mahdi
  • Dan Duvall
  • David Karlsson
  • Jonas Geiler
  • Kevin L.
  • rsteube

... (truncated)

Commits
  • 45b038c git: normalize and validate subdir paths
  • f5462c2 git: harden ref arg handling
  • 71577a5 source: extract SafeFileName into shared pathutil package
  • df43783 source/http: use os.Root for saved file operations
  • 9ce6f62 source/http: sanitize downloaded filenames
  • 099cf80 executor: validate container IDs centrally
  • 2642113 Merge pull request #6610 from thaJeztah/0.28_backport_bump_patternmatcher
  • 802da78 vendor: github.com/moby/patternmatcher v0.6.1
  • 5245d86 Merge pull request #6551 from tonistiigi/v0.28-cherry-picks
  • 90ee5de vendor: update x/net to v0.51.0
  • Additional commits viewable in compare view

Updates `github.com/moby/spdystream` from 0.5.0 to 0.5.1
Release notes

Sourced from github.com/moby/spdystream's releases.

v0.5.1

What's Changed

Security

Fix memory amplification in SPDY frame parsing leads to denial of service (CVE-2026-35469 / GHSA-pc3f-x583-g7j2)

Changes

Full Changelog: https://github.com/moby/spdystream/compare/v0.5.0...v0.5.1

Commits
  • c59e5d7 Merge pull request #109 from thaJeztah/use_ioutil
  • 2fd0155 use ioutil.Discard for go1.13 compatibility
  • ef6121f Merge commit from fork
  • 241cec9 compare with signed Int for 32-bit Arm
  • 21c3864 Add options to customize limits
  • acf9b45 spdy: update godoc for MaxDataLength
  • eb63605 spdy: limit header-size and header-count
  • 2f21da4 spdy: fix header block byte accounting
  • 5976b66 spdy: enforce 24-bit frame length limits
  • cf0ec5d Guard against oversized SPDY frames
  • Additional commits viewable in compare view

Updates `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` from 1.39.0 to 1.43.0
Changelog

Sourced from go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp's changelog.

[1.43.0/0.65.0/0.19.0] 2026-04-02

Added

  • Add IsRandom and WithRandom on TraceFlags, and IsRandom on SpanContext in go.opentelemetry.io/otel/trace for W3C Trace Context Level 2 Random Trace ID Flag support. (#8012)
  • Add service detection with WithService in go.opentelemetry.io/otel/sdk/resource. (#7642)
  • Add DefaultWithContext and EnvironmentWithContext in go.opentelemetry.io/otel/sdk/resource to support plumbing context.Context through default and environment detectors. (#8051)
  • Support attributes with empty value (attribute.EMPTY) in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc. (#8038)
  • Support attributes with empty value (attribute.EMPTY) in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc. (#8038)
  • Support attributes with empty value (attribute.EMPTY) in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc. (#8038)
  • Support attributes with empty value (attribute.EMPTY) in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp. (#8038)
  • Support attributes with empty value (attribute.EMPTY) in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. (#8038)
  • Support attributes with empty value (attribute.EMPTY) in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp. (#8038)
  • Support attributes with empty value (attribute.EMPTY) in go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest. (#8038)
  • Add support for per-series start time tracking for cumulative metrics in go.opentelemetry.io/otel/sdk/metric. Set OTEL_GO_X_PER_SERIES_START_TIMESTAMPS=true to enable. (#8060)
  • Add WithCardinalityLimitSelector for metric reader for configuring cardinality limits specific to the instrument kind. (#7855)

Changed

  • Introduce the EMPTY Type in go.opentelemetry.io/otel/attribute to reflect that an empty value is now a valid value, with INVALID remaining as a deprecated alias of EMPTY. (#8038)
  • Improve slice handling in go.opentelemetry.io/otel/attribute to optimize short slice values with fixed-size fast paths. (#8039)
  • Improve performance of span metric recording in go.opentelemetry.io/otel/sdk/trace by returning early if self-observability is not enabled. (#8067)
  • Improve formatting of metric data diffs in go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest. (#8073)

Deprecated

  • Deprecate INVALID in go.opentelemetry.io/otel/attribute. Use EMPTY instead. (#8038)

Fixed

  • Return spec-compliant TraceIdRatioBased description. This is a breaking behavioral change, but it is necessary to make the implementation spec-compliant. (#8027)
  • Fix a race condition in go.opentelemetry.io/otel/sdk/metric where the lastvalue aggregation could collect the value 0 even when no zero-value measurements were recorded. (#8056)
  • Limit HTTP response body to 4 MiB in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp to mitigate excessive memory usage caused by a misconfigured or malicious server. Responses exceeding the limit are treated as non-retryable errors. (#8108)
  • Limit HTTP response body to 4 MiB in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp to mitigate excessive memory usage caused by a misconfigured or malicious server. Responses exceeding the limit are treated as non-retryable errors. (#8108)
  • Limit HTTP response body to 4 MiB in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp to mitigate excessive memory usage caused by a misconfigured or malicious server. Responses exceeding the limit are treated as non-retryable errors. (#8108)
  • WithHostID detector in go.opentelemetry.io/otel/sdk/resource to use full path for kenv command on BSD. (#8113)
  • Fix missing request.GetBody in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp to correctly handle HTTP2 GOAWAY frame. (#8096)

[1.42.0/0.64.0/0.18.0/0.0.16] 2026-03-06

Added

  • Add go.opentelemetry.io/otel/semconv/v1.40.0 package. The package contains semantic conventions from the v1.40.0 version of the OpenTelemetry Semantic Conventions. See the migration documentation for information on how to upgrade from go.opentelemetry.io/otel/semconv/v1.39.0. (#7985)

... (truncated)

Commits
  • 9276201 Release v1.43.0 / v0.65.0 / v0.19.0 (#8128)
  • 61b8c94 chore(deps): update module github.com/mattn/go-runewidth to v0.0.22 (#8131)
  • 97a086e chore(deps): update github.com/golangci/dupl digest to c99c5cf (#8122)
  • 5e363de limit response body size for OTLP HTTP exporters (#8108)
  • 35214b6 Use an absolute path when calling bsd kenv (#8113)
  • 290024c fix(deps): update module google.golang.org/grpc to v1.80.0 (#8121)
  • e70658e fix: support getBody in otelploghttp (#8096)
  • 4afe468 fix(deps): update googleapis to 9d38bb4 (#8117)
  • b9ca729 chore(deps): update module github.com/go-git/go-git/v5 to v5.17.2 (#8115)
  • 69472ec chore(deps): update fossas/fossa-action action to v1.9.0 (#8118)
  • Additional commits viewable in compare view

Updates `go.opentelemetry.io/otel/sdk` from 1.40.0 to 1.44.0
Changelog

Sourced from go.opentelemetry.io/otel/sdk's changelog.

[1.44.0/0.66.0/0.20.0/0.0.17] 2026-05-27

Added

  • Add ByteSlice and ByteSliceValue functions for new BYTESLICE attribute type in go.opentelemetry.io/otel/attribute. (#7948)
  • Apply attribute value limit to the KindBytes attribute type in go.opentelemetry.io/otel/sdk/log. (#7990)
  • Apply attribute value limit to the BYTESLICE attribute type in go.opentelemetry.io/otel/sdk/trace. (#7990)
  • Support BYTESLICE attributes in go.opentelemetry.io/otel/trace. (#8153)
  • Support BYTESLICE attributes in go.opentelemetry.io/otel/exporters/otlp/otlptrace. (#8153)
  • Support BYTESLICE attributes in go.opentelemetry.io/otel/exporters/otlp/otlplog. (#8153)
  • Support BYTESLICE attributes in go.opentelemetry.io/otel/exporters/otlp/otlpmetric. (#8153)
  • Support BYTESLICE attributes in go.opentelemetry.io/otel/exporters/zipkin. (#8153)
  • Add String method for Value type in go.opentelemetry.io/otel/attribute. (#8142)
  • Add Slice and SliceValue functions for new SLICE attribute type in go.opentelemetry.io/otel/attribute. (#8166)
  • Support SLICE attributes in go.opentelemetry.io/otel/exporters/otlp/otlptrace. (#8216)
  • Support SLICE attributes in go.opentelemetry.io/otel/exporters/otlp/otlplog. (#8216)
  • Support SLICE attributes in go.opentelemetry.io/otel/exporters/otlp/otlpmetric. (#8216)
  • Support SLICE attributes in go.opentelemetry.io/otel/exporters/zipkin. (#8216)
  • Apply AttributeValueLengthLimit to attribute.SLICE type attribute values in go.opentelemetry.io/otel/sdk/trace, recursively truncating contained string values. (#8217)
  • Add Error field on Record type in go.opentelemetry.io/otel/log/logtest. (#8148)
  • Add WithMaxRequestSize option in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc. (#8157)
  • Add WithMaxRequestSize option in go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp. (#8157)
  • Add WithMaxRequestSize option in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc. (#8157)
  • Add WithMaxRequestSize option in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. (#8157)
  • Add WithMaxRequestSize option in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc. (#8157)
  • Add WithMaxRequestSize option in go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp. (#8157)
  • Add Settable to go.opentelemetry.io/otel/metric/x to allow reusing attribute options. (#8178)
  • Add experimental support for splitting metric data across multiple batches in go.opentelemetry.io/otel/sdk/metric. Set OTEL_GO_X_METRIC_EXPORT_BATCH_SIZE=<max_size> to enable for all periodic readers. See go.opentelemetry.io/otel/sdk/metric/internal/x for feature documentation. (#8071)
  • Add experimental self-observability metrics in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc. Enable with OTEL_GO_X_SELF_OBSERVABILITY=true environment variable. See go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x for feature documentation. (#8192)
  • Add experimental self-observability metrics in go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp. Enable with OTEL_GO_X_SELF_OBSERVABILITY=true environment variable. See go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/x for feature documentation. (#8194)
  • Add experimental self-observability metrics in go.opentelemetry.io/otel/exporters/stdout/stdoutlog. Enable with OTEL_GO_X_SELF_OBSERVABILITY=true environment variable. See go.opentelemetry.io/otel/stdout/stdoutlog/internal/x for feature documentation. (#8263)
  • Add WithDefaultAttributes to go.opentelemetry.io/otel/metric/x to support setting default attributes on instruments. (#8135)
  • Add go.opentelemetry.io/otel/semconv/v1.41.0 package. The package contains semantic conventions from the v1.41.0 version of the OpenTelemetry Semantic Conventions. See the migration documentation for information on how to upgrade from go.opentelemetry.io/otel/semconv/v1.40.0. (#8324)
  • Add Observable variants of instruments to go.opentelemetry.io/otel/semconv/v1.41.0 package. (#8350)
  • Generate explicit histogram bucket boundaries from weaver configuration for HTTP and RPC duration instruments in go.opentelemetry.io/otel/semconv/v1.41.0. (#8002)

Changed

  • ⚠️ Breaking Change: go.opentelemetry.io/otel/sdk/metric now applies a default cardinality limit of 2000 to comply with the Metrics SDK specification recommendation. New attribute sets are dropped when the cardinality limit is reached. The measurement of these sets are aggregated into a special attribute set containing attribute.Bool("otel.metric.overflow", true).

... (truncated)

Commits
  • b62d928 Release 1.44.0 (#8376)
  • 94132a0 chore(deps): update golang.org/x/telemetry digest to 5997936 (#8379)
  • 6fdcf82 feat: add self-observability metrics to otlpmetricgrpc metric exporters (#8192)
  • 761bbfc fix(deps): update golang.org/x (#8377)
  • 3a91dc6 fix(deps): update googleapis to 3dc84a4 (#8375)
  • f593185 exporters/otlp: default max request size to 64 MiB (#8365)
  • f02feac Merge commit from fork
  • 36c2f1b semconvkit: add invariant test for histogram-exclusion rule (#8370)
  • d0b6cbd sdk/metric: document unit-sensitivity of DefaultAggregationSelector (#8224)
  • 9a68034 add self observability for stdout exporter (#8263)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` ... _Description has been truncated_ --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Julien Goux --- apps/cli-go/go.mod | 162 ++++++------ apps/cli-go/go.sum | 382 ++++++++++++++++------------- apps/cli-go/internal/utils/git.go | 5 +- apps/cli-go/internal/utils/misc.go | 29 ++- 4 files changed, 326 insertions(+), 252 deletions(-) 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/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. From 1d1e7191268e068fe9cb6aba524d1c619a9c727b Mon Sep 17 00:00:00 2001 From: Vaibhav <117663341+7ttp@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:55:41 +0530 Subject: [PATCH 8/8] fix(cli): status gating (#5335) ## TL;DR fixes `supabase status -o json` so excluding postgrest no longer hides `api_url`, while `rest_url` and `graphql_url` remain tied to postgrest availability... ## ref: - closes https://github.com/supabase/cli/issues/5332 Co-authored-by: Colum Ferry --- apps/cli-go/internal/status/status.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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") }