From fc7c0b910f0cb81d7167b685e7a287d8138c2cea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:12:56 +0000 Subject: [PATCH 01/48] chore(ci): bump the actions-major group with 2 updates (#5744) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the actions-major group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/upload-artifact` from 4.6.2 to 7.0.1
Release notes

Sourced from actions/upload-artifact's releases.

v7.0.1

What's Changed

Full Changelog: https://github.com/actions/upload-artifact/compare/v7...v7.0.1

v7.0.0

v7 What's new

Direct Uploads

Adds support for uploading single files directly (unzipped). Callers can set the new archive parameter to false to skip zipping the file during upload. Right now, we only support single files. The action will fail if the glob passed resolves to multiple files. The name parameter is also ignored with this setting. Instead, the name of the artifact will be the name of the uploaded file.

ESM

To support new versions of the @actions/* packages, we've upgraded the package to ESM.

What's Changed

New Contributors

Full Changelog: https://github.com/actions/upload-artifact/compare/v6...v7.0.0

v6.0.0

v6 - What's new

[!IMPORTANT] actions/upload-artifact@v6 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

Node.js 24

This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

What's Changed

Full Changelog: https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0

v5.0.0

What's Changed

... (truncated)

Commits

Updates `actions/download-artifact` from 4.3.0 to 8.0.1
Release notes

Sourced from actions/download-artifact's releases.

v8.0.1

What's Changed

Full Changelog: https://github.com/actions/download-artifact/compare/v8...v8.0.1

v8.0.0

v8 - What's new

[!IMPORTANT] actions/download-artifact@v8 has been migrated to an ESM module. This should be transparent to the caller but forks might need to make significant changes.

[!IMPORTANT] Hash mismatches will now error by default. Users can override this behavior with a setting change (see below).

Direct downloads

To support direct uploads in actions/upload-artifact, the action will no longer attempt to unzip all downloaded files. Instead, the action checks the Content-Type header ahead of unzipping and skips non-zipped files. Callers wishing to download a zipped file as-is can also set the new skip-decompress parameter to true.

Enforced checks (breaking)

A previous release introduced digest checks on the download. If a download hash didn't match the expected hash from the server, the action would log a warning. Callers can now configure the behavior on mismatch with the digest-mismatch parameter. To be secure by default, we are now defaulting the behavior to error which will fail the workflow run.

ESM

To support new versions of the @actions/* packages, we've upgraded the package to ESM.

What's Changed

Full Changelog: https://github.com/actions/download-artifact/compare/v7...v8.0.0

v7.0.0

v7 - What's new

[!IMPORTANT] actions/download-artifact@v7 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

Node.js 24

This release updates the runtime to Node.js 24. v6 had preliminary support for Node 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

What's Changed

... (truncated)

Commits

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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-cli-artifacts.yml | 2 +- .github/workflows/publish-preview-cli-packages.yml | 2 +- .github/workflows/release-shared.yml | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-cli-artifacts.yml b/.github/workflows/build-cli-artifacts.yml index 7300d26b7a..1b6a22900d 100644 --- a/.github/workflows/build-cli-artifacts.yml +++ b/.github/workflows/build-cli-artifacts.yml @@ -131,7 +131,7 @@ jobs: # Artifacts have their own deterministic retention and survive job re-runs # within the run, which is exactly what this handoff needs. - name: Upload build artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: cli-build-${{ inputs.shell }}-${{ inputs.version }}${{ inputs.artifact_name_suffix }} path: | diff --git a/.github/workflows/publish-preview-cli-packages.yml b/.github/workflows/publish-preview-cli-packages.yml index 206a1931a5..153b75f252 100644 --- a/.github/workflows/publish-preview-cli-packages.yml +++ b/.github/workflows/publish-preview-cli-packages.yml @@ -58,7 +58,7 @@ jobs: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - name: Download preview build artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: cli-build-legacy-${{ env.PREVIEW_VERSION }} diff --git a/.github/workflows/release-shared.yml b/.github/workflows/release-shared.yml index 7d4301c037..aabe7861cf 100644 --- a/.github/workflows/release-shared.yml +++ b/.github/workflows/release-shared.yml @@ -110,7 +110,7 @@ jobs: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - name: Download build artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: cli-build-${{ inputs.shell }}-${{ inputs.version }} @@ -241,7 +241,7 @@ jobs: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - name: Download build artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: cli-build-${{ inputs.shell }}-${{ inputs.version }}-github @@ -295,7 +295,7 @@ jobs: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - name: Download build artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: cli-build-${{ inputs.shell }}-${{ inputs.version }}-github @@ -465,7 +465,7 @@ jobs: # tarballs. Reading it here produced a formula whose sha256 rejected the # downloaded archive ("Formula reports different checksum"). - name: Download build artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: cli-build-${{ inputs.shell }}-${{ inputs.version }}-github @@ -521,7 +521,7 @@ jobs: # tarballs. Reading it here would produce a manifest whose hash rejects the # downloaded archive. - name: Download build artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: cli-build-${{ inputs.shell }}-${{ inputs.version }}-github From 61bd84a88075b91747c869b03ac8b6fe46a7ba67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:18:22 +0000 Subject: [PATCH 02/48] fix(docker): bump the docker-minor group in /apps/cli-go/pkg/config/templates with 2 updates (#5745) Bumps the docker-minor group in /apps/cli-go/pkg/config/templates with 2 updates: supabase/realtime and supabase/storage-api. Updates `supabase/realtime` from v2.112.1 to v2.112.2 Updates `supabase/storage-api` from v1.61.7 to v1.61.9 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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/pkg/config/templates/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index 97dfc0dbea..c9634af0fe 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -11,8 +11,8 @@ FROM supabase/edge-runtime:v1.74.2 AS edgeruntime FROM timberio/vector:0.53.0-alpine AS vector FROM supabase/supavisor:2.9.7 AS supavisor FROM supabase/gotrue:v2.192.0 AS gotrue -FROM supabase/realtime:v2.112.1 AS realtime -FROM supabase/storage-api:v1.61.7 AS storage +FROM supabase/realtime:v2.112.2 AS realtime +FROM supabase/storage-api:v1.61.9 AS storage FROM supabase/logflare:1.45.6 AS logflare # Append to JobImages when adding new dependencies below FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ From 11e59d2a17d98e890dd13a9f6b7131815256a123 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:19:05 +0000 Subject: [PATCH 03/48] fix(docker): bump supabase/postgres from 17.6.1.140 to 17.6.1.141 in /apps/cli-go/pkg/config/templates (#5746) Bumps supabase/postgres from 17.6.1.140 to 17.6.1.141. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=supabase/postgres&package-manager=docker&previous-version=17.6.1.140&new-version=17.6.1.141)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) 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 this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/pkg/config/templates/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index c9634af0fe..4d6d4ac5d9 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -1,5 +1,5 @@ # Exposed for updates by .github/dependabot.yml -FROM supabase/postgres:17.6.1.140 AS pg +FROM supabase/postgres:17.6.1.141 AS pg # Append to ServiceImages when adding new dependencies below FROM library/kong:2.8.1 AS kong FROM axllent/mailpit:v1.30.2 AS mailpit From 03e0f9d641523e7ae88a81389fbf2a39332ca54f Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Wed, 1 Jul 2026 09:33:26 +0200 Subject: [PATCH 04/48] chore(stack): sync service versions from Dockerfile (#5716) Adds a stack-owned sync script that derives `DEFAULT_VERSIONS` from the Dockerfile manifest Dependabot already updates, and adds a Dependabot-only workflow that pushes the synced manifest back to Docker image update PRs with the existing GitHub App token pattern. Also syncs the current `DEFAULT_VERSIONS` values to the Dockerfile manifest and makes version-sensitive tests derive expectations from the manifest defaults where appropriate. Linear: CLI-1589 --- .../workflows/sync-stack-service-versions.yml | 61 ++++++++ .../src/next/commands/stop/stop.e2e.test.ts | 5 +- .../src/next/commands/stop/stop.handler.ts | 15 +- .../commands/stop/stop.integration.test.ts | 27 +++- packages/stack/package.json | 2 + .../scripts/sync-versions-from-dockerfile.ts | 132 ++++++++++++++++++ .../stack/src/BinaryResolver.unit.test.ts | 5 +- packages/stack/src/StackBuilder.unit.test.ts | 6 +- packages/stack/src/StateManager.ts | 80 ++++++++++- packages/stack/src/prefetch.unit.test.ts | 23 ++- packages/stack/src/versions.ts | 22 +-- packages/stack/src/versions.unit.test.ts | 87 ++++++++++++ tools/nx-plugins/src/test.plugin.ts | 27 ++-- 13 files changed, 450 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/sync-stack-service-versions.yml create mode 100644 packages/stack/scripts/sync-versions-from-dockerfile.ts diff --git a/.github/workflows/sync-stack-service-versions.yml b/.github/workflows/sync-stack-service-versions.yml new file mode 100644 index 0000000000..0650e3e233 --- /dev/null +++ b/.github/workflows/sync-stack-service-versions.yml @@ -0,0 +1,61 @@ +name: Sync Stack Service Versions + +on: + pull_request: + types: + - opened + - synchronize + - reopened + paths: + - apps/cli-go/pkg/config/templates/Dockerfile + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + sync: + name: Sync stack service versions + runs-on: blacksmith-2vcpu-ubuntu-2404 + if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == github.event.pull_request.head.repo.full_name + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.ref }} + persist-credentials: false + + - name: Setup + uses: ./.github/actions/setup + with: + dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} + + - name: Sync stack service versions + run: pnpm sync:versions + working-directory: packages/stack + + - name: Generate token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.GH_APP_CLIENT_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + permission-contents: write + + - name: Commit synced stack service versions + env: + GH_APP_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + if git diff --quiet -- packages/stack/src/versions.ts; then + echo "Stack service versions are already synced." + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add packages/stack/src/versions.ts + git commit -m "chore(stack): sync service version manifest" + git push "https://x-access-token:${GH_APP_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:${GITHUB_HEAD_REF}" diff --git a/apps/cli/src/next/commands/stop/stop.e2e.test.ts b/apps/cli/src/next/commands/stop/stop.e2e.test.ts index a63661a6e3..49881e8ed8 100644 --- a/apps/cli/src/next/commands/stop/stop.e2e.test.ts +++ b/apps/cli/src/next/commands/stop/stop.e2e.test.ts @@ -68,7 +68,10 @@ describe("supabase stop", () => { cwd: project.dir, home: home.dir, }); - expect(stopResult.exitCode).toBe(0); + expect( + stopResult.exitCode, + `stdout:\n${stopResult.stdout}\n\nstderr:\n${stopResult.stderr}`, + ).toBe(0); expect(existsSync(stackDir)).toBe(false); }, ); diff --git a/apps/cli/src/next/commands/stop/stop.handler.ts b/apps/cli/src/next/commands/stop/stop.handler.ts index b7e9dd033a..84d8801f59 100644 --- a/apps/cli/src/next/commands/stop/stop.handler.ts +++ b/apps/cli/src/next/commands/stop/stop.handler.ts @@ -16,20 +16,31 @@ export const stop = Effect.fnUntraced(function* (flags: StopFlags) { yield* output.intro("Stop local Supabase stack"); if (flags.noBackup) { + let stoppedRunningStack = true; yield* stopDaemon({ cwd, cacheRoot: cliConfig.supabaseHome, projectDir: projectHome.projectRoot, projectStateRoot: projectHome.projectHomeDir, name: flags.stack, - }).pipe(Effect.catchTag("NoRunningStackError", () => Effect.void)); + }).pipe( + Effect.catchTag("NoRunningStackError", () => + Effect.sync(() => { + stoppedRunningStack = false; + }), + ), + ); yield* deleteManagedStackPersistence({ cwd, cacheRoot: cliConfig.supabaseHome, projectDir: projectHome.projectRoot, projectStateRoot: projectHome.projectHomeDir, name: flags.stack, - }); + }).pipe( + Effect.catchTag("NoRunningStackError", (error) => + stoppedRunningStack ? Effect.void : Effect.fail(error), + ), + ); yield* output.success("Local Supabase stopped and persisted data deleted"); yield* output.outro("Local Supabase stack stopped and local data deleted."); diff --git a/apps/cli/src/next/commands/stop/stop.integration.test.ts b/apps/cli/src/next/commands/stop/stop.integration.test.ts index 77173df170..bcf7a8d46f 100644 --- a/apps/cli/src/next/commands/stop/stop.integration.test.ts +++ b/apps/cli/src/next/commands/stop/stop.integration.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "@effect/vitest"; -import { existsSync, mkdtempSync } from "node:fs"; +import { existsSync, mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { Effect, Exit, Layer } from "effect"; @@ -80,6 +80,31 @@ describe("stop handler", () => { }), ); + it.live("treats already-removed persistence as success after stopping with --no-backup", () => + Effect.gen(function* () { + const fixture = yield* Effect.acquireRelease( + Effect.promise(() => makeRunningStackFixture()), + (resource) => Effect.promise(() => resource.dispose()), + ); + const out = mockOutput(); + const layer = Layer.mergeAll(fixture.baseLayer, out.layer); + + rmSync(fixture.stackMetadataPath, { force: true }); + + yield* stop({ stack: fixture.stackName, noBackup: true }).pipe(Effect.provide(layer)); + + expect(fixture.stopped).toBe(true); + expect(existsSync(fixture.stackStatePath)).toBe(false); + expect(existsSync(fixture.stackMetadataPath)).toBe(false); + expect(out.messages).toContainEqual( + expect.objectContaining({ + type: "success", + message: "Local Supabase stopped and persisted data deleted", + }), + ); + }), + ); + it.live("deletes the requested stopped named stack with --no-backup", () => Effect.gen(function* () { const fixture = yield* Effect.acquireRelease( diff --git a/packages/stack/package.json b/packages/stack/package.json index 54aff19c81..284f92a896 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -15,6 +15,7 @@ "test": "nx run-many -t test:core test:e2e --projects=$npm_package_name", "test:core": "nx run-many -t test:unit test:integration --projects=$npm_package_name", "test:e2e:warmup": "bun run tests/warmup-e2e.ts", + "sync:versions": "bun run scripts/sync-versions-from-dockerfile.ts", "check:all": "nx run-many -t types:check lint:check fmt:check knip:check --projects=$npm_package_name", "fix:all": "nx run-many -t lint:fix fmt:fix knip:fix --projects=$npm_package_name" }, @@ -40,6 +41,7 @@ }, "knip": { "entry": [ + "scripts/**/*.ts", "src/**/*.test.ts", "src/daemon-node.ts", "tests/**/*.ts" diff --git a/packages/stack/scripts/sync-versions-from-dockerfile.ts b/packages/stack/scripts/sync-versions-from-dockerfile.ts new file mode 100644 index 0000000000..39e4592ea2 --- /dev/null +++ b/packages/stack/scripts/sync-versions-from-dockerfile.ts @@ -0,0 +1,132 @@ +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { + normalizeServiceVersion, + SERVICE_NAMES, + type ServiceName, + type VersionManifest, +} from "../src/versions.ts"; + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(scriptDir, "../../.."); +const dockerfilePath = path.join(repoRoot, "apps/cli-go/pkg/config/templates/Dockerfile"); +const versionsPath = path.join(repoRoot, "packages/stack/src/versions.ts"); + +const fromLinePattern = /^FROM\s+(.+):([^:\s]+)\s+AS\s+([^\s#]+)/i; + +const dockerfileAliases = new Map([ + ["pg", "postgres"], + ["postgrest", "postgrest"], + ["gotrue", "auth"], + ["edgeruntime", "edge-runtime"], + ["realtime", "realtime"], + ["storage", "storage"], + ["imgproxy", "imgproxy"], + ["mailpit", "mailpit"], + ["pgmeta", "pgmeta"], + ["studio", "studio"], + ["logflare", "analytics"], + ["vector", "vector"], + ["supavisor", "pooler"], +]); + +const ignoredAliases = new Set(["kong", "differ", "migra", "pgprove"]); + +function assertFullManifest( + versions: Partial>, +): asserts versions is VersionManifest { + const missing = SERVICE_NAMES.filter((service) => versions[service] === undefined); + if (missing.length > 0) { + throw new Error(`Missing Dockerfile versions for: ${missing.join(", ")}`); + } +} + +export function readVersionManifestFromDockerfile(dockerfile: string): VersionManifest { + const versions: Partial> = {}; + + for (const rawLine of dockerfile.split("\n")) { + const line = rawLine.trim(); + const match = fromLinePattern.exec(line); + if (match === null) { + continue; + } + + const [, , tag, alias] = match; + if (tag === undefined || alias === undefined) { + continue; + } + + if (ignoredAliases.has(alias)) { + continue; + } + + const service = dockerfileAliases.get(alias); + if (service === undefined) { + throw new Error(`Unknown Dockerfile image alias '${alias}'.`); + } + if (versions[service] !== undefined) { + throw new Error(`Duplicate Dockerfile version for '${service}'.`); + } + + versions[service] = normalizeServiceVersion(service, tag); + } + + assertFullManifest(versions); + return versions; +} + +function renderManifestKey(service: ServiceName): string { + return /^[a-zA-Z_$][\w$]*$/.test(service) ? service : JSON.stringify(service); +} + +export function renderDefaultVersions(versions: VersionManifest): string { + const lines = SERVICE_NAMES.map( + (service) => ` ${renderManifestKey(service)}: ${JSON.stringify(versions[service])},`, + ); + return ["export const DEFAULT_VERSIONS: VersionManifest = {", ...lines, "} as const;"].join("\n"); +} + +export function syncDefaultVersionsSource(source: string, versions: VersionManifest): string { + const startMarker = "export const DEFAULT_VERSIONS: VersionManifest = {"; + const endMarker = "\n} as const;"; + const start = source.indexOf(startMarker); + if (start === -1) { + throw new Error("Could not find DEFAULT_VERSIONS declaration."); + } + + const end = source.indexOf(endMarker, start); + if (end === -1) { + throw new Error("Could not find DEFAULT_VERSIONS declaration end."); + } + + return `${source.slice(0, start)}${renderDefaultVersions(versions)}${source.slice( + end + endMarker.length, + )}`; +} + +async function main() { + const checkOnly = process.argv.includes("--check"); + const dockerfile = await readFile(dockerfilePath, "utf8"); + const versionsSource = await readFile(versionsPath, "utf8"); + const versions = readVersionManifestFromDockerfile(dockerfile); + const syncedSource = syncDefaultVersionsSource(versionsSource, versions); + + if (syncedSource === versionsSource) { + console.log("DEFAULT_VERSIONS is already synced with the Dockerfile manifest."); + return; + } + + if (checkOnly) { + console.error("DEFAULT_VERSIONS is out of sync with the Dockerfile manifest."); + process.exitCode = 1; + return; + } + + await Bun.write(versionsPath, syncedSource); + console.log("Synced DEFAULT_VERSIONS with the Dockerfile manifest."); +} + +if (import.meta.main) { + await main(); +} diff --git a/packages/stack/src/BinaryResolver.unit.test.ts b/packages/stack/src/BinaryResolver.unit.test.ts index bc09b3b901..b0d63ccc77 100644 --- a/packages/stack/src/BinaryResolver.unit.test.ts +++ b/packages/stack/src/BinaryResolver.unit.test.ts @@ -5,6 +5,7 @@ import { DEFAULT_VERSIONS } from "./versions.ts"; const postgresVersion = DEFAULT_VERSIONS.postgres; const postgrestVersion = DEFAULT_VERSIONS.postgrest; const authVersion = DEFAULT_VERSIONS.auth; +const authRcVersion = "2.188.0-rc.15"; const edgeRuntimeVersion = DEFAULT_VERSIONS["edge-runtime"]; describe("BinaryResolver.downloadUrl", () => { @@ -44,11 +45,11 @@ describe("BinaryResolver.downloadUrl", () => { it("constructs auth URL for rc releases", () => { const url = BinaryResolver.downloadUrl({ service: "auth", - version: authVersion, + version: authRcVersion, assetName: "arm64", }); expect(url).toBe( - `https://github.com/supabase/auth/releases/download/rc${authVersion}/auth-v${authVersion}-arm64.tar.gz`, + `https://github.com/supabase/auth/releases/download/rc${authRcVersion}/auth-v${authRcVersion}-arm64.tar.gz`, ); }); diff --git a/packages/stack/src/StackBuilder.unit.test.ts b/packages/stack/src/StackBuilder.unit.test.ts index 3d3c2c0039..261ccd277c 100644 --- a/packages/stack/src/StackBuilder.unit.test.ts +++ b/packages/stack/src/StackBuilder.unit.test.ts @@ -456,8 +456,10 @@ describe("StackBuilder", () => { }); const realtimeDef = graph.startOrder.find((service) => service.name === "realtime"); - expect(realtimeDef?.args).toContain("supabase/realtime:v2.111.8"); - expect(realtimeDef?.args).not.toContain("public.ecr.aws/supabase/realtime:v2.111.8"); + expect(realtimeDef?.args).toContain(`supabase/realtime:v${DEFAULT_VERSIONS.realtime}`); + expect(realtimeDef?.args).not.toContain( + `public.ecr.aws/supabase/realtime:v${DEFAULT_VERSIONS.realtime}`, + ); }).pipe(Effect.provide(layer)); }); }); diff --git a/packages/stack/src/StateManager.ts b/packages/stack/src/StateManager.ts index 27303a9214..5b2b7e6c40 100644 --- a/packages/stack/src/StateManager.ts +++ b/packages/stack/src/StateManager.ts @@ -1,5 +1,7 @@ import { Data, Effect, Layer, Schema, Context } from "effect"; import { FileSystem, Path } from "effect"; +import { execFileSync } from "node:child_process"; +import { existsSync, rmSync } from "node:fs"; import { AllocatedPortsSchema, type AllocatedPorts } from "./PortAllocator.ts"; import { PartialVersionManifestSchema, @@ -14,7 +16,7 @@ import { defaultManagedRuntimeRoot, socketPathForRuntimeRoot, } from "./paths.ts"; -import { dirname, join } from "node:path"; +import { basename, dirname, join } from "node:path"; // --------------------------------------------------------------------------- // Types @@ -460,9 +462,81 @@ function makeRemove(deps: StateManagerDeps) { function makeDeleteStack(deps: StateManagerDeps) { return (name: string): Effect.Effect => Effect.gen(function* () { - yield* deps.fs.remove(deps.stackDir(name), { recursive: true }); + const stackDir = deps.stackDir(name); + yield* deps.fs + .remove(stackDir, { recursive: true }) + .pipe(Effect.catch((error) => removeStackDirWithDocker(stackDir, error))); yield* deps.fs.remove(deps.runtimeDir(name), { recursive: true }).pipe(Effect.ignore); - }).pipe(Effect.catchTag("PlatformError", (e) => Effect.die(e))); + }); +} + +function localDockerImages(): ReadonlyArray { + try { + return execFileSync("docker", ["image", "ls", "--format", "{{.Repository}}:{{.Tag}}"], { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + timeout: 5_000, + }) + .split("\n") + .map((line) => line.trim()) + .filter((line) => line.length > 0 && !line.endsWith(":")); + } catch { + return []; + } +} + +function postgresCleanupImages(): ReadonlyArray { + return localDockerImages().filter( + (image) => + image.startsWith("public.ecr.aws/supabase/postgres:") || + image.startsWith("supabase/postgres:") || + image.startsWith("ghcr.io/supabase/postgres:"), + ); +} + +function dockerRemovePath(targetPath: string, image: string): void { + execFileSync( + "docker", + [ + "run", + "--rm", + "--user", + "0:0", + "-v", + `${dirname(targetPath)}:/parent`, + "-e", + `TARGET_NAME=${basename(targetPath)}`, + "--entrypoint", + "sh", + image, + "-c", + 'cd /parent && rm -rf -- "$TARGET_NAME"', + ], + { stdio: "ignore", timeout: 30_000 }, + ); +} + +function removeStackDirWithDocker(targetPath: string, cause: unknown): Effect.Effect { + return Effect.sync(() => { + try { + rmSync(targetPath, { recursive: true, force: true }); + } catch {} + + if (!existsSync(targetPath)) { + return; + } + + for (const image of postgresCleanupImages()) { + try { + dockerRemovePath(targetPath, image); + } catch {} + if (!existsSync(targetPath)) { + return; + } + } + + throw cause; + }); } function makeResolve( diff --git a/packages/stack/src/prefetch.unit.test.ts b/packages/stack/src/prefetch.unit.test.ts index bf0e66d50a..1cf509d074 100644 --- a/packages/stack/src/prefetch.unit.test.ts +++ b/packages/stack/src/prefetch.unit.test.ts @@ -14,6 +14,9 @@ import { prepareAssetsWithDependencies } from "./StackPreparation.ts"; import { DEFAULT_VERSIONS, SERVICE_NAMES } from "./versions.ts"; const encoder = new TextEncoder(); +const defaultAuthEcrImage = `public.ecr.aws/supabase/gotrue:v${DEFAULT_VERSIONS.auth}`; +const defaultAuthDockerHubImage = `supabase/gotrue:v${DEFAULT_VERSIONS.auth}`; +const defaultAuthGhcrImage = `ghcr.io/supabase/gotrue:v${DEFAULT_VERSIONS.auth}`; interface SpawnResult { readonly exitCode: number; @@ -112,15 +115,11 @@ describe("prefetch", () => { expect(result.auth).toEqual({ type: "docker", - image: "supabase/gotrue:v2.188.0-rc.15", + image: defaultAuthDockerHubImage, }); expect( spawner.spawned.filter((record) => record.args[0] === "pull").map((record) => record.args[1]), - ).toEqual([ - "public.ecr.aws/supabase/gotrue:v2.188.0-rc.15", - "public.ecr.aws/supabase/gotrue:v2.188.0-rc.15", - "supabase/gotrue:v2.188.0-rc.15", - ]); + ).toEqual([defaultAuthEcrImage, defaultAuthEcrImage, defaultAuthDockerHubImage]); }); test("falls back to GHCR after ECR and Docker Hub fail", async () => { @@ -149,15 +148,15 @@ describe("prefetch", () => { expect(result.auth).toEqual({ type: "docker", - image: "ghcr.io/supabase/gotrue:v2.188.0-rc.15", + image: defaultAuthGhcrImage, }); expect( spawner.spawned.filter((record) => record.args[0] === "pull").map((record) => record.args[1]), ).toEqual([ - "public.ecr.aws/supabase/gotrue:v2.188.0-rc.15", - "supabase/gotrue:v2.188.0-rc.15", - "supabase/gotrue:v2.188.0-rc.15", - "ghcr.io/supabase/gotrue:v2.188.0-rc.15", + defaultAuthEcrImage, + defaultAuthDockerHubImage, + defaultAuthDockerHubImage, + defaultAuthGhcrImage, ]); }); @@ -224,7 +223,7 @@ describe("prefetch", () => { expect(result.auth).toEqual({ type: "docker", - image: "public.ecr.aws/supabase/gotrue:v2.188.0-rc.15", + image: defaultAuthEcrImage, }); expect(events).toEqual([]); }); diff --git a/packages/stack/src/versions.ts b/packages/stack/src/versions.ts index 273e47ffcc..2141ee733a 100644 --- a/packages/stack/src/versions.ts +++ b/packages/stack/src/versions.ts @@ -46,19 +46,19 @@ export interface VersionManifest { } export const DEFAULT_VERSIONS: VersionManifest = { - postgres: "17.6.1.107", - postgrest: "14.5", - auth: "2.188.0-rc.15", - "edge-runtime": "1.73.13", - realtime: "2.111.8", - storage: "1.41.8", + postgres: "17.6.1.141", + postgrest: "14.14", + auth: "2.192.0", + "edge-runtime": "1.74.2", + realtime: "2.112.2", + storage: "1.61.9", imgproxy: "v3.8.0", mailpit: "v1.30.2", - pgmeta: "0.96.1", - studio: "2026.03.04-sha-0043607", - analytics: "1.34.7", - vector: "0.28.1-alpine", - pooler: "2.7.4", + pgmeta: "0.96.6", + studio: "2026.06.29-sha-20290c7", + analytics: "1.45.6", + vector: "0.53.0-alpine", + pooler: "2.9.7", } as const; /** Default registry. Matches the Go CLI default (`public.ecr.aws`). */ diff --git a/packages/stack/src/versions.unit.test.ts b/packages/stack/src/versions.unit.test.ts index 8f6141035f..a8440db663 100644 --- a/packages/stack/src/versions.unit.test.ts +++ b/packages/stack/src/versions.unit.test.ts @@ -1,4 +1,9 @@ +import { readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; +import { + readVersionManifestFromDockerfile, + syncDefaultVersionsSource, +} from "../scripts/sync-versions-from-dockerfile.ts"; import { DEFAULT_VERSIONS, diffPinnedAndAvailableVersions, @@ -9,6 +14,29 @@ import { type VersionManifest, } from "./versions.ts"; +const dockerfile = readFileSync( + new URL("../../../apps/cli-go/pkg/config/templates/Dockerfile", import.meta.url), + "utf8", +); + +const sampleDockerfile = ` +FROM supabase/postgres:17.0.0.1 AS pg +FROM library/kong:2.8.1 AS kong +FROM axllent/mailpit:v1.2.3 AS mailpit +FROM postgrest/postgrest:v14.0 AS postgrest +FROM supabase/postgres-meta:v0.90.0 AS pgmeta +FROM supabase/studio:2026.01.01-sha-abcdef0 AS studio +FROM darthsim/imgproxy:v3.8.0 AS imgproxy +FROM supabase/edge-runtime:v1.70.0 AS edgeruntime +FROM timberio/vector:0.50.0-alpine AS vector +FROM supabase/supavisor:2.1.0 AS supavisor +FROM supabase/gotrue:v2.100.0 AS gotrue +FROM supabase/realtime:v2.100.0 AS realtime +FROM supabase/storage-api:v1.50.0 AS storage +FROM supabase/logflare:1.40.0 AS logflare +FROM supabase/migra:3.0.1663481299 AS migra +`; + describe("DEFAULT_VERSIONS", () => { it("has all required services", () => { expect(DEFAULT_VERSIONS).toHaveProperty("postgres"); @@ -27,6 +55,65 @@ describe("DEFAULT_VERSIONS", () => { expect(typeof DEFAULT_VERSIONS["edge-runtime"]).toBe("string"); expect(DEFAULT_VERSIONS["edge-runtime"].length).toBeGreaterThan(0); }); + + it("matches the Dockerfile manifest exposed to Dependabot", () => { + expect(readVersionManifestFromDockerfile(dockerfile)).toEqual(DEFAULT_VERSIONS); + }); +}); + +describe("syncDefaultVersionsSource", () => { + it("rewrites the DEFAULT_VERSIONS block from Dockerfile versions", () => { + const source = `before +export const DEFAULT_VERSIONS: VersionManifest = { + postgres: "old", + postgrest: "old", + auth: "old", + "edge-runtime": "old", + realtime: "old", + storage: "old", + imgproxy: "old", + mailpit: "old", + pgmeta: "old", + studio: "old", + analytics: "old", + vector: "old", + pooler: "old", +} as const; +after`; + + expect(syncDefaultVersionsSource(source, readVersionManifestFromDockerfile(sampleDockerfile))) + .toMatchInlineSnapshot(` + "before + export const DEFAULT_VERSIONS: VersionManifest = { + postgres: "17.0.0.1", + postgrest: "14.0", + auth: "2.100.0", + "edge-runtime": "1.70.0", + realtime: "2.100.0", + storage: "1.50.0", + imgproxy: "v3.8.0", + mailpit: "v1.2.3", + pgmeta: "0.90.0", + studio: "2026.01.01-sha-abcdef0", + analytics: "1.40.0", + vector: "0.50.0-alpine", + pooler: "2.1.0", + } as const; + after" + `); + }); + + it("fails when a required Dockerfile image alias is missing", () => { + expect(() => + readVersionManifestFromDockerfile("FROM supabase/postgres:17.6.1.139 AS pg\n"), + ).toThrow("Missing Dockerfile versions for:"); + }); + + it("fails when the Dockerfile contains an unexpected image alias", () => { + expect(() => + readVersionManifestFromDockerfile(`${dockerfile}\nFROM supabase/example:1.0.0 AS example\n`), + ).toThrow("Unknown Dockerfile image alias 'example'."); + }); }); describe("dockerImageForService", () => { diff --git a/tools/nx-plugins/src/test.plugin.ts b/tools/nx-plugins/src/test.plugin.ts index 8d3f46e370..27eec91fa3 100644 --- a/tools/nx-plugins/src/test.plugin.ts +++ b/tools/nx-plugins/src/test.plugin.ts @@ -35,14 +35,24 @@ export const createNodesV2: CreateNodesV2 = [ const vitestProjects = vitestConfig.vitestConfig?.projects ?? []; if (vitestProjects.length > 0) { for (const vitestProject of vitestProjects) { - if (vitestProject.test) { + if (typeof vitestProject !== "string" && vitestProject.test) { + const testProject = vitestProject.test; + const targetName = testProject.name; + const extraInputs = + projectRoot === "packages/stack" && targetName === "unit" + ? ["{workspaceRoot}/apps/cli-go/pkg/config/templates/Dockerfile"] + : []; project.targets = { ...project.targets, - ...createTestTarget(vitestProject.test?.name, [ - ...(vitestProject?.test?.include ?? []), - ...(vitestProject?.test?.globalSetup ?? []), - ...(vitestProject?.test?.setupFiles ?? []), - ]), + ...createTestTarget( + targetName, + [ + ...(testProject.include ?? []), + ...(testProject.globalSetup ?? []), + ...(testProject.setupFiles ?? []), + ], + extraInputs, + ), }; } } @@ -62,7 +72,7 @@ export const createNodesV2: CreateNodesV2 = [ }, ]; -function createTestTarget(name: string = "", inputs: string[] = []) { +function createTestTarget(name: string = "", inputs: string[] = [], extraInputs: string[] = []) { return { [name !== "" ? `test:${name}` : "test"]: { command: `bun --bun vitest run${name !== "" ? ` --project ${name} --coverage.reportsDirectory=coverage/${name}` : ``}`, @@ -72,6 +82,7 @@ function createTestTarget(name: string = "", inputs: string[] = []) { "default", "sharedGlobals", ...inputs.map((input) => join(`{projectRoot}`, input)), + ...extraInputs, { externalDependencies: ["vitest"] }, ], }, @@ -79,5 +90,5 @@ function createTestTarget(name: string = "", inputs: string[] = []) { } function loadVitestDynamicImport() { - return Function('return import("vitest/node")')() as Promise; + return Function('return import("vitest/node")')(); } From 78f955470b6e10e2a42bf72ffba65cf45f6889d5 Mon Sep 17 00:00:00 2001 From: Vaibhav <117663341+7ttp@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:03:57 +0530 Subject: [PATCH 05/48] fix(cli): eszip error (#5747) ## TL;DR: adds smol contextual eszip read error to deploy failures ## ref: - towards: https://github.com/supabase/cli/issues/5735 --- .../deploy/deploy.integration.test.ts | 28 +++++++++++++++++++ apps/cli/src/shared/functions/deploy.ts | 8 +++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/cli/src/next/commands/functions/deploy/deploy.integration.test.ts b/apps/cli/src/next/commands/functions/deploy/deploy.integration.test.ts index d4d7e30b0b..0b73309418 100644 --- a/apps/cli/src/next/commands/functions/deploy/deploy.integration.test.ts +++ b/apps/cli/src/next/commands/functions/deploy/deploy.integration.test.ts @@ -1466,6 +1466,34 @@ describe("functions deploy", () => { }).pipe(Effect.ensuring(cleanupTempDir(tempDir))); }); + it.live("fails with a contextual error when Docker bundling produces no eszip output", () => { + const tempDir = makeTempDir(); + const child = mockChildProcessSpawner({ exitCode: 0 }); + + return Effect.gen(function* () { + yield* Effect.promise(() => writeProjectConfig(tempDir)); + yield* Effect.promise(() => writeLocalFunction(tempDir, "hello-world")); + + const { out, layer } = setup(tempDir, { + rawArgs: ["functions", "deploy", "hello-world", "--use-docker"], + childLayer: child.layer, + }); + + const error = yield* functionsDeploy({ + ...BASE_FLAGS, + functionNames: ["hello-world"], + useDocker: true, + }).pipe(Effect.provide(layer), Effect.flip); + + expect(error).toBeInstanceOf(Error); + if (error instanceof Error) { + expect(error.message).toContain("failed to open eszip:"); + expect(error.message).toContain("output.eszip"); + } + expect(out.stderrText).toContain("Bundling Function: hello-world\n"); + }).pipe(Effect.ensuring(cleanupTempDir(tempDir))); + }); + it.live( "accepts nullable optional fields when listing remote functions for Docker deploys", () => { diff --git a/apps/cli/src/shared/functions/deploy.ts b/apps/cli/src/shared/functions/deploy.ts index 8f626e673b..0e2fb6b28c 100644 --- a/apps/cli/src/shared/functions/deploy.ts +++ b/apps/cli/src/shared/functions/deploy.ts @@ -1404,7 +1404,13 @@ const bundleFunctionWithDocker = Effect.fnUntraced(function* ( return yield* Effect.fail(new Error(`failed to bundle function: exit ${result.exitCode}`)); } - const eszip = yield* Effect.tryPromise(() => readFile(outputPath)); + const eszip = yield* Effect.tryPromise({ + try: () => readFile(outputPath), + catch: (error) => + new Error( + `failed to open eszip: ${error instanceof Error ? error.message : String(error)}`, + ), + }); const compressed = new Uint8Array( Buffer.concat([ Buffer.from(COMPRESSED_ESZIP_MAGIC), From 25d5f32f0f3fccad1a639568c795ffdd3fd1fc2b Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Wed, 1 Jul 2026 09:34:47 +0200 Subject: [PATCH 06/48] chore(cli): define hotfix release workflow (#5738) ## Summary Adds a documented hotfix release path that reuses the existing production PR gate instead of introducing a separate approval flow. Hotfix PRs can now target main from hotfix/* branches, and successful stable Release runs on main automatically sync main back into develop so the released commit stays in the integration branch before the next scheduled promotion. The release process docs now describe the dry-run, merge, release-monitoring, and back-port expectations for urgent stable fixes. Addresses https://linear.app/supabase/issue/CLI-1579/define-a-clear-hotfix-release-workflow --- .github/workflows/deploy-check.yml | 4 +- .github/workflows/sync-main-to-develop.yml | 56 ++++++++++++++++++++++ apps/cli/docs/release-process.md | 30 +++++++++++- 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/sync-main-to-develop.yml diff --git a/.github/workflows/deploy-check.yml b/.github/workflows/deploy-check.yml index 8db23a6e55..534aebd7d9 100644 --- a/.github/workflows/deploy-check.yml +++ b/.github/workflows/deploy-check.yml @@ -15,9 +15,9 @@ permissions: jobs: check: - if: github.head_ref != 'develop' + if: github.head_ref != 'develop' && !startsWith(github.head_ref, 'hotfix/') runs-on: ubuntu-latest steps: - run: | - echo "Pull requests to main branch are only allowed from develop branch." + echo "Pull requests to main branch are only allowed from develop or hotfix/* branches." exit 1 diff --git a/.github/workflows/sync-main-to-develop.yml b/.github/workflows/sync-main-to-develop.yml new file mode 100644 index 0000000000..c93f2135ed --- /dev/null +++ b/.github/workflows/sync-main-to-develop.yml @@ -0,0 +1,56 @@ +name: Sync main to develop + +# Keeps stable hotfix commits reachable from the integration branch after the +# production source of truth publishes successfully. +on: + workflow_run: + workflows: + - Release + types: + - completed + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: sync-main-to-develop + cancel-in-progress: false + +jobs: + sync: + name: Merge main into develop + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + steps: + - id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.GH_APP_CLIENT_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + permission-contents: write + + - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 + with: + ref: develop + fetch-depth: 0 + persist-credentials: true + token: ${{ steps.app-token.outputs.token }} + + - name: Merge main into develop + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git fetch origin main develop + if git merge-base --is-ancestor origin/main HEAD; then + echo "develop already contains main." + else + git merge origin/main --no-edit + git push origin develop + fi diff --git a/apps/cli/docs/release-process.md b/apps/cli/docs/release-process.md index c8196ef732..05ab1cb582 100644 --- a/apps/cli/docs/release-process.md +++ b/apps/cli/docs/release-process.md @@ -247,7 +247,7 @@ flowchart TD ### Trigger -Most releases are automatic — merge a PR into `develop` (beta) or approve the weekly Prod-Deploy PR into `main` (stable). For an `alpha` cut or a one-off override, dispatch manually: +Most releases are automatic — merge a PR into `develop` (beta) or approve the weekly Prod-Deploy PR into `main` (stable). Hotfixes use the same production gate: a reviewed `hotfix/*` PR targets `main`, and the resulting `main` push triggers the stable release path. For an `alpha` cut or a one-off override, dispatch manually: ```sh # Manual alpha cut (v3 / next shell): @@ -265,6 +265,34 @@ gh workflow run release.yml \ Auto-trigger paths leave `version` empty: semantic-release computes it from commits since the last tag. +### Hotfix release flow + +Use a hotfix when an urgent stable fix must ship before the next scheduled `develop` -> `main` promotion. The hotfix path deliberately reuses the production PR gate instead of adding a second approval mechanism: + +1. Branch from the current `main` tip: + + ```sh + git fetch origin main + git switch -c hotfix/ origin/main + ``` + +2. Make the smallest safe fix and open a PR from `hotfix/` into `main`. +3. Before merging, run a release dry run against the hotfix branch and the next unique stable version: + + ```sh + gh workflow run release.yml \ + --ref hotfix/ \ + --field channel=stable \ + --field version= \ + --field dry_run=true + ``` + +4. After review and green checks, merge the hotfix PR into `main`. The `push: main` trigger runs the normal stable release pipeline and publishes the next semantic-release version. +5. Watch the stable release workflow through publish, Homebrew/Scoop updates, and verification. +6. Confirm that `Sync main to develop` succeeds. That workflow merges `main` back into `develop` after a successful `Release` run on `main`, keeping the hotfix reachable from the next beta and the next scheduled production deploy. If the sync conflicts, resolve it with a follow-up PR into `develop` before the next production promotion. + +Do not use `workflow_dispatch dry_run=false` as the normal hotfix path. Manual stable dispatch is reserved for re-cutting a unique version after an interrupted or stale-bytes release. Hotfixes should land through a PR to `main` so the production source of truth and release tag history stay aligned. + ### What each job does `**build` (ubuntu-latest):\*\* From 1e33f0b88a05830f26dc97b0b30d8b5a13b538fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:28:54 +0200 Subject: [PATCH 07/48] chore(deps): bump the npm-major group with 9 updates (#5743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-major group with 9 updates: | Package | From | To | | --- | --- | --- | | [@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript) | `0.3.186` | `0.3.187` | | [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.38.2` | `5.38.4` | | [@effect/atom-react](https://github.com/Effect-TS/effect-smol/tree/HEAD/packages/atom/react) | `4.0.0-beta.85` | `4.0.0-beta.87` | | [@effect/platform-bun](https://github.com/Effect-TS/effect/tree/HEAD/packages/platform-bun) | `4.0.0-beta.85` | `4.0.0-beta.87` | | [@effect/platform-node](https://github.com/Effect-TS/effect/tree/HEAD/packages/platform-node) | `4.0.0-beta.85` | `4.0.0-beta.87` | | [@effect/sql-pg](https://github.com/Effect-TS/effect/tree/HEAD/packages/sql-pg) | `4.0.0-beta.85` | `4.0.0-beta.87` | | [@typescript/native-preview](https://github.com/microsoft/typescript-go) | `7.0.0-dev.20260622.1` | `7.0.0-dev.20260623.1` | | [effect](https://github.com/Effect-TS/effect/tree/HEAD/packages/effect) | `4.0.0-beta.85` | `4.0.0-beta.87` | | [tldts](https://github.com/remusao/tldts) | `6.1.86` | `7.4.4` | Updates `@anthropic-ai/claude-agent-sdk` from 0.3.186 to 0.3.187
Release notes

Sourced from @​anthropic-ai/claude-agent-sdk's releases.

v0.3.187

What's changed

  • Added sandbox.credentials to SDK settings types for configuring credential file and environment variable denial in sandboxed commands

Update

npm install @anthropic-ai/claude-agent-sdk@0.3.187
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.187
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.187
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.187
Changelog

Sourced from @​anthropic-ai/claude-agent-sdk's changelog.

0.3.187

  • Added sandbox.credentials to SDK settings types for configuring credential file and environment variable denial in sandboxed commands
Commits

Updates `posthog-node` from 5.38.2 to 5.38.4
Release notes

Sourced from posthog-node's releases.

posthog-node@5.38.4

5.38.4

Patch Changes

  • #3938 6391106 Thanks @​dustinbyrne! - Respect disable_capture_url_hashes when capturing Express and NestJS request URLs. (2026-06-23)

posthog-node@5.38.3

5.38.3

Patch Changes

  • #3937 4d59d0e Thanks @​marandaneto! - Deprecate the Node SDK Sentry integration and point users to PostHog Error Tracking for Node. (2026-06-23)
  • Updated dependencies [440e370]:
    • @​posthog/core@​1.37.0
Changelog

Sourced from posthog-node's changelog.

5.38.4

Patch Changes

  • #3938 6391106 Thanks @​dustinbyrne! - Respect disable_capture_url_hashes when capturing Express and NestJS request URLs. (2026-06-23)

5.38.3

Patch Changes

  • #3937 4d59d0e Thanks @​marandaneto! - Deprecate the Node SDK Sentry integration and point users to PostHog Error Tracking for Node. (2026-06-23)
  • Updated dependencies [440e370]:
    • @​posthog/core@​1.37.0
Commits
  • 2a60c21 chore: update versions and lockfile [version bump]
  • 6391106 fix(node): respect URL hash capture config (#3938)
  • a3136c6 chore: update versions and lockfile [version bump]
  • bf6947d feat(react-native): support session replay event triggers (#3932)
  • 2628a1a chore: update versions and lockfile [version bump]
  • 4d59d0e chore(node): mark Sentry integration deprecated (#3937)
  • 1481f6d chore: update versions and lockfile [version bump]
  • a4c4d31 chore: template public API snapshot versions (#3934)
  • c28b161 feat: strip URL hashes from captured URLs (#3921)
  • See full diff in compare view

Updates `@effect/atom-react` from 4.0.0-beta.85 to 4.0.0-beta.87
Changelog

Sourced from @​effect/atom-react's changelog.

4.0.0-beta.87

Patch Changes

4.0.0-beta.86

Patch Changes

Commits

Updates `@effect/platform-bun` from 4.0.0-beta.85 to 4.0.0-beta.87
Commits

Updates `@effect/platform-node` from 4.0.0-beta.85 to 4.0.0-beta.87
Commits

Updates `@effect/sql-pg` from 4.0.0-beta.85 to 4.0.0-beta.87
Commits

Updates `@typescript/native-preview` from 7.0.0-dev.20260622.1 to 7.0.0-dev.20260623.1
Commits

Updates `effect` from 4.0.0-beta.85 to 4.0.0-beta.87
Commits

Updates `tldts` from 6.1.86 to 7.4.4
Release notes

Sourced from tldts's releases.

v7.4.4

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

Authors: 1

v7.4.3

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

:nut_and_bolt: Dependencies

Authors: 2

v7.4.2

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

:nut_and_bolt: Dependencies

Authors: 2

v7.4.1

:scroll: Update Public Suffix List

  • tldts-experimental, tldts-icann, tldts

:memo: Documentation

... (truncated)

Changelog

Sourced from tldts's changelog.

v7.4.4 (Tue Jun 23 2026)

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

Authors: 1


v7.4.3 (Mon Jun 15 2026)

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

:nut_and_bolt: Dependencies

Authors: 2


v7.4.2 (Sat May 30 2026)

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

:nut_and_bolt: Dependencies

Authors: 2


... (truncated)

Commits
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for tldts since your current version.


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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Julien Goux --- apps/cli/package.json | 4 +- .../next/commands/platform/platform-input.ts | 2 +- .../next/commands/platform/platform-types.ts | 2 +- pnpm-lock.yaml | 296 +++++++++--------- pnpm-workspace.yaml | 14 +- 5 files changed, 159 insertions(+), 159 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 03f695883a..0056938c90 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -43,7 +43,7 @@ "jose": "^6.2.3" }, "devDependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.3.186", + "@anthropic-ai/claude-agent-sdk": "^0.3.187", "@anthropic-ai/sdk": "^0.105.0", "@clack/prompts": "^1.6.0", "@effect/atom-react": "catalog:", @@ -76,7 +76,7 @@ "oxlint-tsgolint": "catalog:", "pg": "^8.22.0", "pg-copy-streams": "^7.0.0", - "posthog-node": "^5.38.2", + "posthog-node": "^5.38.4", "react": "^19.2.7", "react-devtools-core": "^7.0.1", "semantic-release": "^25.0.5", diff --git a/apps/cli/src/next/commands/platform/platform-input.ts b/apps/cli/src/next/commands/platform/platform-input.ts index efb1d0e9b6..db3a7bac10 100644 --- a/apps/cli/src/next/commands/platform/platform-input.ts +++ b/apps/cli/src/next/commands/platform/platform-input.ts @@ -379,7 +379,7 @@ export const promptForMissingPlatformFields = ( return completed; }); -export const decodePlatformInput = >( +export const decodePlatformInput = >( descriptor: PlatformOperationDescriptor, schema: S, input: JsonRecord, diff --git a/apps/cli/src/next/commands/platform/platform-types.ts b/apps/cli/src/next/commands/platform/platform-types.ts index e531db1e4a..06fcb9c5fe 100644 --- a/apps/cli/src/next/commands/platform/platform-types.ts +++ b/apps/cli/src/next/commands/platform/platform-types.ts @@ -68,7 +68,7 @@ export interface PlatformOperationDescriptor { readonly description: string; readonly successMessage: string; readonly confirmsMutation: boolean; - readonly inputSchema: Schema.Decoder; + readonly inputSchema: Schema.ConstraintDecoder; readonly definition: OperationDefinition; readonly execute: (input: unknown) => Effect.Effect; readonly request: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1fb26fcfe6..df0ad7f58c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,17 +7,17 @@ settings: catalogs: default: '@effect/atom-react': - specifier: 4.0.0-beta.85 - version: 4.0.0-beta.85 + specifier: 4.0.0-beta.87 + version: 4.0.0-beta.87 '@effect/platform-bun': - specifier: 4.0.0-beta.85 - version: 4.0.0-beta.85 + specifier: 4.0.0-beta.87 + version: 4.0.0-beta.87 '@effect/platform-node': - specifier: 4.0.0-beta.85 - version: 4.0.0-beta.85 + specifier: 4.0.0-beta.87 + version: 4.0.0-beta.87 '@effect/sql-pg': - specifier: 4.0.0-beta.85 - version: 4.0.0-beta.85 + specifier: 4.0.0-beta.87 + version: 4.0.0-beta.87 '@effect/vitest': specifier: ^4.0.0-beta.85 version: 4.0.0-beta.91 @@ -37,14 +37,14 @@ catalogs: specifier: ^1.3.14 version: 1.3.14 '@typescript/native-preview': - specifier: 7.0.0-dev.20260622.1 - version: 7.0.0-dev.20260622.1 + specifier: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260623.1 '@vitest/coverage-istanbul': specifier: ^4.1.9 version: 4.1.9 effect: - specifier: 4.0.0-beta.85 - version: 4.0.0-beta.85 + specifier: 4.0.0-beta.87 + version: 4.0.0-beta.87 knip: specifier: ^6.17.1 version: 6.23.0 @@ -61,7 +61,7 @@ catalogs: specifier: ^0.23.0 version: 0.23.0 tldts: - specifier: ^7.4.3 + specifier: ^7.4.4 version: 7.4.5 vitest: specifier: ^4.1.9 @@ -97,8 +97,8 @@ importers: version: 6.2.3 devDependencies: '@anthropic-ai/claude-agent-sdk': - specifier: ^0.3.186 - version: 0.3.186(@anthropic-ai/sdk@0.105.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) + specifier: ^0.3.187 + version: 0.3.187(@anthropic-ai/sdk@0.105.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) '@anthropic-ai/sdk': specifier: ^0.105.0 version: 0.105.0(zod@4.4.3) @@ -107,16 +107,16 @@ importers: version: 1.6.0 '@effect/atom-react': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85)(react@19.2.7)(scheduler@0.27.0) + version: 4.0.0-beta.87(effect@4.0.0-beta.87)(react@19.2.7)(scheduler@0.27.0) '@effect/platform-bun': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85) + version: 4.0.0-beta.87(effect@4.0.0-beta.87) '@effect/sql-pg': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85) + version: 4.0.0-beta.87(effect@4.0.0-beta.87) '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.91(effect@4.0.0-beta.85)(vitest@4.1.9) + version: 4.0.0-beta.91(effect@4.0.0-beta.87)(vitest@4.1.9) '@modelcontextprotocol/sdk': specifier: ^1.29.0 version: 1.29.0(zod@4.4.3) @@ -155,7 +155,7 @@ importers: version: 19.2.17 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260622.1 + version: 7.0.0-dev.20260623.1 '@vercel/detect-agent': specifier: ^1.2.3 version: 1.2.3 @@ -167,7 +167,7 @@ importers: version: 17.4.2 effect: specifier: 'catalog:' - version: 4.0.0-beta.85 + version: 4.0.0-beta.87 esbuild: specifier: ^0.28.1 version: 0.28.1 @@ -196,8 +196,8 @@ importers: specifier: ^7.0.0 version: 7.0.0 posthog-node: - specifier: ^5.38.2 - version: 5.38.2 + specifier: ^5.38.4 + version: 5.38.4 react: specifier: ^19.2.7 version: 19.2.7 @@ -259,7 +259,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260622.1 + version: 7.0.0-dev.20260623.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -320,13 +320,13 @@ importers: dependencies: '@effect/platform-bun': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85) + version: 4.0.0-beta.87(effect@4.0.0-beta.87) '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85)(ioredis@5.11.0) + version: 4.0.0-beta.87(effect@4.0.0-beta.87)(ioredis@5.11.0) effect: specifier: 'catalog:' - version: 4.0.0-beta.85 + version: 4.0.0-beta.87 undici: specifier: ^8.5.0 version: 8.5.0 @@ -339,7 +339,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260622.1 + version: 7.0.0-dev.20260623.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -381,7 +381,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260622.1 + version: 7.0.0-dev.20260623.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -409,16 +409,16 @@ importers: dependencies: '@effect/platform-bun': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85) + version: 4.0.0-beta.87(effect@4.0.0-beta.87) '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85)(ioredis@5.11.0) + version: 4.0.0-beta.87(effect@4.0.0-beta.87)(ioredis@5.11.0) dedent: specifier: ^1.7.2 version: 1.7.2 effect: specifier: 'catalog:' - version: 4.0.0-beta.85 + version: 4.0.0-beta.87 smol-toml: specifier: ^1.7.0 version: 1.7.0 @@ -431,7 +431,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260622.1 + version: 7.0.0-dev.20260623.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -455,14 +455,14 @@ importers: dependencies: '@effect/platform-bun': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85) + version: 4.0.0-beta.87(effect@4.0.0-beta.87) effect: specifier: 'catalog:' - version: 4.0.0-beta.85 + version: 4.0.0-beta.87 devDependencies: '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.91(effect@4.0.0-beta.85)(vitest@4.1.9) + version: 4.0.0-beta.91(effect@4.0.0-beta.87)(vitest@4.1.9) '@tsconfig/bun': specifier: 'catalog:' version: 1.0.10 @@ -471,7 +471,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260622.1 + version: 7.0.0-dev.20260623.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -495,10 +495,10 @@ importers: dependencies: '@effect/platform-bun': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85) + version: 4.0.0-beta.87(effect@4.0.0-beta.87) '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85)(ioredis@5.11.0) + version: 4.0.0-beta.87(effect@4.0.0-beta.87)(ioredis@5.11.0) '@supabase/config': specifier: workspace:* version: link:../config @@ -507,11 +507,11 @@ importers: version: link:../process-compose effect: specifier: 'catalog:' - version: 4.0.0-beta.85 + version: 4.0.0-beta.87 devDependencies: '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.91(effect@4.0.0-beta.85)(vitest@4.1.9) + version: 4.0.0-beta.91(effect@4.0.0-beta.87)(vitest@4.1.9) '@supabase/supabase-js': specifier: ^2.108.2 version: 2.108.2 @@ -523,7 +523,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260622.1 + version: 7.0.0-dev.20260623.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -570,52 +570,52 @@ packages: resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} engines: {node: '>=18'} - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.186': - resolution: {integrity: sha512-xHlzB+61OJkLhrc5QJXVlpldwM9IXJAiQ7cCxWj9o0qu165eYtsGaAaWg9X9NAc9IWhtAdXXNpNSuiZNc+OzWw==} + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.187': + resolution: {integrity: sha512-0DNzATzaRmjS/Q1T4T9SLP/VT67gRX7J4reYPnEdcTm91lJwjqOPkHr17+sv+7DoerZ421ZG38dPsQd/V67qQw==} cpu: [arm64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.186': - resolution: {integrity: sha512-+TJSWfoifLLW+7EEbvE4TIHbCj39PL8zEhL1gUudWQjLAgKxWeti+3h4FRDhPI1B/Uwz1eh/eY430od0iMXjfw==} + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.187': + resolution: {integrity: sha512-Va3O8lTDa9IHq/DbSJwU63JJknNV3YCBh4PEznOHXJtyFoXHgRjrks4QQvN3baZm79avVq2sn+6tvpTz9CuY8A==} cpu: [x64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.186': - resolution: {integrity: sha512-pLEaVXulWqHHEgfTwK/5EILSlxXMN5tRA54Ff0tRmEP/FGye8WhLMYrUqg8TSsM2e1bJSszRbyyJSK10xr9Qtg==} + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.187': + resolution: {integrity: sha512-XQ7w2pX0mjPYUC7ayTKiHeAbePOny/ezBATxTWt5JVDZZPfljzvHA0jyXHsN8aLphbgRUfbUpdrtt5b57Bhx5g==} cpu: [arm64] os: [linux] libc: [musl] - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.186': - resolution: {integrity: sha512-bkWmXR3PWcBTrAWAhmJn7+7/ONq/5sIEDe30D6a76qx6Xn8sZICmy9GrbUJec0Mb+XVifcS8LnLjP/Z5GzMc/g==} + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.187': + resolution: {integrity: sha512-VEvrs3MgwckdWRNa7+2rJdyOBbCWGoRaM6OnL+/n9qi4gKlZnXZlj/90Tw9o8ttcN260Opz120/SE1fYwzu/JA==} cpu: [arm64] os: [linux] libc: [glibc] - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.186': - resolution: {integrity: sha512-Zg7htykMkMdQC/00UOPn/gnRJRyDaoY0AeJnyXLUByKEUd0ARshpQEadoJoAS6D//6CscffWaSTsX2ePSvl+aw==} + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.187': + resolution: {integrity: sha512-yi2/L5oztFqTDnAZl4mWOINr+r7WBot70gYUxG2jOoqdYUl6j50vG/ME605X80v+l2qBmKjSGxb5trY/6DkRzQ==} cpu: [x64] os: [linux] libc: [musl] - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.186': - resolution: {integrity: sha512-ARPQwIliHwypU5FQcq4Epi5ahmbSJt89Use5BgzxyeDyrIM3NgK/0c1IKp8DAiKPNntVXNT5R01TwoHdNI3oBA==} + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.187': + resolution: {integrity: sha512-s/Fmg29tzMrbdeCbseTpL3DlzfWineSQ3bDPvhdKw4jcsgLC1YgQhIkAyE8WlceUyhQyw82NAUgYoPwfGny6hQ==} cpu: [x64] os: [linux] libc: [glibc] - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.186': - resolution: {integrity: sha512-P/OMuYtKYlgGYs0wMTGCSo8gQfCETfA+0+lGGMAcPMH1xxOn24gjtQ/fLQKdaVh+0DLaSxjYm9W3Ln5jN6ALlQ==} + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.187': + resolution: {integrity: sha512-HjEl3cDRLAWKopdlrLI54fxTVWq4lZpWA4bTTBVc9UDdDj6AmJIRHKxaCCYLQRvnoswbAUF1sJmJ8EFJ+b6lfw==} cpu: [arm64] os: [win32] - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.186': - resolution: {integrity: sha512-aiBJu0rhlU/gvUsNtwxjIoj377Wj+g3HqoUe6eihcLGbvsR0SE5KpQaYT/B7wspRILegwIM+iUV9K7145SW6sA==} + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.187': + resolution: {integrity: sha512-FnrLhoZ8y/+gkZynL12UeQ8pWKCu6B/8myyRrYMNJRZqFw4DZlPvPCywAjFZf1e1hiXCoiSK0Orl5wzKfAeQsQ==} cpu: [x64] os: [win32] - '@anthropic-ai/claude-agent-sdk@0.3.186': - resolution: {integrity: sha512-TbxhqYPDNluWL5C50pyHbUy2wWtwJs4iR8qQOxeVkRRTUWn3rdchzpSA8fLWiU+iyiyLDgxfPs6E79OWqrJ8Pw==} + '@anthropic-ai/claude-agent-sdk@0.3.187': + resolution: {integrity: sha512-HHkuC3he/Wi8fX/WjfS8mJr4TFPGoAd1QyxOuTwjnyOLboGkX1H+UhBYLxHX1ouFvHnYVJglB2wsuWemVQgahQ==} engines: {node: '>=18.0.0'} peerDependencies: '@anthropic-ai/sdk': '>=0.93.0' @@ -724,17 +724,17 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 - '@effect/atom-react@4.0.0-beta.85': - resolution: {integrity: sha512-DsBau/iXF8GN6lpbObhwTsP0HcqzoaSJWzbewIUHp/SY2YeQV2bvo0IfPa0dhfwACZHZmhgzdPUaD7SpFyukmg==} + '@effect/atom-react@4.0.0-beta.87': + resolution: {integrity: sha512-obmHFW1PQDsb5vet/qaQuByGZL9htoZ7TgShh1E9xpKPZ2JoQNMibkytDyAg48E/s02FVGJxGlvCdEU8PBnTkA==} peerDependencies: - effect: ^4.0.0-beta.85 + effect: ^4.0.0-beta.87 react: ^19.2.4 scheduler: '*' - '@effect/platform-bun@4.0.0-beta.85': - resolution: {integrity: sha512-hSOCmUiIFQ6uWGlmfKF0ZS3dK2VQLLhrlTJU3I9rqQTfwDVGUUeoVR8I2wvmBZercYLClt/OsLTR6KQ1RbCZkA==} + '@effect/platform-bun@4.0.0-beta.87': + resolution: {integrity: sha512-pbV1Nos/RNVuHxpW5aigoASyXgRDKuCc6NxzH3my5RqZVV8DeJ7NhgQrRnlBDX0qBJYdfG8KSYDwj+2qgZ3W8Q==} peerDependencies: - effect: ^4.0.0-beta.85 + effect: ^4.0.0-beta.87 '@effect/platform-node-shared@4.0.0-beta.91': resolution: {integrity: sha512-JX++yhomaOEpQmnTYuhn+pKAgKg5MQvycAf1rXGtyVKi202Nc5nK37xbywDcugfwrc1FrPxrxa3BY3lbSc74cw==} @@ -742,17 +742,17 @@ packages: peerDependencies: effect: ^4.0.0-beta.91 - '@effect/platform-node@4.0.0-beta.85': - resolution: {integrity: sha512-tfZ/nUGB6gsw/b7ZQuhIv6w0iXNKOYj5m7dltd3SouY3y1BvIYrS/KxIUi4s3w8Qfx83i9l17q2Wa2b9Fzth5A==} + '@effect/platform-node@4.0.0-beta.87': + resolution: {integrity: sha512-bmW72GSCWcvsQStO39BmRgwcQ+311qXm9/MjllS6MgtYP0Id0Ivlysj5L+YTjCRwfJAimmlZEWBphWTwbA+bXw==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.85 + effect: ^4.0.0-beta.87 ioredis: ^5.7.0 - '@effect/sql-pg@4.0.0-beta.85': - resolution: {integrity: sha512-EqyvyPf2AgOhQzr+c9Enupn3/byVBQB6W3GdHXLPx/UBMt/xPudpI/lMXnU+PFG9EajN1TAEuCCWW8hiiEgAvg==} + '@effect/sql-pg@4.0.0-beta.87': + resolution: {integrity: sha512-lKWYzw+zwzycfbrU3SQRwshKHt7LIotO6u/KyLwsk/oqNDbqQExhVkIKE6OrM3NvfjqyF6SJMWquBRsnANKkcg==} peerDependencies: - effect: ^4.0.0-beta.85 + effect: ^4.0.0-beta.87 '@effect/vitest@4.0.0-beta.91': resolution: {integrity: sha512-RTowh92EawJWk+PewFrbqmPSOjbJ9xcq4BcWGV4t0aaYIvZj5g5dubXUvCioh6aq4lkOnTEJmC6seGZjDFO/MQ==} @@ -2107,11 +2107,11 @@ packages: resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==} engines: {node: '>=12'} - '@posthog/core@1.38.0': - resolution: {integrity: sha512-QLrJh0hMVpEJXHNyiJR9YhvIO5tzLDedw4UHdvB+ub4fXHMtHCA8H44qgl11HWR3ajpjxtJ8hs3HyOGVF3Zc1w==} + '@posthog/core@1.39.2': + resolution: {integrity: sha512-G/TL5Xykl+9xCDtBwHHPRSToS/43UjzyxKOfhlTwu/hJHt3v39Q2OFu8hdum71Iu7+tuxxISGVcFMlIST7ksjA==} - '@posthog/types@1.391.1': - resolution: {integrity: sha512-ASwd7Nf4pViqdYRYaNRyPYRVKWa1CcHUAUWR0XeQJLGdNnsWACBwe0sSieb/cHnKsRXjRwO/23KIY83lm/Ccpw==} + '@posthog/types@1.392.0': + resolution: {integrity: sha512-nctNujXL3FC1v99FktaTMSugSD9ZOZekEpahUSafkU2TSvW+XGKNkQZbokuJtiWvPBK208dwMJva8UfBkChqpw==} '@radix-ui/number@1.1.2': resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==} @@ -2864,50 +2864,50 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260622.1': - resolution: {integrity: sha512-g/fjmdh/i/ngSmUeWNHh98iuONzW+kUaqwZRaZCmGkssBJ6ZrJAsyg85cm1t4aK9ne5nNjbjNPzJFTZ+QNMSRw==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260623.1': + resolution: {integrity: sha512-8AX9NwC+G6Sbh5hNLnx8YgxoRV/8BH8FQRtZ86OTtUQfESMRvwszOGTtfcC32g86O5jsEQPDHXKVSnsIzWh6lg==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260622.1': - resolution: {integrity: sha512-AqRrnPQCR+7j6u6uC6eQpacJ/btxZW6ktpWxPj7byoyk6kGSSgUb0YNcZ2Iao2brdbYw4LOxVcrVW4mP+K7sxg==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260623.1': + resolution: {integrity: sha512-ef+JmOjtTu0BRdhAq69WbzCWjZsG6gqS8xDI4fKABdWXlrVojPkrD2OObog3PC7Qte8us6QspQvvnKTf7yKFSQ==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260622.1': - resolution: {integrity: sha512-sxo7AlhQiX5J5WYWBxSBjJ02nA2kiO8j+K51fuF24nmbQRtSLjTa8/BimpYbBywTAsSps8vgnJjsLONf243HYw==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260623.1': + resolution: {integrity: sha512-xb9m0WXvIPHOogGTC02iBv3cwRJ7q+Ql2ABcsu4kHYc1p02JSbIBEfu3g58TpcFZyODsO+8WaDylx/hBiGzlVw==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260622.1': - resolution: {integrity: sha512-XnDHJZWFY2M8DCzxXj598Ql9op+zYpk6k5X3FYB4KeOXpdzjAke2H9wKW7UKeYfvPJyAgvyS+IapcvogCwx13A==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260623.1': + resolution: {integrity: sha512-8f9dT1RaOK3Wy/puboAcCik4HBX6mfWt5hOMipWehDfInNFI6XP3ZYErShowBQ0Tkln5qdGqDIoq452PO/ZwCA==} engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260622.1': - resolution: {integrity: sha512-41TR69UuB1be2DmjcavcUMoFipIQNVpqsm1FvVCtGnscL41pDa/ijXHnSwzn7v8EbCgAI7hRUhRsPNPGY3OCKQ==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260623.1': + resolution: {integrity: sha512-AZRp6q0SyVw9xe9kpyfTmavqkQKizDhmOLglDHbxAu4Y3inL6r2GSE7q3w/eO4R259dIwl5TM52DZKK8onoT6Q==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260622.1': - resolution: {integrity: sha512-TMdYylCqBvQOVz6x5dT1HQ/49eLBijNE/uMGD2CA3YR4Hp4NkG+Rqg46NZiaO48D2+sjZws2uoNtPhfefN9IdA==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260623.1': + resolution: {integrity: sha512-DXRMXOe/JEmf2CULZZn1xvRuJrnZ9DxzDubxtW85pE0aSLgl+VuplWCsZrBxaCZDrWMRIfMDV8Y0y+Yftx/jeQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260622.1': - resolution: {integrity: sha512-ROz8ncFZsMC2nn8ClBZ3wLNXpea6BbdMYyNTWx8xYrikhOIgFlkmAdc10PA/MiOh78RraUbdWvwbXNe19He+iQ==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260623.1': + resolution: {integrity: sha512-kYB1I6tdxWHjhSt8B2v9CHmpOJwJvD88LUCxiBS9C59pwrGe+LAwZvbTTJxvULNaUEjxdiMomYerWWhD/09Y3g==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260622.1': - resolution: {integrity: sha512-t4XoYz/Jr1Jt7wkaHwe4e/r3OcYgKxvL0dqfZsnBmqG8HsJ57VpOwd0PINU5b5umcb4g9akVto028L70lAJUpA==} + '@typescript/native-preview@7.0.0-dev.20260623.1': + resolution: {integrity: sha512-33AkAhmRu1/w4nRFLnJ9lic7FSzW4zWfU1u5DP7A2+j1445SSNvf3Q4WGtDzdAR5HSlk8vXEa0B4xUHR5vM1tg==} engines: {node: '>=16.20.0'} hasBin: true @@ -3670,8 +3670,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@4.0.0-beta.85: - resolution: {integrity: sha512-Cjv9YQyv4CiIccmRAQIWAoeESCpCpiuHYY8zb5vqiYs3Ac2yE5RQAnKq7z4Ir/3VJYZb8kxh6rS8czDDnpTdkQ==} + effect@4.0.0-beta.87: + resolution: {integrity: sha512-GANlkUQo9pPN4tIOeUhF/gdgBq4OA1w5lUstT/GjS3Csd48Y6KV1FbxsEtq4/SNRBkl6JE0nU9pP9fi6/+GU7A==} ejs@5.0.1: resolution: {integrity: sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==} @@ -5659,8 +5659,8 @@ packages: postgres-range@1.1.4: resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} - posthog-node@5.38.2: - resolution: {integrity: sha512-eiKpU+vX4hVuHbO/EosvPHsmh2AVIdoVmWss/uUOs1t4b0ViCblw2o8OIFqHxKj3mYRnSOBlX0Dw3wBvcCaYpA==} + posthog-node@5.38.4: + resolution: {integrity: sha512-Jti056f+5C9y1MXb5yhxwwYBITxypWZHWcAp+UevcZ7/AKEQdSB9FiJP3WzEznw4vH3Bz0IlRKVgsofizRAoEA==} engines: {node: ^20.20.0 || >=22.22.0} peerDependencies: rxjs: ^7.0.0 @@ -6849,44 +6849,44 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.186': + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.187': optional: true - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.186': + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.187': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.186': + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.187': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.186': + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.187': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.186': + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.187': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.186': + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.187': optional: true - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.186': + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.187': optional: true - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.186': + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.187': optional: true - '@anthropic-ai/claude-agent-sdk@0.3.186(@anthropic-ai/sdk@0.105.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': + '@anthropic-ai/claude-agent-sdk@0.3.187(@anthropic-ai/sdk@0.105.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': dependencies: '@anthropic-ai/sdk': 0.105.0(zod@4.4.3) '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) zod: 4.4.3 optionalDependencies: - '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.186 - '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.186 - '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.186 - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.186 - '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.186 - '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.186 - '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.186 - '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.186 + '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.187 + '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.187 + '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.187 + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.187 + '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.187 + '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.187 + '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.187 + '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.187 '@anthropic-ai/sdk@0.105.0(zod@4.4.3)': dependencies: @@ -7037,33 +7037,33 @@ snapshots: dependencies: '@noble/ciphers': 1.3.0 - '@effect/atom-react@4.0.0-beta.85(effect@4.0.0-beta.85)(react@19.2.7)(scheduler@0.27.0)': + '@effect/atom-react@4.0.0-beta.87(effect@4.0.0-beta.87)(react@19.2.7)(scheduler@0.27.0)': dependencies: - effect: 4.0.0-beta.85 + effect: 4.0.0-beta.87 react: 19.2.7 scheduler: 0.27.0 - '@effect/platform-bun@4.0.0-beta.85(effect@4.0.0-beta.85)': + '@effect/platform-bun@4.0.0-beta.87(effect@4.0.0-beta.87)': dependencies: - '@effect/platform-node-shared': 4.0.0-beta.91(effect@4.0.0-beta.85) - effect: 4.0.0-beta.85 + '@effect/platform-node-shared': 4.0.0-beta.91(effect@4.0.0-beta.87) + effect: 4.0.0-beta.87 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node-shared@4.0.0-beta.91(effect@4.0.0-beta.85)': + '@effect/platform-node-shared@4.0.0-beta.91(effect@4.0.0-beta.87)': dependencies: '@types/ws': 8.18.1 - effect: 4.0.0-beta.85 + effect: 4.0.0-beta.87 ws: 8.21.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node@4.0.0-beta.85(effect@4.0.0-beta.85)(ioredis@5.11.0)': + '@effect/platform-node@4.0.0-beta.87(effect@4.0.0-beta.87)(ioredis@5.11.0)': dependencies: - '@effect/platform-node-shared': 4.0.0-beta.91(effect@4.0.0-beta.85) - effect: 4.0.0-beta.85 + '@effect/platform-node-shared': 4.0.0-beta.91(effect@4.0.0-beta.87) + effect: 4.0.0-beta.87 ioredis: 5.11.0 mime: 4.1.0 undici: 8.5.0 @@ -7071,9 +7071,9 @@ snapshots: - bufferutil - utf-8-validate - '@effect/sql-pg@4.0.0-beta.85(effect@4.0.0-beta.85)': + '@effect/sql-pg@4.0.0-beta.87(effect@4.0.0-beta.87)': dependencies: - effect: 4.0.0-beta.85 + effect: 4.0.0-beta.87 pg: 8.22.0 pg-connection-string: 2.12.0 pg-cursor: 2.20.0(pg@8.22.0) @@ -7082,9 +7082,9 @@ snapshots: transitivePeerDependencies: - pg-native - '@effect/vitest@4.0.0-beta.91(effect@4.0.0-beta.85)(vitest@4.1.9)': + '@effect/vitest@4.0.0-beta.91(effect@4.0.0-beta.87)(vitest@4.1.9)': dependencies: - effect: 4.0.0-beta.85 + effect: 4.0.0-beta.87 vitest: 4.1.9(@types/node@26.0.1)(@vitest/coverage-istanbul@4.1.9)(vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) '@emnapi/core@1.10.0': @@ -8002,11 +8002,11 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@posthog/core@1.38.0': + '@posthog/core@1.39.2': dependencies: - '@posthog/types': 1.391.1 + '@posthog/types': 1.392.0 - '@posthog/types@1.391.1': {} + '@posthog/types@1.392.0': {} '@radix-ui/number@1.1.2': {} @@ -8742,36 +8742,36 @@ snapshots: dependencies: '@types/node': 26.0.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260622.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260623.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260622.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260623.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260622.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260623.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260622.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260623.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260622.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260623.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260622.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260623.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260622.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260623.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260622.1': + '@typescript/native-preview@7.0.0-dev.20260623.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260622.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260622.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260622.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260622.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260622.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260622.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260622.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260623.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260623.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260623.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260623.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260623.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260623.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260623.1 '@ungap/structured-clone@1.3.2': {} @@ -9587,7 +9587,7 @@ snapshots: ee-first@1.1.1: {} - effect@4.0.0-beta.85: + effect@4.0.0-beta.87: dependencies: '@standard-schema/spec': 1.1.0 fast-check: 4.8.0 @@ -12060,9 +12060,9 @@ snapshots: postgres-range@1.1.4: {} - posthog-node@5.38.2: + posthog-node@5.38.4: dependencies: - '@posthog/core': 1.38.0 + '@posthog/core': 1.39.2 pretty-ms@9.3.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3c3edfd134..314ddf0419 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,25 +12,25 @@ allowBuilds: sharp: true catalog: - "@effect/atom-react": "4.0.0-beta.85" - "@effect/platform-bun": "4.0.0-beta.85" - "@effect/platform-node": "4.0.0-beta.85" - "@effect/sql-pg": "4.0.0-beta.85" + "@effect/atom-react": "4.0.0-beta.87" + "@effect/platform-bun": "4.0.0-beta.87" + "@effect/platform-node": "4.0.0-beta.87" + "@effect/sql-pg": "4.0.0-beta.87" "@effect/vitest": "^4.0.0-beta.85" "@nx/devkit": "^23.0.0" "@swc-node/register": "^1.10.9" "@swc/core": "^1.15.43" "@tsconfig/bun": "^1.0.10" "@types/bun": "^1.3.14" - "@typescript/native-preview": "7.0.0-dev.20260622.1" + "@typescript/native-preview": "7.0.0-dev.20260623.1" "@vitest/coverage-istanbul": "^4.1.9" - "effect": "4.0.0-beta.85" + "effect": "4.0.0-beta.87" "knip": "^6.17.1" "nx": "^23.0.0" "oxfmt": "^0.56.0" "oxlint": "^1.70.0" "oxlint-tsgolint": "^0.23.0" - "tldts": "^7.4.3" + "tldts": "^7.4.4" "vitest": "^4.1.9" blockExoticSubdeps: true From b83f2e0db4e804b7505302c8e713c72d9d6597e9 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Wed, 1 Jul 2026 10:47:53 +0200 Subject: [PATCH 08/48] ci(cli): sync releases to Linear (#5727) Fixes CLI-1541 Adds Linear release synchronization to the CLI release workflow after the GitHub Release is published. Stable releases sync when the stable Linear pipeline key is configured, and beta prereleases sync only when the beta pipeline key is configured. The release sync attaches links back to the GitHub Release and the release workflow run so Linear releases have the published artifact context reviewers need. --- .github/workflows/release-shared.yml | 29 ++++++++++++++++++++++++++++ .github/workflows/release.yml | 2 ++ 2 files changed, 31 insertions(+) diff --git a/.github/workflows/release-shared.yml b/.github/workflows/release-shared.yml index aabe7861cf..f79b5c1f6b 100644 --- a/.github/workflows/release-shared.yml +++ b/.github/workflows/release-shared.yml @@ -55,6 +55,10 @@ on: required: false DF_FIREWALL_TOKEN: required: false + LINEAR_CLI_STABLE_RELEASE_ACCESS_KEY: + required: false + LINEAR_CLI_BETA_RELEASE_ACCESS_KEY: + required: false jobs: build-blacksmith: name: Build CLI artifacts (Blacksmith) @@ -270,6 +274,8 @@ jobs: CHANNEL: ${{ inputs.channel }} NPM_TAG: ${{ inputs.npm_tag }} VERSION: ${{ inputs.version }} + LINEAR_CLI_STABLE_RELEASE_ACCESS_KEY: ${{ secrets.LINEAR_CLI_STABLE_RELEASE_ACCESS_KEY }} + LINEAR_CLI_BETA_RELEASE_ACCESS_KEY: ${{ secrets.LINEAR_CLI_BETA_RELEASE_ACCESS_KEY }} permissions: contents: write id-token: write @@ -287,6 +293,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: true + fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} - name: Setup @@ -409,6 +416,28 @@ jobs: GH_TOKEN: ${{ github.token }} run: gh release edit "v${VERSION}" --draft=false + - name: Sync stable release to Linear + if: ${{ inputs.channel == 'stable' && env.LINEAR_CLI_STABLE_RELEASE_ACCESS_KEY != '' }} + uses: linear/linear-release-action@c0cb8354a362c24c6d3e0948f37fd66d07588e3f # v0 + with: + access_key: ${{ env.LINEAR_CLI_STABLE_RELEASE_ACCESS_KEY }} + version: v${{ inputs.version }} + name: Supabase CLI v${{ inputs.version }} + links: | + GitHub Release=https://github.com/${{ github.repository }}/releases/tag/v${{ inputs.version }} + Release workflow=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Sync beta release to Linear + if: ${{ inputs.channel == 'beta' && env.LINEAR_CLI_BETA_RELEASE_ACCESS_KEY != '' }} + uses: linear/linear-release-action@c0cb8354a362c24c6d3e0948f37fd66d07588e3f # v0 + with: + access_key: ${{ env.LINEAR_CLI_BETA_RELEASE_ACCESS_KEY }} + version: v${{ inputs.version }} + name: Supabase CLI v${{ inputs.version }} + links: | + GitHub Release=https://github.com/${{ github.repository }}/releases/tag/v${{ inputs.version }} + Release workflow=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} + # Changelog backfill only needs tag + published GH release (from publish). # Runs in parallel with brew/scoop/smoke; must not gate distribution. backfill-release-notes: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 276c8c5add..ccfd511d6a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -225,6 +225,8 @@ jobs: GH_APP_PRIVATE_KEY: ${{ secrets.GH_APP_PRIVATE_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} DF_FIREWALL_TOKEN: ${{ secrets.DF_FIREWALL_TOKEN }} + LINEAR_CLI_STABLE_RELEASE_ACCESS_KEY: ${{ secrets.LINEAR_CLI_STABLE_RELEASE_ACCESS_KEY }} + LINEAR_CLI_BETA_RELEASE_ACCESS_KEY: ${{ secrets.LINEAR_CLI_BETA_RELEASE_ACCESS_KEY }} # Posts to the release Slack channel once the pipeline succeeds. Listing # `release` in `needs` without a status function in `if:` keeps the implicit From bc2f17ead856358de657cfa578beb38e146cdcb5 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Wed, 1 Jul 2026 11:11:15 +0200 Subject: [PATCH 09/48] ci(release): restore action/cache release handoff (#5748) ## Summary Reverts the release workflow handoff from uploaded artifacts back to `actions/cache` now that the repository has more GitHub cache capacity. This restores the previous run-scoped cache keys for CLI build outputs across smoke, publish, Homebrew, Scoop, and preview-package jobs while keeping the newer pinned `actions/cache` version from the dependency bump. Reverts #5740 --- .github/workflows/build-cli-artifacts.yml | 37 ++++---- .../publish-preview-cli-packages.yml | 11 ++- .github/workflows/release-shared.yml | 84 ++++++++++++------- 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/.github/workflows/build-cli-artifacts.yml b/.github/workflows/build-cli-artifacts.yml index 1b6a22900d..336c125cbf 100644 --- a/.github/workflows/build-cli-artifacts.yml +++ b/.github/workflows/build-cli-artifacts.yml @@ -21,8 +21,8 @@ on: required: false type: string default: blacksmith-32vcpu-ubuntu-2404 - artifact_name_suffix: - description: Suffix to distinguish build artifact producers (e.g. -github) + cache_key_suffix: + description: Suffix to distinguish build artifact cache producers required: false type: string default: "" @@ -123,26 +123,23 @@ jobs: echo "Checking dist/..." ls -la dist/ + - name: Check existing build artifacts cache + id: build-artifacts-cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + packages/cli-*/bin/ + dist/ + key: cli-build-${{ github.run_id }}-${{ inputs.shell }}-${{ inputs.version }}${{ inputs.cache_key_suffix }}-v1 + enableCrossOsArchive: true + lookup-only: true - # Hand the build off to the smoke/publish/brew/scoop jobs via a run-scoped - # artifact rather than a cache. Caches share a 10 GB per-repo budget and - # are evicted LRU, so a large build cache could vanish mid-run between the - # producer and a later consumer (e.g. publish), failing the restore. - # Artifacts have their own deterministic retention and survive job re-runs - # within the run, which is exactly what this handoff needs. - - name: Upload build artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + - name: Save build artifacts cache + if: steps.build-artifacts-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - name: cli-build-${{ inputs.shell }}-${{ inputs.version }}${{ inputs.artifact_name_suffix }} path: | packages/cli-*/bin/ dist/ - # Intra-run handoff, not a kept deliverable — expire it the next day. - retention-days: 1 - # A full re-run of this job replaces its own artifact instead of - # failing on the duplicate name from the previous attempt. - overwrite: true - # dist/* is already compressed (tar.gz/zip/deb/rpm/apk); a light level - # trims the raw bin/ binaries without burning CPU re-packing the rest. - compression-level: 1 - if-no-files-found: error + key: cli-build-${{ github.run_id }}-${{ inputs.shell }}-${{ inputs.version }}${{ inputs.cache_key_suffix }}-v1 + enableCrossOsArchive: true diff --git a/.github/workflows/publish-preview-cli-packages.yml b/.github/workflows/publish-preview-cli-packages.yml index 153b75f252..e7486fc5f7 100644 --- a/.github/workflows/publish-preview-cli-packages.yml +++ b/.github/workflows/publish-preview-cli-packages.yml @@ -57,10 +57,15 @@ jobs: with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - - name: Download preview build artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Restore preview build artifacts cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - name: cli-build-legacy-${{ env.PREVIEW_VERSION }} + path: | + packages/cli-*/bin/ + dist/ + key: cli-build-${{ github.run_id }}-legacy-${{ env.PREVIEW_VERSION }}-v1 + enableCrossOsArchive: true + fail-on-cache-miss: true - name: Prepare package files run: | diff --git a/.github/workflows/release-shared.yml b/.github/workflows/release-shared.yml index f79b5c1f6b..79a3175805 100644 --- a/.github/workflows/release-shared.yml +++ b/.github/workflows/release-shared.yml @@ -79,7 +79,7 @@ jobs: version: ${{ inputs.version }} shell: ${{ inputs.shell }} runner: large-linux-x86 - artifact_name_suffix: -github + cache_key_suffix: -github timeout_minutes: 45 build_timeout_minutes: 20 secrets: @@ -113,10 +113,15 @@ jobs: with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - - name: Download build artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Restore build artifacts cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - name: cli-build-${{ inputs.shell }}-${{ inputs.version }} + path: | + packages/cli-*/bin/ + dist/ + key: cli-build-${{ github.run_id }}-${{ inputs.shell }}-${{ inputs.version }}-v1 + enableCrossOsArchive: true + fail-on-cache-miss: true # Docker's classic image store keeps a single platform manifest per # tag, so pulling `alpine:3.21` for amd64 and again for arm64 leaves @@ -244,10 +249,15 @@ jobs: with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - - name: Download build artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Restore build artifacts cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - name: cli-build-${{ inputs.shell }}-${{ inputs.version }}-github + path: | + packages/cli-*/bin/ + dist/ + key: cli-build-${{ github.run_id }}-${{ inputs.shell }}-${{ inputs.version }}-github-v1 + enableCrossOsArchive: true + fail-on-cache-miss: true - name: Fix binary permissions run: chmod +x packages/cli-*/bin/supabase || true @@ -301,17 +311,15 @@ jobs: with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - - name: Download build artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Restore build artifacts cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - name: cli-build-${{ inputs.shell }}-${{ inputs.version }}-github - - # Artifacts are zipped and do not carry Unix permissions, so the compiled - # binaries arrive without the executable bit. publish.ts ships - # packages/cli-*/bin/supabase to npm verbatim, so restore +x before - # publishing or the installed CLI would not be runnable. - - name: Fix binary permissions - run: chmod +x packages/cli-*/bin/supabase || true + path: | + packages/cli-*/bin/ + dist/ + key: cli-build-${{ github.run_id }}-${{ inputs.shell }}-${{ inputs.version }}-github-v1 + enableCrossOsArchive: true + fail-on-cache-miss: true - name: Sync versions run: pnpm exec bun apps/cli/scripts/sync-versions.ts --version "${VERSION}" @@ -471,6 +479,8 @@ jobs: publish-homebrew: needs: publish if: ${{ !inputs.dry_run && inputs.publish_brew_scoop }} + # github-hosted to share a cache store with build-github/publish, whose + # -github-v1 artifacts this job's checksums must match. runs-on: ubuntu-latest timeout-minutes: 30 env: @@ -487,16 +497,21 @@ jobs: with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - # Must download the github-hosted build (-github), the same artifacts the - # publish job uploads to the GitHub Release. The Bun-compiled binaries are - # not byte-for-byte reproducible across the blacksmith and github builds, - # so the blacksmith dist/checksums.txt does not match the released + # Must restore the github-hosted build (-github-v1), the same artifacts + # the publish job uploads to the GitHub Release. The Bun-compiled binaries + # are not byte-for-byte reproducible across the blacksmith and github + # builds, so the blacksmith dist/checksums.txt does not match the released # tarballs. Reading it here produced a formula whose sha256 rejected the # downloaded archive ("Formula reports different checksum"). - - name: Download build artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Restore build artifacts cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - name: cli-build-${{ inputs.shell }}-${{ inputs.version }}-github + path: | + packages/cli-*/bin/ + dist/ + key: cli-build-${{ github.run_id }}-${{ inputs.shell }}-${{ inputs.version }}-github-v1 + enableCrossOsArchive: true + fail-on-cache-miss: true - name: Generate Homebrew tap token id: app-token @@ -527,6 +542,8 @@ jobs: publish-scoop: needs: publish if: ${{ !inputs.dry_run && inputs.publish_brew_scoop }} + # github-hosted to share a cache store with build-github/publish, whose + # -github-v1 artifacts this job's checksums must match. runs-on: ubuntu-latest timeout-minutes: 30 env: @@ -543,16 +560,21 @@ jobs: with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - # Must download the github-hosted build (-github), the same artifacts the - # publish job uploads to the GitHub Release. The Bun-compiled binaries are - # not byte-for-byte reproducible across the blacksmith and github builds, - # so the blacksmith dist/checksums.txt does not match the released + # Must restore the github-hosted build (-github-v1), the same artifacts + # the publish job uploads to the GitHub Release. The Bun-compiled binaries + # are not byte-for-byte reproducible across the blacksmith and github + # builds, so the blacksmith dist/checksums.txt does not match the released # tarballs. Reading it here would produce a manifest whose hash rejects the # downloaded archive. - - name: Download build artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Restore build artifacts cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - name: cli-build-${{ inputs.shell }}-${{ inputs.version }}-github + path: | + packages/cli-*/bin/ + dist/ + key: cli-build-${{ github.run_id }}-${{ inputs.shell }}-${{ inputs.version }}-github-v1 + enableCrossOsArchive: true + fail-on-cache-miss: true - name: Generate Scoop bucket token id: app-token From 38d2f7cb948f766ffd114ec5b41601e0d7730abd Mon Sep 17 00:00:00 2001 From: Andrew Valleteau Date: Wed, 1 Jul 2026 11:49:23 +0200 Subject: [PATCH 10/48] fix(cli): stop declarative sync hanging on edge-runtime container exit (#5714) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes `supabase db schema declarative sync --experimental` (and other pg-delta flows) hanging indefinitely at 0% CPU after the shadow-database work completes. Closes supabase/pg-toolbelt#312 There are two independent causes, both needed to unblock the command: ## 1. The Edge Runtime worker never exits The pg-delta scripts run in a one-shot Edge Runtime container and rely on the event loop draining for the worker to be destroyed and the container to stop. The catalog-export script opens a real connection pool (`createManagedPool`); when a keepalive handle stays registered after `close()` resolves, the worker never exits, so the container never stops. Only the error path force-closed the loop (`throw new Error("")`); the success path did not. Fix: force-close the event loop on the **success** path of every pg-delta script once its output is flushed, so the worker is torn down deterministically: - `templates/pgdelta_catalog_export.ts` — catalog snapshot (the path reported in #312) - `templates/pgdelta.ts` — diff SOURCE→TARGET - `templates/pgdelta_declarative_export.ts` — declarative file export - `templates/pgdelta_declarative_apply.ts` — apply declarative schema - the inline catalog-export script in `internal/db/pgcache/cache.go` — the `db start` / `db push` migrations-catalog cache path The byte-for-byte embedded copies in `legacy-pgdelta.deno-templates.ts` are kept in sync. ## 2. Following the container log stream hangs under podman With the worker now exiting, the container stops — but the parent still hung for podman users. A goroutine dump on the stuck `__catalog` subprocess showed the block in `stdcopy.StdCopy` reading the `Follow:true` Docker log stream (`DockerStreamLogs`): podman's `/containers//logs?follow` endpoint does not close when the container stops, so the read never gets EOF and blocks forever. Docker closes it, which is why this only reproduced on podman. Fix: run the bounded edge-runtime scripts via a new `DockerRunOnceWaitWithConfig`, which detects completion by polling `ContainerInspect` (reliable on podman) and then reads the buffered logs **without following**. The shared `DockerStreamLogs` (live streaming for `functions serve`) and `DockerRunOnceWithConfig` (large streaming output for `db dump`) are intentionally left unchanged, so only the bounded pg-delta/edge-runtime path changes behaviour. Regression coverage is added for both: guard tests asserting each script's success path force-closes, and a test pinning that the runner reads captured output and maps the inspected exit code without following the log stream. https://claude.ai/code/session_01XbxecW4DVmwgQB1YX321K3 --------- Co-authored-by: Claude --- apps/cli-go/internal/db/diff/diff_test.go | 10 +++ .../internal/db/diff/pgdelta_template_test.go | 48 +++++++++++++ .../internal/db/diff/templates/pgdelta.ts | 6 ++ .../diff/templates/pgdelta_catalog_export.ts | 9 +++ .../templates/pgdelta_declarative_export.ts | 6 ++ apps/cli-go/internal/db/pgcache/cache.go | 9 +++ .../db/pgcache/cache_template_test.go | 33 +++++++++ .../pgdelta/pgdelta_apply_template_test.go | 33 +++++++++ .../templates/pgdelta_declarative_apply.ts | 7 ++ apps/cli-go/internal/utils/docker.go | 68 +++++++++++++++++++ .../cli-go/internal/utils/docker_wait_test.go | 66 ++++++++++++++++++ apps/cli-go/internal/utils/edgeruntime.go | 9 ++- .../shared/legacy-pgdelta.deno-templates.ts | 8 +-- 13 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 apps/cli-go/internal/db/diff/pgdelta_template_test.go create mode 100644 apps/cli-go/internal/db/pgcache/cache_template_test.go create mode 100644 apps/cli-go/internal/pgdelta/pgdelta_apply_template_test.go create mode 100644 apps/cli-go/internal/utils/docker_wait_test.go diff --git a/apps/cli-go/internal/db/diff/diff_test.go b/apps/cli-go/internal/db/diff/diff_test.go index 2a6a2d4ca4..ca52d9e09b 100644 --- a/apps/cli-go/internal/db/diff/diff_test.go +++ b/apps/cli-go/internal/db/diff/diff_test.go @@ -345,6 +345,16 @@ create schema public`) Delete("/v" + utils.Docker.ClientVersion() + "/containers/test-shadow-db"). Reply(http.StatusOK) apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.EdgeRuntime.Image), "test-migra") + // The edge-runtime diff waits for the container to exit via inspect before + // reading its logs (it must not follow the log stream — that hangs under + // podman, supabase/pg-toolbelt#312), so the diff failure here surfaces from + // the log read rather than the followed stream. + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/containers/test-migra/json"). + Reply(http.StatusOK). + JSON(container.InspectResponse{ContainerJSONBase: &container.ContainerJSONBase{ + State: &container.State{ExitCode: 0}, + }}) gock.New(utils.Docker.DaemonHost()). Get("/v" + utils.Docker.ClientVersion() + "/containers/test-migra/logs"). ReplyError(errors.New("network error")) diff --git a/apps/cli-go/internal/db/diff/pgdelta_template_test.go b/apps/cli-go/internal/db/diff/pgdelta_template_test.go new file mode 100644 index 0000000000..3fbc2bb792 --- /dev/null +++ b/apps/cli-go/internal/db/diff/pgdelta_template_test.go @@ -0,0 +1,48 @@ +package diff + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// lastCodeLine returns the final non-blank, non-comment line of a script. +func lastCodeLine(script string) string { + lines := strings.Split(script, "\n") + for i := len(lines) - 1; i >= 0; i-- { + line := strings.TrimSpace(lines[i]) + if line == "" || strings.HasPrefix(line, "//") { + continue + } + return line + } + return "" +} + +// Every pg-delta edge-runtime script must force the worker's event loop closed +// once its output has been written. The pg connection pool can leave keepalive +// handles registered even after close() resolves; if the worker never exits, +// the container never stops and the CLI — which streams the container logs with +// Follow:true — blocks forever following them, hanging declarative sync at 0% +// CPU (supabase/pg-toolbelt#312). The success path must terminate +// unconditionally rather than rely on the event loop draining on its own, so +// guard against the force-close being dropped from any template's success path. +func TestPgDeltaScriptsForceCloseOnSuccess(t *testing.T) { + scripts := map[string]string{ + "pgdelta.ts": pgDeltaScript, + "pgdelta_declarative_export.ts": pgDeltaDeclarativeExportScript, + "pgdelta_catalog_export.ts": pgDeltaCatalogExportScript, + } + for name, script := range scripts { + t.Run(name, func(t *testing.T) { + require.NotEmpty(t, script) + // The terminating statement runs on the success path (the catch + // branch no longer re-throws), so the worker is torn down whether + // or not the body succeeded. + assert.Equal(t, `throw new Error("");`, lastCodeLine(script), + "success path must force the Edge Runtime worker to exit so the container stops") + }) + } +} diff --git a/apps/cli-go/internal/db/diff/templates/pgdelta.ts b/apps/cli-go/internal/db/diff/templates/pgdelta.ts index 306fed6a73..0fd9d00e30 100644 --- a/apps/cli-go/internal/db/diff/templates/pgdelta.ts +++ b/apps/cli-go/internal/db/diff/templates/pgdelta.ts @@ -69,3 +69,9 @@ try { // Force close event loop throw new Error(""); } +// Force close the event loop on the success path too. When SOURCE/TARGET are +// live database URLs the plan opens connections whose keepalive handles can keep +// the Edge Runtime worker alive after the diff has been written, so the container +// never exits and the CLI — which follows this container's logs — hangs +// indefinitely at 0% CPU (supabase/pg-toolbelt#312). +throw new Error(""); diff --git a/apps/cli-go/internal/db/diff/templates/pgdelta_catalog_export.ts b/apps/cli-go/internal/db/diff/templates/pgdelta_catalog_export.ts index 992c5f21a8..6b7d426ce1 100644 --- a/apps/cli-go/internal/db/diff/templates/pgdelta_catalog_export.ts +++ b/apps/cli-go/internal/db/diff/templates/pgdelta_catalog_export.ts @@ -21,7 +21,16 @@ try { console.log(stringifyCatalogSnapshot(serializeCatalog(catalog))); } catch (e) { console.error(e); + // Force close event loop throw new Error(""); } finally { await close(); } +// Force close the event loop on the success path too. The connection pool can +// leave keepalive handles registered even after close() resolves, which keeps +// the Edge Runtime worker (and therefore the container) alive after the catalog +// has already been written to stdout. The CLI streams this container's logs with +// Follow:true, so a worker that never exits hangs the parent `__catalog` +// subprocess — and the declarative-sync command that spawned it — indefinitely +// at 0% CPU (supabase/pg-toolbelt#312). +throw new Error(""); diff --git a/apps/cli-go/internal/db/diff/templates/pgdelta_declarative_export.ts b/apps/cli-go/internal/db/diff/templates/pgdelta_declarative_export.ts index 117f16c58e..4656e4690e 100644 --- a/apps/cli-go/internal/db/diff/templates/pgdelta_declarative_export.ts +++ b/apps/cli-go/internal/db/diff/templates/pgdelta_declarative_export.ts @@ -71,3 +71,9 @@ try { // Force close event loop throw new Error(""); } +// Force close the event loop on the success path too. When SOURCE/TARGET are +// live database URLs the plan opens connections whose keepalive handles can keep +// the Edge Runtime worker alive after the export has been written, so the +// container never exits and the CLI — which follows this container's logs — +// hangs indefinitely at 0% CPU (supabase/pg-toolbelt#312). +throw new Error(""); diff --git a/apps/cli-go/internal/db/pgcache/cache.go b/apps/cli-go/internal/db/pgcache/cache.go index 5b518464ba..52960c9b06 100644 --- a/apps/cli-go/internal/db/pgcache/cache.go +++ b/apps/cli-go/internal/db/pgcache/cache.go @@ -49,10 +49,19 @@ try { console.log(stringifyCatalogSnapshot(serializeCatalog(catalog))); } catch (e) { console.error(e); + // Force close event loop throw new Error(""); } finally { await close(); } +// Force close the event loop on the success path too. The connection pool can +// leave keepalive handles registered even after close() resolves, which keeps +// the Edge Runtime worker (and therefore the container) alive after the catalog +// has already been written to stdout. The CLI streams this container's logs with +// Follow:true, so a worker that never exits hangs the migrations-catalog cache +// path (db start / db push with pg-delta caching) indefinitely at 0% CPU +// (supabase/pg-toolbelt#312). +throw new Error(""); ` ) diff --git a/apps/cli-go/internal/db/pgcache/cache_template_test.go b/apps/cli-go/internal/db/pgcache/cache_template_test.go new file mode 100644 index 0000000000..1118853597 --- /dev/null +++ b/apps/cli-go/internal/db/pgcache/cache_template_test.go @@ -0,0 +1,33 @@ +package pgcache + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// The migrations-catalog cache script (db start / db push with pg-delta caching) +// opens a connection pool and must force the worker's event loop closed once it +// has written its snapshot. If a keepalive handle lingers after close() resolves +// the worker never exits, so the container never stops and the CLI — which +// follows the container logs with Follow:true — hangs indefinitely at 0% CPU +// (supabase/pg-toolbelt#312). Guard against the success-path force-close being +// dropped. +func TestPgDeltaCatalogExportScriptForceClosesOnSuccess(t *testing.T) { + require.NotEmpty(t, pgDeltaCatalogExportTS) + + lines := strings.Split(pgDeltaCatalogExportTS, "\n") + last := "" + for i := len(lines) - 1; i >= 0; i-- { + line := strings.TrimSpace(lines[i]) + if line == "" || strings.HasPrefix(line, "//") { + continue + } + last = line + break + } + assert.Equal(t, `throw new Error("");`, last, + "success path must force the Edge Runtime worker to exit so the container stops") +} diff --git a/apps/cli-go/internal/pgdelta/pgdelta_apply_template_test.go b/apps/cli-go/internal/pgdelta/pgdelta_apply_template_test.go new file mode 100644 index 0000000000..c0a7e601f3 --- /dev/null +++ b/apps/cli-go/internal/pgdelta/pgdelta_apply_template_test.go @@ -0,0 +1,33 @@ +package pgdelta + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// The declarative-apply script connects to TARGET and must force the worker's +// event loop closed once it has written its result JSON. applyDeclarativeSchema +// can leave connection keepalive handles registered, and if the worker never +// exits the container never stops — the CLI, which follows the container logs +// with Follow:true, then hangs indefinitely at 0% CPU (supabase/pg-toolbelt#312). +// The success path must terminate unconditionally, so guard against the +// force-close being dropped. +func TestDeclarativeApplyScriptForceClosesOnSuccess(t *testing.T) { + require.NotEmpty(t, pgDeltaDeclarativeApplyScript) + + lines := strings.Split(pgDeltaDeclarativeApplyScript, "\n") + last := "" + for i := len(lines) - 1; i >= 0; i-- { + line := strings.TrimSpace(lines[i]) + if line == "" || strings.HasPrefix(line, "//") { + continue + } + last = line + break + } + assert.Equal(t, `throw new Error("");`, last, + "success path must force the Edge Runtime worker to exit so the container stops") +} diff --git a/apps/cli-go/internal/pgdelta/templates/pgdelta_declarative_apply.ts b/apps/cli-go/internal/pgdelta/templates/pgdelta_declarative_apply.ts index a6589bf2b0..9dfb07cf62 100644 --- a/apps/cli-go/internal/pgdelta/templates/pgdelta_declarative_apply.ts +++ b/apps/cli-go/internal/pgdelta/templates/pgdelta_declarative_apply.ts @@ -52,3 +52,10 @@ try { } catch (e) { throw e instanceof Error ? e : new Error(String(e)); } +// Force close the event loop on the success path. applyDeclarativeSchema opens a +// connection to TARGET whose keepalive handles can keep the Edge Runtime worker +// alive after the result JSON has been written, so the container never exits and +// the CLI — which follows this container's logs — hangs indefinitely at 0% CPU +// (supabase/pg-toolbelt#312). The catch above re-throws the real error, so this +// only runs once a successful apply has been reported on stdout. +throw new Error(""); diff --git a/apps/cli-go/internal/utils/docker.go b/apps/cli-go/internal/utils/docker.go index eff7854a4f..29d2edd397 100644 --- a/apps/cli-go/internal/utils/docker.go +++ b/apps/cli-go/internal/utils/docker.go @@ -486,6 +486,74 @@ func DockerRunOnceWithConfig(ctx context.Context, config container.Config, hostC return DockerStreamLogs(ctx, container, stdout, stderr) } +// DockerRunOnceWaitWithConfig is like DockerRunOnceWithConfig but waits for the +// container to exit and then reads its already-buffered logs WITHOUT following +// the stream. +// +// DockerRunOnceWithConfig detects completion by reading a follow=true log stream +// until EOF. That EOF never arrives under podman: its /containers//logs?follow +// endpoint does not close the response when the container stops, so +// stdcopy.StdCopy blocks on the chunked HTTP body forever and the caller hangs at +// 0% CPU (supabase/pg-toolbelt#312). Waiting on /wait for the exit code and then +// reading the log once is reliable on both Docker and podman. +// +// Use this only for short-lived containers with bounded output (e.g. the +// edge-runtime pg-delta scripts): the full log is read after exit rather than +// streamed, so it is not suitable for large streaming output such as db dump. +func DockerRunOnceWaitWithConfig(ctx context.Context, config container.Config, hostConfig container.HostConfig, networkingConfig network.NetworkingConfig, containerName string, stdout, stderr io.Writer) error { + containerId, err := DockerStart(ctx, config, hostConfig, networkingConfig, containerName) + if err != nil { + return err + } + defer DockerRemove(containerId) + exitCode, err := dockerWaitExit(ctx, containerId) + if err != nil { + return err + } + if err := DockerStreamLogsOnce(ctx, containerId, stdout, stderr); err != nil { + return err + } + switch exitCode { + case 0: + return nil + case 137: + err = ErrContainerKilled + default: + err = errors.Errorf("exit %d", exitCode) + } + return errors.Errorf("error running container: %w", err) +} + +// dockerWaitInterval is how often dockerWaitExit polls container state. Kept +// short so bounded one-shot scripts return promptly; it is a package var so +// tests can drop it to zero. +var dockerWaitInterval = 200 * time.Millisecond + +// dockerWaitExit polls container state until it stops and returns its exit code. +// +// It deliberately uses ContainerInspect rather than a followed log stream (or +// /wait) to detect completion: inspect is reliable under podman, whereas +// podman's /logs?follow endpoint does not close when the container stops, which +// is what hangs DockerStreamLogs (see DockerRunOnceWaitWithConfig). Reusing +// inspect also keeps the request surface identical to DockerStreamLogs, so the +// existing test mocks continue to apply. +func dockerWaitExit(ctx context.Context, containerId string) (int64, error) { + for { + resp, err := Docker.ContainerInspect(ctx, containerId) + if err != nil { + return 0, errors.Errorf("failed to inspect docker container: %w", err) + } + if resp.State != nil && !resp.State.Running { + return int64(resp.State.ExitCode), nil + } + select { + case <-ctx.Done(): + return 0, ctx.Err() + case <-time.After(dockerWaitInterval): + } + } +} + var ErrContainerKilled = errors.New("exit 137") func DockerStreamLogs(ctx context.Context, containerId string, stdout, stderr io.Writer, opts ...func(*container.LogsOptions)) error { diff --git a/apps/cli-go/internal/utils/docker_wait_test.go b/apps/cli-go/internal/utils/docker_wait_test.go new file mode 100644 index 0000000000..22ee32e0ee --- /dev/null +++ b/apps/cli-go/internal/utils/docker_wait_test.go @@ -0,0 +1,66 @@ +package utils + +import ( + "bytes" + "context" + "testing" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/h2non/gock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/supabase/cli/internal/testing/apitest" +) + +// DockerRunOnceWaitWithConfig must detect container completion via inspect and +// read logs without following the stream. Following the log stream to detect +// exit hangs forever under podman, whose /logs?follow endpoint never closes when +// the container stops (supabase/pg-toolbelt#312). These tests pin that the runner +// reads the captured output and maps the inspected exit code to an error. +func TestDockerRunOnceWait(t *testing.T) { + imageUrl := GetRegistryImageUrl(imageId) + + t.Run("waits for exit then reads buffered logs", func(t *testing.T) { + require.NoError(t, apitest.MockDocker(Docker)) + defer gock.OffAll() + apitest.MockDockerStart(Docker, imageUrl, containerId) + require.NoError(t, apitest.MockDockerLogs(Docker, containerId, "CATALOG\n")) + // Run test + var stdout, stderr bytes.Buffer + err := DockerRunOnceWaitWithConfig( + context.Background(), + container.Config{Image: imageUrl}, + container.HostConfig{}, + network.NetworkingConfig{}, + containerId, + &stdout, + &stderr, + ) + // Validate + assert.NoError(t, err) + assert.Equal(t, "CATALOG\n", stdout.String()) + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) + + t.Run("maps non-zero exit code to error", func(t *testing.T) { + require.NoError(t, apitest.MockDocker(Docker)) + defer gock.OffAll() + apitest.MockDockerStart(Docker, imageUrl, containerId) + require.NoError(t, apitest.MockDockerLogsExitCode(Docker, containerId, 1)) + // Run test + var stdout, stderr bytes.Buffer + err := DockerRunOnceWaitWithConfig( + context.Background(), + container.Config{Image: imageUrl}, + container.HostConfig{}, + network.NetworkingConfig{}, + containerId, + &stdout, + &stderr, + ) + // Validate + assert.ErrorContains(t, err, "error running container: exit 1") + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) +} diff --git a/apps/cli-go/internal/utils/edgeruntime.go b/apps/cli-go/internal/utils/edgeruntime.go index f5ee9e9bbb..c81169ea6f 100644 --- a/apps/cli-go/internal/utils/edgeruntime.go +++ b/apps/cli-go/internal/utils/edgeruntime.go @@ -94,7 +94,14 @@ func RunEdgeRuntimeScript(ctx context.Context, env []string, script string, bind if len(state.extraEnv) > 0 { combinedEnv = append(append([]string{}, env...), state.extraEnv...) } - if err := DockerRunOnceWithConfig( + // Wait for the container to exit and then read its logs, rather than + // following the log stream to detect completion. The edge-runtime worker is + // forced to exit once the script flushes its output, but podman's + // /logs?follow endpoint does not close when the container stops, so a + // followed read (DockerRunOnceWithConfig) hangs the CLI forever + // (supabase/pg-toolbelt#312). pg-delta script output is bounded, so reading + // the log once after exit is safe. + if err := DockerRunOnceWaitWithConfig( ctx, container.Config{ Image: Config.EdgeRuntime.Image, diff --git a/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.deno-templates.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.deno-templates.ts index 625967555d..e43e9828a8 100644 --- a/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.deno-templates.ts +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.deno-templates.ts @@ -15,19 +15,19 @@ /** `templates/pgdelta.ts` — diffs SOURCE→TARGET and prints SQL statements. */ export const legacyPgDeltaDiffScript = - 'import {\n createPlan,\n deserializeCatalog,\n formatSqlStatements,\n} from "npm:@supabase/pg-delta@1.0.0-alpha.20";\nimport { supabase } from "npm:@supabase/pg-delta@1.0.0-alpha.20/integrations/supabase";\n\nasync function resolveInput(ref: string | undefined) {\n if (!ref) {\n return null;\n }\n if (ref.startsWith("postgres://") || ref.startsWith("postgresql://")) {\n return ref;\n }\n const json = await Deno.readTextFile(ref);\n return deserializeCatalog(JSON.parse(json));\n}\n\nconst source = Deno.env.get("SOURCE");\nconst target = Deno.env.get("TARGET");\n\nconst includedSchemas = Deno.env.get("INCLUDED_SCHEMAS");\nif (includedSchemas) {\n const schemas = includedSchemas.split(",");\n const schemaFilter = {\n or: [{ "*/schema": schemas }, { "schema/name": schemas }],\n };\n // CompositionPattern `and` is valid FilterDSL; Deno\'s structural typing is strict on `or` branches.\n supabase.filter = {\n and: [supabase.filter!, schemaFilter],\n } as typeof supabase.filter;\n}\n\nconst formatOptionsRaw = Deno.env.get("FORMAT_OPTIONS");\nlet formatOptions = undefined;\nif (formatOptionsRaw) {\n formatOptions = JSON.parse(formatOptionsRaw);\n}\n\ntry {\n const result = await createPlan(\n await resolveInput(source),\n await resolveInput(target),\n {\n ...supabase,\n skipDefaultPrivilegeSubtraction: true,\n },\n );\n let statements = result?.plan.statements ?? [];\n if (formatOptions != null) {\n statements = formatSqlStatements(statements, formatOptions);\n }\n if (Deno.env.get("PGDELTA_DEBUG")) {\n console.error(\n JSON.stringify({\n statementCount: statements.length,\n source: source ? "connected" : "null",\n target: target ? "connected" : "null",\n includedSchemas: includedSchemas ?? null,\n skipDefaultPrivilegeSubtraction: true,\n }),\n );\n }\n for (const sql of statements) {\n console.log(`${sql};`);\n }\n} catch (e) {\n console.error(e);\n // Force close event loop\n throw new Error("");\n}\n'; + 'import {\n createPlan,\n deserializeCatalog,\n formatSqlStatements,\n} from "npm:@supabase/pg-delta@1.0.0-alpha.20";\nimport { supabase } from "npm:@supabase/pg-delta@1.0.0-alpha.20/integrations/supabase";\n\nasync function resolveInput(ref: string | undefined) {\n if (!ref) {\n return null;\n }\n if (ref.startsWith("postgres://") || ref.startsWith("postgresql://")) {\n return ref;\n }\n const json = await Deno.readTextFile(ref);\n return deserializeCatalog(JSON.parse(json));\n}\n\nconst source = Deno.env.get("SOURCE");\nconst target = Deno.env.get("TARGET");\n\nconst includedSchemas = Deno.env.get("INCLUDED_SCHEMAS");\nif (includedSchemas) {\n const schemas = includedSchemas.split(",");\n const schemaFilter = {\n or: [{ "*/schema": schemas }, { "schema/name": schemas }],\n };\n // CompositionPattern `and` is valid FilterDSL; Deno\'s structural typing is strict on `or` branches.\n supabase.filter = {\n and: [supabase.filter!, schemaFilter],\n } as typeof supabase.filter;\n}\n\nconst formatOptionsRaw = Deno.env.get("FORMAT_OPTIONS");\nlet formatOptions = undefined;\nif (formatOptionsRaw) {\n formatOptions = JSON.parse(formatOptionsRaw);\n}\n\ntry {\n const result = await createPlan(\n await resolveInput(source),\n await resolveInput(target),\n {\n ...supabase,\n skipDefaultPrivilegeSubtraction: true,\n },\n );\n let statements = result?.plan.statements ?? [];\n if (formatOptions != null) {\n statements = formatSqlStatements(statements, formatOptions);\n }\n if (Deno.env.get("PGDELTA_DEBUG")) {\n console.error(\n JSON.stringify({\n statementCount: statements.length,\n source: source ? "connected" : "null",\n target: target ? "connected" : "null",\n includedSchemas: includedSchemas ?? null,\n skipDefaultPrivilegeSubtraction: true,\n }),\n );\n }\n for (const sql of statements) {\n console.log(`${sql};`);\n }\n} catch (e) {\n console.error(e);\n // Force close event loop\n throw new Error("");\n}\n// Force close the event loop on the success path too. When SOURCE/TARGET are\n// live database URLs the plan opens connections whose keepalive handles can keep\n// the Edge Runtime worker alive after the diff has been written, so the container\n// never exits and the CLI — which follows this container\'s logs — hangs\n// indefinitely at 0% CPU (supabase/pg-toolbelt#312).\nthrow new Error("");\n'; /** `templates/pgdelta_declarative_export.ts` — exports declarative file payloads. */ export const legacyPgDeltaDeclarativeExportScript = - '// This script is executed inside Edge Runtime by the CLI to export a target\n// schema as declarative file payloads. It accepts either live DB URLs or\n// catalog-file references for SOURCE/TARGET, which enables cached sync flows.\nimport {\n createPlan,\n deserializeCatalog,\n exportDeclarativeSchema,\n} from "npm:@supabase/pg-delta@1.0.0-alpha.20";\nimport { supabase } from "npm:@supabase/pg-delta@1.0.0-alpha.20/integrations/supabase";\n\nasync function resolveInput(ref: string | undefined) {\n if (!ref) {\n return null;\n }\n if (ref.startsWith("postgres://") || ref.startsWith("postgresql://")) {\n return ref;\n }\n const json = await Deno.readTextFile(ref);\n return deserializeCatalog(JSON.parse(json));\n}\n\nconst source = Deno.env.get("SOURCE");\nconst target = Deno.env.get("TARGET");\n\nconst includedSchemas = Deno.env.get("INCLUDED_SCHEMAS");\nif (includedSchemas) {\n const schemas = includedSchemas.split(",");\n const schemaFilter = {\n or: [{ "*/schema": schemas }, { "schema/name": schemas }],\n };\n supabase.filter = {\n and: [supabase.filter!, schemaFilter],\n } as unknown as typeof supabase.filter;\n}\n\nconst formatOptionsRaw = Deno.env.get("FORMAT_OPTIONS");\nlet formatOptions = undefined;\nif (formatOptionsRaw) {\n formatOptions = JSON.parse(formatOptionsRaw);\n}\ntry {\n const result = await createPlan(\n await resolveInput(source),\n await resolveInput(target),\n {\n ...supabase,\n skipDefaultPrivilegeSubtraction: true,\n },\n );\n if (!result) {\n console.log(\n JSON.stringify({\n version: 1,\n mode: "declarative",\n files: [],\n }),\n );\n } else {\n const output = exportDeclarativeSchema(result, {\n integration: supabase,\n formatOptions,\n });\n console.log(\n JSON.stringify(output, (_key, value) =>\n typeof value === "bigint" ? Number(value) : value,\n ),\n );\n }\n} catch (e) {\n console.error(e);\n // Force close event loop\n throw new Error("");\n}\n'; + '// This script is executed inside Edge Runtime by the CLI to export a target\n// schema as declarative file payloads. It accepts either live DB URLs or\n// catalog-file references for SOURCE/TARGET, which enables cached sync flows.\nimport {\n createPlan,\n deserializeCatalog,\n exportDeclarativeSchema,\n} from "npm:@supabase/pg-delta@1.0.0-alpha.20";\nimport { supabase } from "npm:@supabase/pg-delta@1.0.0-alpha.20/integrations/supabase";\n\nasync function resolveInput(ref: string | undefined) {\n if (!ref) {\n return null;\n }\n if (ref.startsWith("postgres://") || ref.startsWith("postgresql://")) {\n return ref;\n }\n const json = await Deno.readTextFile(ref);\n return deserializeCatalog(JSON.parse(json));\n}\n\nconst source = Deno.env.get("SOURCE");\nconst target = Deno.env.get("TARGET");\n\nconst includedSchemas = Deno.env.get("INCLUDED_SCHEMAS");\nif (includedSchemas) {\n const schemas = includedSchemas.split(",");\n const schemaFilter = {\n or: [{ "*/schema": schemas }, { "schema/name": schemas }],\n };\n supabase.filter = {\n and: [supabase.filter!, schemaFilter],\n } as unknown as typeof supabase.filter;\n}\n\nconst formatOptionsRaw = Deno.env.get("FORMAT_OPTIONS");\nlet formatOptions = undefined;\nif (formatOptionsRaw) {\n formatOptions = JSON.parse(formatOptionsRaw);\n}\ntry {\n const result = await createPlan(\n await resolveInput(source),\n await resolveInput(target),\n {\n ...supabase,\n skipDefaultPrivilegeSubtraction: true,\n },\n );\n if (!result) {\n console.log(\n JSON.stringify({\n version: 1,\n mode: "declarative",\n files: [],\n }),\n );\n } else {\n const output = exportDeclarativeSchema(result, {\n integration: supabase,\n formatOptions,\n });\n console.log(\n JSON.stringify(output, (_key, value) =>\n typeof value === "bigint" ? Number(value) : value,\n ),\n );\n }\n} catch (e) {\n console.error(e);\n // Force close event loop\n throw new Error("");\n}\n// Force close the event loop on the success path too. When SOURCE/TARGET are\n// live database URLs the plan opens connections whose keepalive handles can keep\n// the Edge Runtime worker alive after the export has been written, so the\n// container never exits and the CLI — which follows this container\'s logs —\n// hangs indefinitely at 0% CPU (supabase/pg-toolbelt#312).\nthrow new Error("");\n'; /** `templates/pgdelta_catalog_export.ts` — serializes a catalog snapshot for caching. */ export const legacyPgDeltaCatalogExportScript = - '// This script serializes a database catalog for caching/reuse in declarative\n// sync workflows, so later diff/export operations can run from file references.\nimport {\n createManagedPool,\n extractCatalog,\n serializeCatalog,\n stringifyCatalogSnapshot,\n} from "npm:@supabase/pg-delta@1.0.0-alpha.20";\n\nconst target = Deno.env.get("TARGET");\nconst role = Deno.env.get("ROLE") ?? undefined;\n\nif (!target) {\n console.error("TARGET is required");\n throw new Error("");\n}\nconst { pool, close } = await createManagedPool(target, { role });\n\ntry {\n const catalog = await extractCatalog(pool);\n console.log(stringifyCatalogSnapshot(serializeCatalog(catalog)));\n} catch (e) {\n console.error(e);\n throw new Error("");\n} finally {\n await close();\n}\n'; + '// This script serializes a database catalog for caching/reuse in declarative\n// sync workflows, so later diff/export operations can run from file references.\nimport {\n createManagedPool,\n extractCatalog,\n serializeCatalog,\n stringifyCatalogSnapshot,\n} from "npm:@supabase/pg-delta@1.0.0-alpha.20";\n\nconst target = Deno.env.get("TARGET");\nconst role = Deno.env.get("ROLE") ?? undefined;\n\nif (!target) {\n console.error("TARGET is required");\n throw new Error("");\n}\nconst { pool, close } = await createManagedPool(target, { role });\n\ntry {\n const catalog = await extractCatalog(pool);\n console.log(stringifyCatalogSnapshot(serializeCatalog(catalog)));\n} catch (e) {\n console.error(e);\n // Force close event loop\n throw new Error("");\n} finally {\n await close();\n}\n// Force close the event loop on the success path too. The connection pool can\n// leave keepalive handles registered even after close() resolves, which keeps\n// the Edge Runtime worker (and therefore the container) alive after the catalog\n// has already been written to stdout. The CLI streams this container\'s logs with\n// Follow:true, so a worker that never exits hangs the parent `__catalog`\n// subprocess — and the declarative-sync command that spawned it — indefinitely\n// at 0% CPU (supabase/pg-toolbelt#312).\nthrow new Error("");\n'; /** `internal/pgdelta/templates/pgdelta_declarative_apply.ts` — applies declarative files to TARGET. */ export const legacyPgDeltaDeclarativeApplyScript = - '// This script applies declarative schema files to a target database and emits\n// structured JSON so the Go caller can report success/failure deterministically.\nimport {\n applyDeclarativeSchema,\n loadDeclarativeSchema,\n} from "npm:@supabase/pg-delta@1.0.0-alpha.20/declarative";\n\nconst schemaPath = Deno.env.get("SCHEMA_PATH");\nconst target = Deno.env.get("TARGET");\n\nif (!schemaPath) {\n throw new Error("SCHEMA_PATH is required");\n}\nif (!target) {\n throw new Error("TARGET is required");\n}\n\ntry {\n const content = await loadDeclarativeSchema(schemaPath);\n if (content.length === 0) {\n console.log(JSON.stringify({ status: "success", totalStatements: 0 }));\n } else {\n const result = await applyDeclarativeSchema({\n content,\n targetUrl: target,\n });\n const apply = result?.apply;\n if (!apply) {\n throw new Error("pg-delta apply returned no result");\n }\n const payload = {\n status: apply.status,\n totalStatements: result.totalStatements ?? 0,\n totalRounds: apply.totalRounds ?? 0,\n totalApplied: apply.totalApplied ?? 0,\n totalSkipped: apply.totalSkipped ?? 0,\n errors: apply.errors ?? [],\n stuckStatements: apply.stuckStatements ?? [],\n // validationErrors is populated when the final\n // check_function_bodies=on pass catches issues that didn\'t surface during\n // the initial apply rounds (e.g. a function body that references a\n // column whose type changed). Without surfacing this field, callers see\n // status=error with empty errors/stuckStatements and no actionable info.\n validationErrors: apply.validationErrors ?? [],\n diagnostics: result.diagnostics ?? [],\n };\n console.log(JSON.stringify(payload));\n if (apply.status !== "success") {\n throw new Error("pg-delta apply failed with status: " + apply.status);\n }\n }\n} catch (e) {\n throw e instanceof Error ? e : new Error(String(e));\n}\n'; + '// This script applies declarative schema files to a target database and emits\n// structured JSON so the Go caller can report success/failure deterministically.\nimport {\n applyDeclarativeSchema,\n loadDeclarativeSchema,\n} from "npm:@supabase/pg-delta@1.0.0-alpha.20/declarative";\n\nconst schemaPath = Deno.env.get("SCHEMA_PATH");\nconst target = Deno.env.get("TARGET");\n\nif (!schemaPath) {\n throw new Error("SCHEMA_PATH is required");\n}\nif (!target) {\n throw new Error("TARGET is required");\n}\n\ntry {\n const content = await loadDeclarativeSchema(schemaPath);\n if (content.length === 0) {\n console.log(JSON.stringify({ status: "success", totalStatements: 0 }));\n } else {\n const result = await applyDeclarativeSchema({\n content,\n targetUrl: target,\n });\n const apply = result?.apply;\n if (!apply) {\n throw new Error("pg-delta apply returned no result");\n }\n const payload = {\n status: apply.status,\n totalStatements: result.totalStatements ?? 0,\n totalRounds: apply.totalRounds ?? 0,\n totalApplied: apply.totalApplied ?? 0,\n totalSkipped: apply.totalSkipped ?? 0,\n errors: apply.errors ?? [],\n stuckStatements: apply.stuckStatements ?? [],\n // validationErrors is populated when the final\n // check_function_bodies=on pass catches issues that didn\'t surface during\n // the initial apply rounds (e.g. a function body that references a\n // column whose type changed). Without surfacing this field, callers see\n // status=error with empty errors/stuckStatements and no actionable info.\n validationErrors: apply.validationErrors ?? [],\n diagnostics: result.diagnostics ?? [],\n };\n console.log(JSON.stringify(payload));\n if (apply.status !== "success") {\n throw new Error("pg-delta apply failed with status: " + apply.status);\n }\n }\n} catch (e) {\n throw e instanceof Error ? e : new Error(String(e));\n}\n// Force close the event loop on the success path. applyDeclarativeSchema opens a\n// connection to TARGET whose keepalive handles can keep the Edge Runtime worker\n// alive after the result JSON has been written, so the container never exits and\n// the CLI — which follows this container\'s logs — hangs indefinitely at 0% CPU\n// (supabase/pg-toolbelt#312). The catch above re-throws the real error, so this\n// only runs once a successful apply has been reported on stdout.\nthrow new Error("");\n'; /** * The npm dist-tag/version used for `@supabase/pg-delta` when From 9fed4998a2106fd4d91f97163df2bd74a3c736ee Mon Sep 17 00:00:00 2001 From: Andrew Valleteau Date: Wed, 1 Jul 2026 14:10:09 +0200 Subject: [PATCH 11/48] ci: change live e2e schedule from hourly to daily (#5750) Update the Live E2E workflow schedule from hourly to daily execution. **Changes:** - Updated cron schedule from `"23 * * * *"` (hourly) to `"23 6 * * *"` (daily at 6:23 AM UTC) - Updated workflow documentation comments to reflect the new daily cadence - Updated finalize job comment to reference "next day" instead of "next hour" This reduces the frequency of scheduled beta channel testing from every hour to once per day, which should reduce CI resource usage while still maintaining regular validation of the `@beta` channel. https://claude.ai/code/session_01LtXPZUBWv6YFNmMGVKxtY2 Co-authored-by: Claude --- .github/workflows/live-e2e.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/live-e2e.yml b/.github/workflows/live-e2e.yml index 8b2d94e10f..a2954fcbde 100644 --- a/.github/workflows/live-e2e.yml +++ b/.github/workflows/live-e2e.yml @@ -11,7 +11,7 @@ name: Live E2E # ref (github.ref), always a same-repo branch. We deliberately take NO # free-form `ref` input: that would let a manual run check out arbitrary # (e.g. external PR) code while the staging token is in the job env. -# - schedule (hourly) — exercises the `@beta` channel. `develop` is the default +# - schedule (daily) — exercises the `@beta` channel. `develop` is the default # branch AND the beta release source, so a scheduled run checks it out and # builds from source. The `gate` job skips the run unless the published # `supabase@beta` version changed since the last green run (an actions/cache @@ -23,8 +23,8 @@ name: Live E2E on: workflow_dispatch: schedule: - # Hourly, offset from other scheduled workflows. Cron timing is best-effort. - - cron: "23 * * * *" + # Daily, offset from other scheduled workflows. Cron timing is best-effort. + - cron: "23 6 * * *" permissions: contents: read @@ -168,7 +168,7 @@ jobs: # Record that this beta tested green so the next scheduled run skips it. Needs # the whole matrix: the marker is saved only if BOTH go and ts-legacy passed - # (a red leg leaves no marker, so the next hour re-runs the same beta). + # (a red leg leaves no marker, so the next day re-runs the same beta). finalize: needs: [gate, live-e2e] if: github.event_name == 'schedule' && needs.live-e2e.result == 'success' From 39b3b8c6c39638c2f1aa57768b5781ea208d1ff7 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Wed, 1 Jul 2026 23:20:28 +0200 Subject: [PATCH 12/48] fix(cli): suggest subcommand placement for misplaced flags (#5751) ## Summary Improve CLI parser errors when a flag is passed before a subcommand even though it belongs to descendant subcommands. The formatter now inspects the command tree and appends a hint with the corrected placement, while suppressing secondary unknown-subcommand noise caused by the misplaced flag value. This fixes the confusing `network-restrictions --project-ref get --experimental` output by suggesting `network-restrictions get --project-ref `. No linked issue found. --- apps/cli/src/shared/cli/run.ts | 28 ++- .../shared/cli/subcommand-flag-suggestions.ts | 235 ++++++++++++++++++ .../subcommand-flag-suggestions.unit.test.ts | 125 ++++++++++ apps/cli/src/shared/output/json-formatter.ts | 9 +- apps/cli/src/shared/output/text-formatter.ts | 39 ++- .../shared/output/text-formatter.unit.test.ts | 56 +++++ 6 files changed, 484 insertions(+), 8 deletions(-) create mode 100644 apps/cli/src/shared/cli/subcommand-flag-suggestions.ts create mode 100644 apps/cli/src/shared/cli/subcommand-flag-suggestions.unit.test.ts create mode 100644 apps/cli/src/shared/output/text-formatter.unit.test.ts diff --git a/apps/cli/src/shared/cli/run.ts b/apps/cli/src/shared/cli/run.ts index be9eb45706..1b29aba459 100644 --- a/apps/cli/src/shared/cli/run.ts +++ b/apps/cli/src/shared/cli/run.ts @@ -78,10 +78,28 @@ export function shouldUseGlobalSignalInterrupt(args: ReadonlyArray): boo ); } -function formatterLayerFor(format: OutputFormat) { +function formatterLayerFor( + rootCommand: Command.Command.Any, + args: ReadonlyArray, + format: OutputFormat, +) { + const context = { rootCommand, args }; return format === "json" || format === "stream-json" - ? CliOutput.layer(jsonCliOutputFormatter()) - : CliOutput.layer(textCliOutputFormatter()); + ? CliOutput.layer(jsonCliOutputFormatter(context)) + : CliOutput.layer(textCliOutputFormatter(context)); +} + +function isErrorRecord(error: unknown): error is Record { + return typeof error === "object" && error !== null; +} + +function isExplicitHelpCause(cause: Cause.Cause): boolean { + const error = Cause.findErrorOption(cause); + if (error._tag !== "Some" || !isErrorRecord(error.value)) return false; + if (error.value["_tag"] !== "ShowHelp") return false; + + const errors = error.value["errors"]; + return !Array.isArray(errors) || errors.length === 0; } function projectContextLayerFor(runtimeLayer: Layer.Layer) { @@ -141,7 +159,7 @@ function cliProgramFor( ), ); return Command.runWith(rootCommand, { version: CLI_VERSION })(args).pipe( - Effect.provide(formatterLayerFor(outputFormat)), + Effect.provide(formatterLayerFor(rootCommand, args, outputFormat)), Effect.provide(options.analyticsLayer), Effect.provide(tracingLayer), Effect.provide(telemetryRuntimeLayer), @@ -211,7 +229,7 @@ export async function runCli(rootCommand: Command.Command.Any, options: RunCliOp const exit = yield* program.pipe(Effect.exit); if (Exit.isFailure(exit)) { const interrupted = Cause.hasInterruptsOnly(exit.cause); - if (!interrupted) { + if (!interrupted && !isExplicitHelpCause(exit.cause)) { yield* output.fail(normalizeCause(exit.cause)); } return yield* processControl.exit(interrupted ? 130 : 1); diff --git a/apps/cli/src/shared/cli/subcommand-flag-suggestions.ts b/apps/cli/src/shared/cli/subcommand-flag-suggestions.ts new file mode 100644 index 0000000000..1cc5124f98 --- /dev/null +++ b/apps/cli/src/shared/cli/subcommand-flag-suggestions.ts @@ -0,0 +1,235 @@ +import type { CliError, Command, HelpDoc } from "effect/unstable/cli"; + +export interface CliErrorSuggestionContext { + readonly rootCommand: Command.Command.Any; + readonly args: ReadonlyArray; +} + +export interface FormattedCliError { + readonly _tag: string; + readonly message: string; + readonly source: CliError.CliError; + readonly changed: boolean; +} + +export interface FormattedCliErrors { + readonly errors: ReadonlyArray; + readonly changed: boolean; +} + +interface CommandWithHelpDoc extends Command.Command.Any { + readonly buildHelpDoc: (path: ReadonlyArray) => HelpDoc.HelpDoc; +} + +interface MatchingCommand { + readonly command: Command.Command.Any; + readonly commandPath: ReadonlyArray; + readonly flag: HelpDoc.FlagDoc; +} + +function hasHelpDoc(command: Command.Command.Any): command is CommandWithHelpDoc { + return "buildHelpDoc" in command && typeof command.buildHelpDoc === "function"; +} + +function helpDocFor( + command: Command.Command.Any, + commandPath: ReadonlyArray, +): HelpDoc.HelpDoc | undefined { + return hasHelpDoc(command) ? command.buildHelpDoc(commandPath) : undefined; +} + +function findCommand( + root: Command.Command.Any, + pathWithoutRoot: ReadonlyArray, +): Command.Command.Any | undefined { + let current = root; + for (const segment of pathWithoutRoot) { + let next: Command.Command.Any | undefined; + for (const group of current.subcommands) { + next = group.commands.find( + (command) => command.name === segment || command.alias === segment, + ); + if (next) break; + } + if (!next) return undefined; + current = next; + } + return current; +} + +function collectDescendants( + command: Command.Command.Any, + commandPath: ReadonlyArray, +): ReadonlyArray { + const matches: Array = []; + const visit = (current: Command.Command.Any, path: ReadonlyArray) => { + for (const group of current.subcommands) { + for (const child of group.commands) { + if (child.hidden) continue; + + const childPath = [...path, child.name]; + const helpDoc = helpDocFor(child, childPath); + if (helpDoc) { + for (const flag of helpDoc.flags) { + matches.push({ command: child, commandPath: childPath, flag }); + } + } + visit(child, childPath); + } + } + }; + visit(command, commandPath); + return matches; +} + +function optionToken(option: string): string { + const withoutValue = option.split("=", 1)[0] ?? option; + return withoutValue; +} + +function normalizeOption(option: string): string { + const withoutValue = optionToken(option); + if (withoutValue.startsWith("--")) return withoutValue.slice(2); + if (withoutValue.startsWith("-")) return withoutValue.slice(1); + return withoutValue; +} + +function flagMatchesOption(flag: HelpDoc.FlagDoc, option: string): boolean { + const optionName = normalizeOption(option); + if (flag.name === optionName) return true; + if (flag.type === "boolean" && optionName === `no-${flag.name}`) return true; + return flag.aliases.includes(option); +} + +function findPathEndIndex( + args: ReadonlyArray, + pathWithoutRoot: ReadonlyArray, +): number | undefined { + if (pathWithoutRoot.length === 0) return 0; + for (let start = 0; start <= args.length - pathWithoutRoot.length; start++) { + let matches = true; + for (let offset = 0; offset < pathWithoutRoot.length; offset++) { + if (args[start + offset] !== pathWithoutRoot[offset]) { + matches = false; + break; + } + } + if (matches) return start + pathWithoutRoot.length; + } + return undefined; +} + +function inferAttemptedCommand( + args: ReadonlyArray, + currentPath: ReadonlyArray, + matches: ReadonlyArray, +): MatchingCommand | undefined { + const pathEnd = findPathEndIndex(args, currentPath.slice(1)); + const searchArgs = pathEnd === undefined ? args : args.slice(pathEnd); + for (const arg of searchArgs) { + if (arg.startsWith("-")) continue; + const match = matches.find((candidate) => { + const leaf = candidate.commandPath[candidate.commandPath.length - 1]; + return leaf === arg || candidate.command.alias === arg; + }); + if (match) return match; + } + return matches.length === 1 ? matches[0] : undefined; +} + +function formatCommandList(matches: ReadonlyArray): string { + const commands = [...new Set(matches.map((match) => `\`${match.commandPath.join(" ")}\``))]; + if (commands.length === 1) return commands[0] ?? ""; + if (commands.length === 2) return `${commands[0]} and ${commands[1]}`; + return `${commands.slice(0, -1).join(", ")}, and ${commands[commands.length - 1]}`; +} + +function formatFlagUsage(option: string, flag: HelpDoc.FlagDoc): string { + const flagToken = optionToken(option); + return flag.type === "boolean" ? flagToken : `${flagToken} `; +} + +function findValueAfterOption(args: ReadonlyArray, option: string): string | undefined { + const flagToken = optionToken(option); + if (option !== flagToken) return undefined; + + for (let index = 0; index < args.length; index++) { + const arg = args[index]; + if (!arg) continue; + if (arg === flagToken) { + const next = args[index + 1]; + return next && !next.startsWith("-") ? next : undefined; + } + if (arg.startsWith(`${flagToken}=`)) return undefined; + } + return undefined; +} + +function buildSubcommandFlagHint( + error: CliError.UnrecognizedOption, + context: CliErrorSuggestionContext, +): { readonly hint: string; readonly consumedValue?: string } | undefined { + if (!error.command || error.command.length === 0) return undefined; + + const current = findCommand(context.rootCommand, error.command.slice(1)); + if (!current || current.subcommands.length === 0) return undefined; + + const matches = collectDescendants(current, error.command).filter((match) => + flagMatchesOption(match.flag, error.option), + ); + if (matches.length === 0) return undefined; + + const attempted = inferAttemptedCommand(context.args, error.command, matches); + const flagToken = optionToken(error.option); + const availableOn = + matches.length === 1 + ? `a flag for ${formatCommandList(matches)}` + : `available on ${formatCommandList(matches)}`; + const example = attempted + ? `, for example:\n ${attempted.commandPath.join(" ")} ${formatFlagUsage(error.option, attempted.flag)}` + : "."; + const consumedValue = + attempted && attempted.flag.type !== "boolean" + ? findValueAfterOption(context.args, error.option) + : undefined; + + return { + hint: `${flagToken} is ${availableOn}. Pass it after the subcommand${example}`, + ...(consumedValue ? { consumedValue } : {}), + }; +} + +export function formatCliErrorsForDisplay( + errors: ReadonlyArray, + context?: CliErrorSuggestionContext, +): FormattedCliErrors { + const suppressedUnknownSubcommands = new Set(); + const formatted: Array = []; + let changed = false; + + for (const error of errors) { + if (error._tag === "UnrecognizedOption" && context) { + const hint = buildSubcommandFlagHint(error, context); + if (hint) { + if (hint.consumedValue) suppressedUnknownSubcommands.add(hint.consumedValue); + changed = true; + formatted.push({ + _tag: error._tag, + message: `${error.message}\n\n Hint: ${hint.hint}`, + source: error, + changed: true, + }); + continue; + } + } + + if (error._tag === "UnknownSubcommand" && suppressedUnknownSubcommands.has(error.subcommand)) { + changed = true; + continue; + } + + formatted.push({ _tag: error._tag, message: error.message, source: error, changed: false }); + } + + return { errors: formatted, changed }; +} diff --git a/apps/cli/src/shared/cli/subcommand-flag-suggestions.unit.test.ts b/apps/cli/src/shared/cli/subcommand-flag-suggestions.unit.test.ts new file mode 100644 index 0000000000..079256d354 --- /dev/null +++ b/apps/cli/src/shared/cli/subcommand-flag-suggestions.unit.test.ts @@ -0,0 +1,125 @@ +import { CliError, Command } from "effect/unstable/cli"; +import { describe, expect, it } from "vitest"; +import { legacyBranchesCommand } from "../../legacy/commands/branches/branches.command.ts"; +import { legacyNetworkRestrictionsCommand } from "../../legacy/commands/network-restrictions/network-restrictions.command.ts"; +import { formatCliErrorsForDisplay } from "./subcommand-flag-suggestions.ts"; + +const testRoot = Command.make("supabase").pipe( + Command.withSubcommands([legacyBranchesCommand, legacyNetworkRestrictionsCommand]), +); + +describe("subcommand flag placement suggestions", () => { + it("suggests moving a subcommand flag after the attempted subcommand", () => { + const errors = formatCliErrorsForDisplay( + [ + new CliError.UnrecognizedOption({ + option: "--project-ref", + command: ["supabase", "network-restrictions"], + suggestions: [], + }), + new CliError.UnknownSubcommand({ + subcommand: "jacraenyzrorgjhsdvvf", + parent: ["supabase", "network-restrictions"], + suggestions: [], + }), + ], + { + rootCommand: testRoot, + args: [ + "network-restrictions", + "--project-ref", + "jacraenyzrorgjhsdvvf", + "get", + "--experimental", + ], + }, + ); + + expect(errors.changed).toBe(true); + expect(errors.errors).toHaveLength(1); + expect(errors.errors[0]?.message).toContain( + "Unrecognized flag: --project-ref in command supabase network-restrictions", + ); + expect(errors.errors[0]?.message).toContain( + "Hint: --project-ref is available on `supabase network-restrictions get` and `supabase network-restrictions update`.", + ); + expect(errors.errors[0]?.message).toContain( + "supabase network-restrictions get --project-ref ", + ); + expect(errors.errors[0]?.message).not.toContain("Unknown subcommand"); + }); + + it("leaves unrelated unrecognized flags unchanged", () => { + const errors = formatCliErrorsForDisplay( + [ + new CliError.UnrecognizedOption({ + option: "--definitely-not-a-child-flag", + command: ["supabase", "network-restrictions"], + suggestions: [], + }), + ], + { + rootCommand: testRoot, + args: ["network-restrictions", "--definitely-not-a-child-flag", "get"], + }, + ); + + expect(errors.changed).toBe(false); + expect(errors.errors).toHaveLength(1); + expect(errors.errors[0]?.changed).toBe(false); + expect(errors.errors[0]?.message).toBe( + "Unrecognized flag: --definitely-not-a-child-flag in command supabase network-restrictions", + ); + }); + + it("omits hidden subcommands from placement hints", () => { + const errors = formatCliErrorsForDisplay( + [ + new CliError.UnrecognizedOption({ + option: "--project-ref", + command: ["supabase", "branches"], + suggestions: [], + }), + new CliError.UnknownSubcommand({ + subcommand: "abcdefghijklmnopqrst", + parent: ["supabase", "branches"], + suggestions: [], + }), + ], + { + rootCommand: testRoot, + args: ["branches", "--project-ref", "abcdefghijklmnopqrst", "get"], + }, + ); + + expect(errors.changed).toBe(true); + expect(errors.errors).toHaveLength(1); + expect(errors.errors[0]?.message).toContain("`supabase branches get`"); + expect(errors.errors[0]?.message).not.toContain("branches disable"); + }); + + it("normalizes assigned flags in placement examples", () => { + const errors = formatCliErrorsForDisplay( + [ + new CliError.UnrecognizedOption({ + option: "--project-ref=jacraenyzrorgjhsdvvf", + command: ["supabase", "network-restrictions"], + suggestions: [], + }), + ], + { + rootCommand: testRoot, + args: ["network-restrictions", "--project-ref=jacraenyzrorgjhsdvvf", "get"], + }, + ); + + expect(errors.changed).toBe(true); + expect(errors.errors[0]?.message).toContain( + "Hint: --project-ref is available on `supabase network-restrictions get` and `supabase network-restrictions update`.", + ); + expect(errors.errors[0]?.message).toContain( + "supabase network-restrictions get --project-ref ", + ); + expect(errors.errors[0]?.message).not.toContain("--project-ref=jacraenyzrorgjhsdvvf "); + }); +}); diff --git a/apps/cli/src/shared/output/json-formatter.ts b/apps/cli/src/shared/output/json-formatter.ts index e5fb754aa9..f0c127f5dd 100644 --- a/apps/cli/src/shared/output/json-formatter.ts +++ b/apps/cli/src/shared/output/json-formatter.ts @@ -1,6 +1,8 @@ import type { CliOutput, HelpDoc } from "effect/unstable/cli"; +import type { CliErrorSuggestionContext } from "../cli/subcommand-flag-suggestions.ts"; +import { formatCliErrorsForDisplay } from "../cli/subcommand-flag-suggestions.ts"; -export function jsonCliOutputFormatter(): CliOutput.Formatter { +export function jsonCliOutputFormatter(context?: CliErrorSuggestionContext): CliOutput.Formatter { return { formatHelpDoc: (doc: HelpDoc.HelpDoc) => JSON.stringify({ _tag: "Help", doc }), formatCliError: (error) => @@ -11,7 +13,10 @@ export function jsonCliOutputFormatter(): CliOutput.Formatter { formatErrors: (errors) => JSON.stringify({ _tag: "Errors", - errors: errors.map((e) => ({ code: e._tag, message: e.message })), + errors: formatCliErrorsForDisplay(errors, context).errors.map((e) => ({ + code: e._tag, + message: e.message, + })), }), }; } diff --git a/apps/cli/src/shared/output/text-formatter.ts b/apps/cli/src/shared/output/text-formatter.ts index 4b497742ed..4829507963 100644 --- a/apps/cli/src/shared/output/text-formatter.ts +++ b/apps/cli/src/shared/output/text-formatter.ts @@ -1,9 +1,46 @@ import { CliOutput } from "effect/unstable/cli"; +import type { + CliErrorSuggestionContext, + FormattedCliError, +} from "../cli/subcommand-flag-suggestions.ts"; +import { formatCliErrorsForDisplay } from "../cli/subcommand-flag-suggestions.ts"; -export function textCliOutputFormatter(): CliOutput.Formatter { +function stripSingleErrorHeader(text: string): string { + const prefix = "\nERROR\n "; + return text.startsWith(prefix) ? text.slice(prefix.length) : text.trimStart(); +} + +export function textCliOutputFormatter(context?: CliErrorSuggestionContext): CliOutput.Formatter { const base = CliOutput.defaultFormatter({ colors: false }); + const formatErrorMessage = (error: FormattedCliError) => + error.changed ? error.message : stripSingleErrorHeader(base.formatErrors([error.source])); + return { ...base, + formatErrors: (errors) => { + const formatted = formatCliErrorsForDisplay(errors, context); + if (!formatted.changed) return base.formatErrors(errors); + if (formatted.errors.length === 0) return ""; + if (formatted.errors.length === 1) { + const [error] = formatted.errors; + if (!error) return ""; + return error.changed ? `\nERROR\n ${error.message}` : base.formatErrors([error.source]); + } + + const sections = ["\nERRORS"]; + const grouped = new Map>(); + for (const error of formatted.errors) { + const group = grouped.get(error._tag) ?? []; + group.push(error); + grouped.set(error._tag, group); + } + for (const group of grouped.values()) { + for (const error of group) { + sections.push(` ${formatErrorMessage(error)}`); + } + } + return sections.join("\n"); + }, formatVersion: (_name, version) => version, }; } diff --git a/apps/cli/src/shared/output/text-formatter.unit.test.ts b/apps/cli/src/shared/output/text-formatter.unit.test.ts new file mode 100644 index 0000000000..f49cf597ea --- /dev/null +++ b/apps/cli/src/shared/output/text-formatter.unit.test.ts @@ -0,0 +1,56 @@ +import { CliError, Command } from "effect/unstable/cli"; +import { describe, expect, it } from "vitest"; +import { legacyNetworkRestrictionsCommand } from "../../legacy/commands/network-restrictions/network-restrictions.command.ts"; +import { textCliOutputFormatter } from "./text-formatter.ts"; + +const testRoot = Command.make("supabase").pipe( + Command.withSubcommands([legacyNetworkRestrictionsCommand]), +); + +describe("textCliOutputFormatter", () => { + it("preserves default parser suggestions for unchanged errors", () => { + const formatter = textCliOutputFormatter(); + + const text = formatter.formatErrors([ + new CliError.UnrecognizedOption({ + option: "--pla", + command: ["supabase", "projects", "create"], + suggestions: ["--plan"], + }), + ]); + + expect(text).toContain("Unrecognized flag: --pla in command supabase projects create"); + expect(text).toContain("Did you mean this?"); + expect(text).toContain("--plan"); + }); + + it("preserves default parser suggestions for unchanged siblings in rewritten errors", () => { + const formatter = textCliOutputFormatter({ + rootCommand: testRoot, + args: ["network-restrictions", "--project-ref", "jacraenyzrorgjhsdvvf", "get", "--pla"], + }); + + const text = formatter.formatErrors([ + new CliError.UnrecognizedOption({ + option: "--project-ref", + command: ["supabase", "network-restrictions"], + suggestions: [], + }), + new CliError.UnknownSubcommand({ + subcommand: "jacraenyzrorgjhsdvvf", + parent: ["supabase", "network-restrictions"], + suggestions: [], + }), + new CliError.UnrecognizedOption({ + option: "--pla", + command: ["supabase", "projects", "create"], + suggestions: ["--plan"], + }), + ]); + + expect(text).toContain("Hint: --project-ref is available on"); + expect(text).toContain("Unrecognized flag: --pla in command supabase projects create"); + expect(text).toContain("Did you mean this?"); + expect(text).toContain("--plan"); + }); +}); From c3287497bddfedfadbdcc585ed3ee362106d4b44 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Wed, 1 Jul 2026 23:22:01 +0200 Subject: [PATCH 13/48] fix(cli): align services version sources (#5754) ## Summary - Align next `supabase services` local rows with the stack version resolution used by `supabase start`, including linked baselines and local `.temp` overrides. - Keep legacy `supabase services` on the Go-compatible path: read `config.toml`, use legacy Postgres image resolution, and preserve the Deno 1 edge-runtime image override. - Keep shared service row rendering source-agnostic, with each command handler supplying the image and version source it actually runs. ## Context This prevents false local/linked mismatch warnings when config values or temp pins drift from the runtime source of truth. The next shell now follows stack runtime versions; the legacy shell follows the Go-compatible config and image path until it is retired. --- .../commands/services/services.handler.ts | 99 ++++++++- .../services/services.integration.test.ts | 179 ++++++++++++++++- .../shared/legacy-db-config.toml-read.ts | 2 +- .../shared/legacy-edge-runtime-image.ts | 10 +- .../legacy-edge-runtime-image.unit.test.ts | 4 +- .../commands/services/services.command.ts | 4 + .../commands/services/services.handler.ts | 23 ++- .../services/services.integration.test.ts | 188 +++++++++++++++++- apps/cli/src/shared/functions/serve.ts | 2 +- .../src/shared/services/services.shared.ts | 114 +++++++++-- .../services/services.shared.unit.test.ts | 16 ++ 11 files changed, 611 insertions(+), 30 deletions(-) diff --git a/apps/cli/src/legacy/commands/services/services.handler.ts b/apps/cli/src/legacy/commands/services/services.handler.ts index 28b4dc2461..f6dd731220 100644 --- a/apps/cli/src/legacy/commands/services/services.handler.ts +++ b/apps/cli/src/legacy/commands/services/services.handler.ts @@ -3,6 +3,10 @@ import { LegacyCliConfig } from "../../config/legacy-cli-config.service.ts"; import { LegacyCredentials } from "../../auth/legacy-credentials.service.ts"; import { LegacyLinkedProjectCache } from "../../telemetry/legacy-linked-project-cache.service.ts"; import { LegacyTelemetryState } from "../../telemetry/legacy-telemetry-state.service.ts"; +import { legacyReadDbToml } from "../../shared/legacy-db-config.toml-read.ts"; +import { legacyResolveDbImage } from "../../shared/legacy-db-image.ts"; +import { legacyResolveEdgeRuntimeImage } from "../../shared/legacy-edge-runtime-image.ts"; +import { legacyTempPaths } from "../../shared/legacy-temp-paths.ts"; import { LegacyOutputFlag } from "../../../shared/legacy/global-flags.ts"; import { Output } from "../../../shared/output/output.service.ts"; import { encodeGoJson, encodeToml, encodeYaml } from "../../shared/legacy-go-output.encoders.ts"; @@ -11,6 +15,9 @@ import { fetchLinkedServiceVersions, formatServicesWarning, listLocalServiceVersions, + type LocalServiceImageOverrides, + type LocalServiceVersionName, + type LocalServiceVersionOverrides, mergeRemoteServiceVersions, renderServicesTable, renderServicesWarning, @@ -55,8 +62,53 @@ export const legacyServices = Effect.fn("legacy.services")(function* (_flags: Le yield* Effect.gen(function* () { const accessTokenExit = yield* credentials.getAccessToken.pipe(Effect.exit); const accessToken = Exit.isSuccess(accessTokenExit) ? accessTokenExit.value : Option.none(); + const tomlValues = yield* legacyReadDbToml( + fs, + path, + cliConfig.workdir, + Option.getOrUndefined(linkedProjectRef), + ).pipe( + Effect.catch((error) => + output.raw(`${formatConfigLoadError(error)}\n`, "stderr").pipe(Effect.as(null)), + ), + ); + const serviceVersions = + tomlValues === null + ? {} + : yield* readLegacyServiceVersionOverrides( + fs, + path, + cliConfig.workdir, + tomlValues.majorVersion, + ); + const postgresImage = + tomlValues === null + ? undefined + : yield* legacyResolveDbImage( + fs, + path, + cliConfig.workdir, + tomlValues.majorVersion, + Option.getOrUndefined(tomlValues.orioledbVersion), + ); + const edgeRuntimeImage = + tomlValues === null + ? undefined + : yield* legacyResolveEdgeRuntimeImage(fs, path, cliConfig.workdir, tomlValues.denoVersion); + const imageOverrides: LocalServiceImageOverrides = {}; + if (postgresImage !== undefined) { + imageOverrides.postgres = postgresImage; + } + if (edgeRuntimeImage !== undefined) { + imageOverrides["edge-runtime"] = edgeRuntimeImage; + } + const localImageOptions = { + imageOverrides, + normalizeVersionTags: false, + serviceVersions, + }; - let rows = listLocalServiceVersions(); + let rows = listLocalServiceVersions(localImageOptions); if (Option.isSome(linkedProjectRef) && Option.isSome(accessToken)) { const remote = yield* fetchLinkedServiceVersions({ apiUrl: cliConfig.apiUrl, @@ -65,7 +117,7 @@ export const legacyServices = Effect.fn("legacy.services")(function* (_flags: Le accessToken: accessToken.value, userAgent: cliConfig.userAgent, }); - rows = mergeRemoteServiceVersions(remote); + rows = mergeRemoteServiceVersions(remote, localImageOptions); } const warning = renderServicesWarning(rows); @@ -110,3 +162,46 @@ export const legacyServices = Effect.fn("legacy.services")(function* (_flags: Le yield* output.raw(renderServicesTable(rows)); }).pipe(Effect.ensuring(cacheLinkedProject), Effect.ensuring(telemetryState.flush)); }); + +function formatConfigLoadError(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +const LEGACY_VERSION_FILES = [ + ["auth", "gotrue-version", (majorVersion: number | undefined) => (majorVersion ?? 17) > 14], + ["postgrest", "rest-version", (majorVersion: number | undefined) => (majorVersion ?? 17) > 14], + ["storage", "storage-version"], + ["realtime", "realtime-version"], + ["studio", "studio-version"], + ["pgmeta", "pgmeta-version"], + ["analytics", "logflare-version"], + ["pooler", "pooler-version"], +] as const satisfies ReadonlyArray< + readonly [LocalServiceVersionName, string, ((majorVersion: number | undefined) => boolean)?] +>; + +const readLegacyServiceVersionOverrides = Effect.fnUntraced(function* ( + fs: FileSystem.FileSystem, + path: Path.Path, + workdir: string, + majorVersion: number | undefined, +) { + const paths = legacyTempPaths(path, workdir); + const versions: LocalServiceVersionOverrides = {}; + + for (const [service, fileName, shouldRead] of LEGACY_VERSION_FILES) { + if (shouldRead !== undefined && !shouldRead(majorVersion)) { + continue; + } + + const version = yield* fs.readFileString(path.join(paths.tempDir, fileName)).pipe( + Effect.map((content) => content.trim()), + Effect.orElseSucceed(() => ""), + ); + if (version.length > 0) { + versions[service] = version; + } + } + + return versions; +}); diff --git a/apps/cli/src/legacy/commands/services/services.integration.test.ts b/apps/cli/src/legacy/commands/services/services.integration.test.ts index 564e8f6e11..f3c8ce3e64 100644 --- a/apps/cli/src/legacy/commands/services/services.integration.test.ts +++ b/apps/cli/src/legacy/commands/services/services.integration.test.ts @@ -1,4 +1,4 @@ -import { mkdtempSync } from "node:fs"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "@effect/vitest"; @@ -19,7 +19,10 @@ import { processEnvLayer, } from "../../../../tests/helpers/mocks.ts"; import { mockLegacyTelemetryStateTracked } from "../../../../tests/helpers/legacy-mocks.ts"; -import { listLocalServiceVersions } from "../../../shared/services/services.shared.ts"; +import { + listLocalServiceVersions, + postgresImageForDbMajorVersion, +} from "../../../shared/services/services.shared.ts"; import { textCliOutputFormatter } from "../../../shared/output/text-formatter.ts"; import { processControlLayer } from "../../../shared/runtime/process-control.layer.ts"; import { TelemetryRuntime } from "../../../shared/telemetry/runtime.service.ts"; @@ -41,6 +44,7 @@ function setup( opts: { format?: "text" | "json" | "stream-json"; goOutput?: Option.Option<"env" | "pretty" | "json" | "toml" | "yaml">; + workdir?: string; } = {}, ) { const out = mockOutput({ @@ -69,7 +73,7 @@ function setup( poolerHost: "supabase.com", accessToken: Option.none(), projectId: Option.none(), - workdir: process.cwd(), + workdir: opts.workdir ?? process.cwd(), userAgent: "SupabaseCLI/test", }), ), @@ -100,6 +104,38 @@ const legacyTestRoot = Command.make("supabase").pipe( Command.withSubcommands([legacyServicesCommand]), ); +function makeProjectWithConfig(config: string): string { + const workdir = mkdtempSync(join(tmpdir(), "supabase-services-config-")); + const configDir = join(workdir, "supabase"); + mkdirSync(configDir, { recursive: true }); + writeFileSync(join(configDir, "config.toml"), config); + return workdir; +} + +function makeProjectWithConfigFiles(opts: { toml: string; json: string }): string { + const workdir = makeProjectWithConfig(opts.toml); + writeFileSync(join(workdir, "supabase", "config.json"), opts.json); + return workdir; +} + +function makeProjectWithDbMajorVersion(majorVersion: number): string { + return makeProjectWithConfig(`[db]\nmajor_version = ${majorVersion}\n`); +} + +function writeTempFile(workdir: string, name: string, content: string): void { + const tempDir = join(workdir, "supabase", ".temp"); + mkdirSync(tempDir, { recursive: true }); + writeFileSync(join(tempDir, name), content); +} + +function postgresVersionForDbMajorVersion(majorVersion: number): string { + const image = postgresImageForDbMajorVersion(majorVersion); + if (image === undefined) { + throw new Error(`Missing Postgres image for db major ${majorVersion}.`); + } + return image.slice(image.lastIndexOf(":") + 1); +} + function expectFailureTag(exit: Exit.Exit, tag: string) { expect(Exit.isFailure(exit)).toBe(true); if (!Exit.isFailure(exit)) { @@ -197,6 +233,143 @@ describe("legacy services", () => { }); }); + it.live("reports the configured Postgres version for local projects", () => { + const workdir = makeProjectWithDbMajorVersion(15); + const { layer, out } = setup({ goOutput: Option.some("json"), workdir }); + + return Effect.gen(function* () { + yield* legacyServices({}).pipe(Effect.provide(layer)); + + const rows = JSON.parse(out.stdoutText) as Array<{ + name: string; + local: string; + remote: string; + }>; + expect(rows).toContainEqual( + expect.objectContaining({ + name: "supabase/postgres", + local: postgresVersionForDbMajorVersion(15), + }), + ); + }).pipe(Effect.ensuring(Effect.sync(() => rmSync(workdir, { recursive: true, force: true })))); + }); + + it.live("ignores config.json and reads legacy config.toml for local image selection", () => { + const workdir = makeProjectWithConfigFiles({ + toml: "[db]\nmajor_version = 15\n", + json: JSON.stringify({ db: { major_version: 14 } }), + }); + const { layer, out } = setup({ goOutput: Option.some("json"), workdir }); + + return Effect.gen(function* () { + yield* legacyServices({}).pipe(Effect.provide(layer)); + + const rows = JSON.parse(out.stdoutText) as Array<{ + name: string; + local: string; + remote: string; + }>; + expect(rows).toContainEqual( + expect.objectContaining({ + name: "supabase/postgres", + local: postgresVersionForDbMajorVersion(15), + }), + ); + }).pipe(Effect.ensuring(Effect.sync(() => rmSync(workdir, { recursive: true, force: true })))); + }); + + it.live("applies linked-project remote config overrides when choosing the local image", () => { + const workdir = makeProjectWithConfig(` +[db] +major_version = 17 + +[remotes.linked] +project_id = "abcdefghijklmnopqrst" + +[remotes.linked.db] +major_version = 15 +`); + writeTempFile(workdir, "project-ref", "abcdefghijklmnopqrst"); + const { layer, out } = setup({ goOutput: Option.some("json"), workdir }); + + return Effect.gen(function* () { + yield* legacyServices({}).pipe(Effect.provide(layer)); + + const rows = JSON.parse(out.stdoutText) as Array<{ + name: string; + local: string; + remote: string; + }>; + expect(rows).toContainEqual( + expect.objectContaining({ + name: "supabase/postgres", + local: postgresVersionForDbMajorVersion(15), + }), + ); + }).pipe(Effect.ensuring(Effect.sync(() => rmSync(workdir, { recursive: true, force: true })))); + }); + + it.live("reports pinned legacy temp service versions", () => { + const workdir = makeProjectWithDbMajorVersion(15); + writeTempFile(workdir, "postgres-version", "15.1.0.117\n"); + writeTempFile(workdir, "gotrue-version", "2.74.2\n"); + writeTempFile(workdir, "storage-version", "v1.28.0\n"); + const { layer, out } = setup({ goOutput: Option.some("json"), workdir }); + + return Effect.gen(function* () { + yield* legacyServices({}).pipe(Effect.provide(layer)); + + const rows = JSON.parse(out.stdoutText) as Array<{ + name: string; + local: string; + remote: string; + }>; + expect(rows).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: "supabase/postgres", local: "15.1.0.117" }), + expect.objectContaining({ name: "supabase/gotrue", local: "2.74.2" }), + expect.objectContaining({ name: "supabase/storage-api", local: "v1.28.0" }), + ]), + ); + }).pipe(Effect.ensuring(Effect.sync(() => rmSync(workdir, { recursive: true, force: true })))); + }); + + it.live("reports the Deno 1 edge-runtime image instead of the temp pin", () => { + const workdir = makeProjectWithConfig("[edge_runtime]\ndeno_version = 1\n"); + writeTempFile(workdir, "edge-runtime-version", "v9.9.9\n"); + const { layer, out } = setup({ goOutput: Option.some("json"), workdir }); + + return Effect.gen(function* () { + yield* legacyServices({}).pipe(Effect.provide(layer)); + + const rows = JSON.parse(out.stdoutText) as Array<{ + name: string; + local: string; + remote: string; + }>; + expect(rows).toContainEqual( + expect.objectContaining({ + name: "supabase/edge-runtime", + local: "v1.68.4", + }), + ); + }).pipe(Effect.ensuring(Effect.sync(() => rmSync(workdir, { recursive: true, force: true })))); + }); + + it.live("prints config load errors and falls back to the default matrix", () => { + const workdir = makeProjectWithConfig("[db]\nmajor_version = "); + writeTempFile(workdir, "storage-version", "v9.9.9\n"); + const { layer, out } = setup({ workdir }); + + return Effect.gen(function* () { + yield* legacyServices({}).pipe(Effect.provide(layer)); + + expect(out.stdoutText).toContain("supabase/postgres"); + expect(out.stdoutText).not.toContain("v9.9.9"); + expect(out.stderrText).not.toBe(""); + }).pipe(Effect.ensuring(Effect.sync(() => rmSync(workdir, { recursive: true, force: true })))); + }); + it.live("emits structured JSON for --output pretty combined with --output-format json", () => { // Regression guard (CLI-1546): a Go `--output pretty` must defer to the TS // `--output-format json` flag instead of forcing the human-readable table. diff --git a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts index ac8e8e573a..b9a89e6577 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts @@ -143,7 +143,7 @@ const DEFAULT_PORT = 54322; const DEFAULT_SHADOW_PORT = 54320; const DEFAULT_MAJOR_VERSION = 17; const DEFAULT_PASSWORD = "postgres"; -/** `[edge_runtime] deno_version` default (`config.toml` template). 2 → v1.74.1. */ +/** `[edge_runtime] deno_version` default (`config.toml` template). 2 → the current edge-runtime image. */ const DEFAULT_DENO_VERSION = 2; /** Default declarative schema dir (`utils.DeclarativeDir`, `misc.go:102`). */ diff --git a/apps/cli/src/legacy/shared/legacy-edge-runtime-image.ts b/apps/cli/src/legacy/shared/legacy-edge-runtime-image.ts index da146970ba..6bc21cbfb1 100644 --- a/apps/cli/src/legacy/shared/legacy-edge-runtime-image.ts +++ b/apps/cli/src/legacy/shared/legacy-edge-runtime-image.ts @@ -6,14 +6,14 @@ import { Effect, type FileSystem, type Path } from "effect"; * declarative pg-delta scripts that run inside the edge-runtime container. * * The default tag is baked into the Go binary via the embedded Dockerfile - * (`FROM supabase/edge-runtime:v1.74.1 AS edgeruntime`), mirrored here as a + * (`FROM supabase/edge-runtime:v1.74.2 AS edgeruntime`), mirrored here as a * constant. A pinned tag in `supabase/.temp/edge-runtime-version` overrides it * (written by `supabase start`). `edge_runtime.deno_version = 1` selects the - * legacy `deno1` image instead (default `deno_version = 2` keeps v1.74.1). + * legacy `deno1` image instead (default `deno_version = 2` keeps v1.74.2). */ -// `FROM supabase/edge-runtime:v1.74.1 AS edgeruntime` (embedded Dockerfile). -export const LEGACY_EDGE_RUNTIME_IMAGE = "supabase/edge-runtime:v1.74.1"; +// `FROM supabase/edge-runtime:v1.74.2 AS edgeruntime` (embedded Dockerfile). +export const LEGACY_EDGE_RUNTIME_IMAGE = "supabase/edge-runtime:v1.74.2"; // `deno1` (`pkg/config/constants.go:15`) — used when `deno_version = 1`. const LEGACY_EDGE_RUNTIME_DENO1_IMAGE = "supabase/edge-runtime:v1.68.4"; @@ -26,7 +26,7 @@ function replaceImageTag(image: string, tag: string): string { /** * Resolve the edge-runtime image, honoring the pinned tag in * `supabase/.temp/edge-runtime-version` and the `deno_version` selector - * (default 2 → v1.74.1; 1 → `deno1`). The version pin is applied first (Go's + * (default 2 → v1.74.2; 1 → `deno1`). The version pin is applied first (Go's * `Load`), then `deno_version = 1` overrides to `deno1` (Go's validate pass). */ export const legacyResolveEdgeRuntimeImage = Effect.fnUntraced(function* ( diff --git a/apps/cli/src/legacy/shared/legacy-edge-runtime-image.unit.test.ts b/apps/cli/src/legacy/shared/legacy-edge-runtime-image.unit.test.ts index 5565da4153..9b15eecf24 100644 --- a/apps/cli/src/legacy/shared/legacy-edge-runtime-image.unit.test.ts +++ b/apps/cli/src/legacy/shared/legacy-edge-runtime-image.unit.test.ts @@ -15,12 +15,12 @@ const resolve = (workdir: string, denoVersion: number) => }).pipe(Effect.provide(BunServices.layer)); describe("legacyResolveEdgeRuntimeImage", () => { - it.effect("returns the default v1.74.1 image when nothing is pinned", () => { + it.effect("returns the default v1.74.2 image when nothing is pinned", () => { const dir = mkdtempSync(join(tmpdir(), "legacy-edge-img-")); return resolve(dir, 2).pipe( Effect.tap((image) => Effect.sync(() => { - expect(image).toBe("supabase/edge-runtime:v1.74.1"); + expect(image).toBe("supabase/edge-runtime:v1.74.2"); rmSync(dir, { recursive: true, force: true }); }), ), diff --git a/apps/cli/src/next/commands/services/services.command.ts b/apps/cli/src/next/commands/services/services.command.ts index 9716a5685c..2404d750b6 100644 --- a/apps/cli/src/next/commands/services/services.command.ts +++ b/apps/cli/src/next/commands/services/services.command.ts @@ -2,8 +2,10 @@ import { Layer } from "effect"; import { Command } from "effect/unstable/cli"; import { FetchHttpClient } from "effect/unstable/http"; import { credentialsLayer } from "../../auth/credentials.layer.ts"; +import { projectLocalServiceVersionsLayer } from "../../config/project-local-service-versions.layer.ts"; import { projectLinkStateLayer } from "../../config/project-link-state.layer.ts"; import { provideProjectCommandRuntime } from "../../config/project-runtime.layer.ts"; +import { projectStackStateManagerLayer } from "../../config/project-stack-state-manager.layer.ts"; import { withJsonErrorHandling } from "../../../shared/output/json-error-handling.ts"; import { commandRuntimeLayer } from "../../../shared/runtime/command-runtime.layer.ts"; import { withCommandInstrumentation } from "../../../shared/telemetry/command-instrumentation.ts"; @@ -13,6 +15,8 @@ const servicesRuntimeLayer = provideProjectCommandRuntime( Layer.mergeAll( credentialsLayer, projectLinkStateLayer, + projectLocalServiceVersionsLayer, + projectStackStateManagerLayer, commandRuntimeLayer(["services"]), // `fetchLinkedServiceVersions` builds its management/tenant API clients from // the ambient HttpClient rather than self-provisioning one. diff --git a/apps/cli/src/next/commands/services/services.handler.ts b/apps/cli/src/next/commands/services/services.handler.ts index 5b7fc61c94..176495e638 100644 --- a/apps/cli/src/next/commands/services/services.handler.ts +++ b/apps/cli/src/next/commands/services/services.handler.ts @@ -1,6 +1,8 @@ +import { planStackVersions, StateManager } from "@supabase/stack/effect"; import { Effect, Exit, Option } from "effect"; import { Credentials } from "../../auth/credentials.service.ts"; import { CliConfig } from "../../config/cli-config.service.ts"; +import { ProjectLocalServiceVersions } from "../../config/project-local-service-versions.service.ts"; import { ProjectLinkState } from "../../config/project-link-state.service.ts"; import { Output } from "../../../shared/output/output.service.ts"; import { @@ -20,14 +22,31 @@ export const services = Effect.fnUntraced(function* () { const output = yield* Output; const cliConfig = yield* CliConfig; const credentials = yield* Credentials; + const projectLocalServiceVersions = yield* ProjectLocalServiceVersions; const projectLinkState = yield* ProjectLinkState; + const stateManager = yield* StateManager; const commandRuntime = yield* CommandRuntime; const linkedStateExit = yield* projectLinkState.load.pipe(Effect.exit); const linkedState = Exit.isSuccess(linkedStateExit) ? linkedStateExit.value : Option.none(); const accessToken = yield* credentials.getAccessToken; + const localServiceVersions = yield* projectLocalServiceVersions.load; + const existingMetadata = yield* stateManager.readMetadata("default").pipe( + Effect.map(Option.some), + Effect.catchTag("StackMetadataNotFoundError", () => Effect.succeed(Option.none())), + ); + const serviceVersionContext = planStackVersions({ + ...(Option.isSome(linkedState) ? { candidateBaseline: linkedState.value.versions } : {}), + ...(Option.isSome(existingMetadata) ? { pinnedBaseline: existingMetadata.value.services } : {}), + ...(Option.isSome(localServiceVersions) + ? { localOverrides: localServiceVersions.value.versions } + : {}), + }); + const localImageOptions = { + serviceVersions: serviceVersionContext.runtimeVersions, + }; - let rows = listLocalServiceVersions(); + let rows = listLocalServiceVersions(localImageOptions); if (Option.isSome(linkedState) && Option.isSome(accessToken)) { const remote = yield* fetchLinkedServiceVersions({ apiUrl: cliConfig.apiUrl, @@ -40,7 +59,7 @@ export const services = Effect.fnUntraced(function* () { "X-Supabase-Command-Run-ID": commandRuntime.commandRunId, }, }); - rows = mergeRemoteServiceVersions(remote); + rows = mergeRemoteServiceVersions(remote, localImageOptions); } const warning = renderServicesWarning(rows); diff --git a/apps/cli/src/next/commands/services/services.integration.test.ts b/apps/cli/src/next/commands/services/services.integration.test.ts index b3abe533aa..72648c8210 100644 --- a/apps/cli/src/next/commands/services/services.integration.test.ts +++ b/apps/cli/src/next/commands/services/services.integration.test.ts @@ -1,14 +1,25 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { describe, expect, it } from "@effect/vitest"; +import { BunServices } from "@effect/platform-bun"; +import { DEFAULT_VERSIONS, stackMetadata, type VersionManifest } from "@supabase/stack/effect"; import { Effect, Layer, Option, Redacted } from "effect"; import { FetchHttpClient } from "effect/unstable/http"; import { CliConfig } from "../../config/cli-config.service.ts"; +import type { LocalServiceVersionsState } from "../../config/project-local-service-versions.service.ts"; +import { ProjectHome } from "../../config/project-home.service.ts"; import { ProjectLinkState, type ProjectLinkStateValue, } from "../../config/project-link-state.service.ts"; import { InvalidProjectLinkStateError } from "../../config/project-link-state.service.ts"; import { Credentials } from "../../auth/credentials.service.ts"; -import { mockOutput } from "../../../../tests/helpers/mocks.ts"; +import { + mockOutput, + mockProjectLocalServiceVersions, + mockStateManager, +} from "../../../../tests/helpers/mocks.ts"; import { CommandRuntime } from "../../../shared/runtime/command-runtime.service.ts"; import { listLocalServiceVersions } from "../../../shared/services/services.shared.ts"; import { services } from "./services.handler.ts"; @@ -45,6 +56,9 @@ function setup( invalidLinkedState?: boolean; accessToken?: string; apiUrl?: string; + workdir?: string; + localServiceVersions?: LocalServiceVersionsState; + pinnedStackVersions?: VersionManifest; } = {}, ) { const out = mockOutput({ @@ -52,11 +66,49 @@ function setup( interactive: (opts.format ?? "text") === "text", }); const linkedState = opts.linkedState ?? Option.none(); + const projectRoot = opts.workdir ?? process.cwd(); + const supabaseDir = join(projectRoot, "supabase"); return { out, layer: Layer.mergeAll( + BunServices.layer, out.layer, + mockStateManager({ + metadata: + opts.pinnedStackVersions === undefined + ? [] + : [ + { + name: "default", + metadata: stackMetadata({ + ports: { + apiPort: 54321, + dbPort: 54322, + authPort: 54323, + postgrestPort: 54324, + postgrestAdminPort: 54325, + edgeRuntimePort: 54337, + edgeRuntimeInspectorPort: 54338, + realtimePort: 54326, + storagePort: 54327, + imgproxyPort: 54328, + mailpitPort: 54329, + mailpitSmtpPort: 54330, + mailpitPop3Port: 54331, + pgmetaPort: 54332, + studioPort: 54333, + analyticsPort: 54334, + poolerPort: 54335, + poolerApiPort: 54336, + }, + services: opts.pinnedStackVersions, + launch: { mode: "auto", excludedServices: [] }, + }), + }, + ], + }), + mockProjectLocalServiceVersions(opts.localServiceVersions), FetchHttpClient.layer, Layer.succeed( CliConfig, @@ -87,6 +139,22 @@ function setup( deleteAccessToken: Effect.die("unexpected deleteAccessToken"), }), ), + Layer.succeed( + ProjectHome, + ProjectHome.of({ + projectRoot, + supabaseDir, + projectHomeDir: join(supabaseDir, ".temp"), + projectLinkPath: join(supabaseDir, ".temp", "project-ref"), + projectLocalVersionsPath: join(supabaseDir, ".temp", "local-versions"), + ensureProjectHomeDir: Effect.void, + stackDir: (name) => join(supabaseDir, ".branches", name), + stackStatePath: (name) => join(supabaseDir, ".branches", name, "stack-state.json"), + stackMetadataPath: (name) => join(supabaseDir, ".branches", name, "stack.json"), + stackDataDir: (name) => join(supabaseDir, ".branches", name, "data"), + stackLogsDir: (name) => join(supabaseDir, ".branches", name, "logs"), + }), + ), Layer.succeed( ProjectLinkState, ProjectLinkState.of({ @@ -115,6 +183,18 @@ function setup( }; } +function makeProjectWithConfig(config: string): string { + const workdir = mkdtempSync(join(tmpdir(), "supabase-services-config-")); + const configDir = join(workdir, "supabase"); + mkdirSync(configDir, { recursive: true }); + writeFileSync(join(configDir, "config.toml"), config); + return workdir; +} + +function makeProjectWithDbMajorVersion(majorVersion: number): string { + return makeProjectWithConfig(`[db]\nmajor_version = ${majorVersion}\n`); +} + describe("next services", () => { it.live("prints the services table in text mode", () => { const { layer, out } = setup(); @@ -147,6 +227,112 @@ describe("next services", () => { }); }); + it.live("reports the stack runtime Postgres version instead of config db.major_version", () => { + const workdir = makeProjectWithDbMajorVersion(15); + const { layer, out } = setup({ format: "json", workdir }); + + return Effect.gen(function* () { + yield* services().pipe(Effect.provide(layer)); + + const success = out.messages.find((message) => message.type === "success"); + expect(success?.data).toMatchObject({ + services: expect.arrayContaining([ + expect.objectContaining({ + name: "supabase/postgres", + local: DEFAULT_VERSIONS.postgres, + }), + ]), + }); + }).pipe(Effect.ensuring(Effect.sync(() => rmSync(workdir, { recursive: true, force: true })))); + }); + + it.live("reports the stack runtime version instead of linked remote config db overrides", () => { + const workdir = makeProjectWithConfig(` +[db] +major_version = 17 + +[remotes.linked] +project_id = "${LINKED_REF}" + +[remotes.linked.db] +major_version = 15 +`); + const { layer, out } = setup({ + format: "json", + linkedState: Option.some(linkedStateFixture()), + workdir, + }); + + return Effect.gen(function* () { + yield* services().pipe(Effect.provide(layer)); + + const success = out.messages.find((message) => message.type === "success"); + expect(success?.data).toMatchObject({ + services: expect.arrayContaining([ + expect.objectContaining({ + name: "supabase/postgres", + local: DEFAULT_VERSIONS.postgres, + }), + ]), + }); + }).pipe(Effect.ensuring(Effect.sync(() => rmSync(workdir, { recursive: true, force: true })))); + }); + + it.live("reports pinned local service versions", () => { + const { layer, out } = setup({ + format: "json", + localServiceVersions: { + updatedAt: "2026-07-01T14:00:00.000Z", + versions: { + postgres: "15.1.0.117", + auth: "2.74.2", + storage: "1.28.0", + }, + }, + }); + + return Effect.gen(function* () { + yield* services().pipe(Effect.provide(layer)); + + const success = out.messages.find((message) => message.type === "success"); + expect(success?.data).toMatchObject({ + services: expect.arrayContaining([ + expect.objectContaining({ name: "supabase/postgres", local: "15.1.0.117" }), + expect.objectContaining({ name: "supabase/gotrue", local: "v2.74.2" }), + expect.objectContaining({ name: "supabase/storage-api", local: "v1.28.0" }), + ]), + }); + }); + }); + + it.live("reports pinned stack metadata before newer linked baseline versions", () => { + const { layer, out } = setup({ + format: "json", + linkedState: Option.some({ + ...linkedStateFixture(), + versions: { postgres: "17.6.1.200" }, + }), + pinnedStackVersions: { + ...DEFAULT_VERSIONS, + postgres: "17.6.1.100", + }, + }); + + return Effect.gen(function* () { + yield* services().pipe(Effect.provide(layer)); + + const success = out.messages.find((message) => message.type === "success"); + expect(success?.data).toMatchObject({ + services: expect.arrayContaining([ + expect.objectContaining({ + name: "supabase/postgres", + local: "17.6.1.100", + }), + ]), + }); + }); + }); + it.live("falls back to local output when linked state is invalid", () => { const { layer, out } = setup({ invalidLinkedState: true }); diff --git a/apps/cli/src/shared/functions/serve.ts b/apps/cli/src/shared/functions/serve.ts index 91874fb603..e11a1c4736 100644 --- a/apps/cli/src/shared/functions/serve.ts +++ b/apps/cli/src/shared/functions/serve.ts @@ -83,7 +83,7 @@ const ignoredDirNames = new Set([ const dockerLogRetryDelay = Duration.millis(400); const dockerLogDiagnosticTailLength = 4_096; const remoteJwksTimeoutMs = 10_000; -const legacyDefaultEdgeRuntimeVersion = "v1.74.1"; +const legacyDefaultEdgeRuntimeVersion = "v1.74.2"; const defaultSupabaseEnv = "development"; const serveMainContainerPath = "/root/index.ts"; const clerkDomainPattern = /^(clerk([.][a-z0-9-]+){2,}|([a-z0-9-]+[.])+clerk[.]accounts[.]dev)$/; diff --git a/apps/cli/src/shared/services/services.shared.ts b/apps/cli/src/shared/services/services.shared.ts index 96f36cb10d..05840cb893 100644 --- a/apps/cli/src/shared/services/services.shared.ts +++ b/apps/cli/src/shared/services/services.shared.ts @@ -14,6 +14,26 @@ export { parseDockerfileServiceImages } from "./dockerfile-images.ts"; export type RemoteServiceName = "postgres" | "auth" | "postgrest" | "storage"; export type OptionalRemoteServiceName = Exclude; +export type LocalServiceVersionName = + | "postgres" + | "auth" + | "postgrest" + | "realtime" + | "storage" + | "edge-runtime" + | "studio" + | "pgmeta" + | "analytics" + | "pooler"; + +export type LocalServiceVersionOverrides = Partial>; +export type LocalServiceImageOverrides = Partial>; + +export interface LocalServiceImageOptions { + readonly imageOverrides?: LocalServiceImageOverrides; + readonly normalizeVersionTags?: boolean; + readonly serviceVersions?: LocalServiceVersionOverrides; +} // Mirrors Go's `utils.ProjectRefPattern` (`apps/cli-go/internal/utils/misc.go`). // Validating the ref before it reaches the management API path param or the @@ -24,26 +44,37 @@ const PROJECT_REF_PATTERN = /^[a-z]{20}$/; interface ServiceImageSpec { readonly image: string; readonly remoteService: RemoteServiceName | undefined; + readonly localService: LocalServiceVersionName; } interface ServiceImageAliasSpec { readonly alias: string; readonly remoteService: RemoteServiceName | undefined; + readonly localService: LocalServiceVersionName; } const SERVICE_IMAGE_ALIASES: ReadonlyArray = [ - { alias: "pg", remoteService: "postgres" }, - { alias: "gotrue", remoteService: "auth" }, - { alias: "postgrest", remoteService: "postgrest" }, - { alias: "realtime", remoteService: undefined }, - { alias: "storage", remoteService: "storage" }, - { alias: "edgeruntime", remoteService: undefined }, - { alias: "studio", remoteService: undefined }, - { alias: "pgmeta", remoteService: undefined }, - { alias: "logflare", remoteService: undefined }, - { alias: "supavisor", remoteService: undefined }, + { alias: "pg", remoteService: "postgres", localService: "postgres" }, + { alias: "gotrue", remoteService: "auth", localService: "auth" }, + { alias: "postgrest", remoteService: "postgrest", localService: "postgrest" }, + { alias: "realtime", remoteService: undefined, localService: "realtime" }, + { alias: "storage", remoteService: "storage", localService: "storage" }, + { alias: "edgeruntime", remoteService: undefined, localService: "edge-runtime" }, + { alias: "studio", remoteService: undefined, localService: "studio" }, + { alias: "pgmeta", remoteService: undefined, localService: "pgmeta" }, + { alias: "logflare", remoteService: undefined, localService: "analytics" }, + { alias: "supavisor", remoteService: undefined, localService: "pooler" }, ]; +const SERVICE_VERSION_TAG_PREFIX: Partial> = { + auth: "v", + postgrest: "v", + realtime: "v", + storage: "v", + "edge-runtime": "v", + pgmeta: "v", +}; + function localServiceImagesFromSpecs( specs: ReadonlyArray, ): ReadonlyArray { @@ -57,6 +88,7 @@ function localServiceImagesFromSpecs( return { image, remoteService: service.remoteService, + localService: service.localService, }; }); } @@ -69,6 +101,57 @@ export function localServiceImagesFromDockerfile( const LOCAL_SERVICE_IMAGES = localServiceImagesFromSpecs(dockerfileServiceImages); +// Mirrors Go's config image rewrite in `apps/cli-go/pkg/config/config.go`. +// Major version 13 intentionally falls through to the pg15 image there. +export function postgresImageForDbMajorVersion(majorVersion: number): string | undefined { + switch (majorVersion) { + case 13: + case 15: + return "supabase/postgres:15.8.1.085"; + case 14: + return "supabase/postgres:14.1.0.89"; + default: + return undefined; + } +} + +function replaceImageTag(image: string, tag: string): string { + const index = image.lastIndexOf(":"); + if (index === -1) { + return image; + } + return `${image.slice(0, index + 1)}${tag.trim()}`; +} + +function tagForServiceVersion(service: LocalServiceVersionName, version: string): string { + const trimmed = version.trim(); + const prefix = SERVICE_VERSION_TAG_PREFIX[service]; + if (prefix === "v" && !trimmed.toLowerCase().startsWith("v")) { + return `v${trimmed}`; + } + return trimmed; +} + +function localServiceImagesForOptions( + options: LocalServiceImageOptions = {}, +): ReadonlyArray { + const normalizeVersionTags = options.normalizeVersionTags ?? true; + return LOCAL_SERVICE_IMAGES.map((service) => { + const baseImage = options.imageOverrides?.[service.localService] ?? service.image; + const version = options.serviceVersions?.[service.localService]; + if (version === undefined || version.trim().length === 0) { + return baseImage === service.image ? service : { ...service, image: baseImage }; + } + return { + ...service, + image: replaceImageTag( + baseImage, + normalizeVersionTags ? tagForServiceVersion(service.localService, version) : version, + ), + }; + }); +} + const TABLE_HEADERS = ["SERVICE IMAGE", "LOCAL", "LINKED"] as const; type ProjectApiKey = { @@ -288,14 +371,19 @@ const makeConfiguredApiClient = Effect.fnUntraced(function* (input: ServiceFetch ); }); -export function listLocalServiceVersions(): ReadonlyArray { - return LOCAL_SERVICE_IMAGES.map((service) => toServiceVersionRow(service)); +export function listLocalServiceVersions( + options: LocalServiceImageOptions = {}, +): ReadonlyArray { + return localServiceImagesForOptions(options).map((service) => toServiceVersionRow(service)); } export function mergeRemoteServiceVersions( remote: Partial>, + options: LocalServiceImageOptions = {}, ): ReadonlyArray { - return LOCAL_SERVICE_IMAGES.map((service) => toServiceVersionRow(service, remote)); + return localServiceImagesForOptions(options).map((service) => + toServiceVersionRow(service, remote), + ); } export function renderServicesTable(rows: ReadonlyArray): string { diff --git a/apps/cli/src/shared/services/services.shared.unit.test.ts b/apps/cli/src/shared/services/services.shared.unit.test.ts index 27be161714..bb343ee103 100644 --- a/apps/cli/src/shared/services/services.shared.unit.test.ts +++ b/apps/cli/src/shared/services/services.shared.unit.test.ts @@ -68,6 +68,22 @@ describe("services shared", () => { ]); }); + test("can preserve raw local service version overrides", () => { + expect( + listLocalServiceVersions({ + normalizeVersionTags: false, + serviceVersions: { + auth: "2.151.0", + }, + }), + ).toContainEqual( + expect.objectContaining({ + name: "supabase/gotrue", + local: "2.151.0", + }), + ); + }); + test("returns postgres only when no service-role key is available", async () => { const server = Bun.serve({ port: 0, From c8e29948f95d59a29a0a302a58f9e5378a1d8054 Mon Sep 17 00:00:00 2001 From: "supabase-cli-releaser[bot]" <246109035+supabase-cli-releaser[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:22:27 +0000 Subject: [PATCH 14/48] chore: sync API types from infrastructure (#5753) This PR was automatically created to sync API types from the infrastructure repository. Changes were detected in the generated API code after syncing with the latest spec from infrastructure. Co-authored-by: supabase-cli-releaser[bot] <246109035+supabase-cli-releaser[bot]@users.noreply.github.com> --- apps/cli-go/pkg/api/types.gen.go | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/cli-go/pkg/api/types.gen.go b/apps/cli-go/pkg/api/types.gen.go index 838e1e776a..f0785b4a4b 100644 --- a/apps/cli-go/pkg/api/types.gen.go +++ b/apps/cli-go/pkg/api/types.gen.go @@ -1427,6 +1427,7 @@ const ( // Defines values for V1ListEntitlementsResponseEntitlementsFeatureKey. const ( + V1ListEntitlementsResponseEntitlementsFeatureKeyApiMembersInvitations V1ListEntitlementsResponseEntitlementsFeatureKey = "api.members.invitations" V1ListEntitlementsResponseEntitlementsFeatureKeyApiMembersRoles V1ListEntitlementsResponseEntitlementsFeatureKey = "api.members.roles" V1ListEntitlementsResponseEntitlementsFeatureKeyAssistantAdvanceModel V1ListEntitlementsResponseEntitlementsFeatureKey = "assistant.advance_model" V1ListEntitlementsResponseEntitlementsFeatureKeyAuditLogDrains V1ListEntitlementsResponseEntitlementsFeatureKey = "audit_log_drains" From 77e9079c1a2796ed517782e3b5945ecdfeb2e445 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:10:56 +0000 Subject: [PATCH 15/48] chore(deps): bump github.com/posthog/posthog-go from 1.16.1 to 1.16.2 in /apps/cli-go in the go-minor group across 1 directory (#5757) Bumps the go-minor group with 1 update in the /apps/cli-go directory: [github.com/posthog/posthog-go](https://github.com/posthog/posthog-go). Updates `github.com/posthog/posthog-go` from 1.16.1 to 1.16.2
Release notes

Sourced from github.com/posthog/posthog-go's releases.

1.16.2

Unreleased

Changelog

Sourced from github.com/posthog/posthog-go's changelog.

1.16.2

Patch Changes

  • 76de226: Dedupe $feature_flag_called events by feature flag response value.
Commits
  • 89309b8 chore: release v1.16.2 [version bump] [skip ci]
  • 76de226 fix: Dedupe feature flag called events by response (#234)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/posthog/posthog-go&package-manager=go_modules&previous-version=1.16.1&new-version=1.16.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) 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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/go.mod | 2 +- apps/cli-go/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/cli-go/go.mod b/apps/cli-go/go.mod index 4bc46b5d07..e11a2d06fc 100644 --- a/apps/cli-go/go.mod +++ b/apps/cli-go/go.mod @@ -42,7 +42,7 @@ require ( github.com/multigres/multigres v0.0.0-20260126223308-f5a52171bbc4 github.com/oapi-codegen/nullable v1.2.0 github.com/olekukonko/tablewriter v1.1.4 - github.com/posthog/posthog-go v1.16.1 + github.com/posthog/posthog-go v1.16.2 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/apps/cli-go/go.sum b/apps/cli-go/go.sum index e8ed2f0f72..1069d1a832 100644 --- a/apps/cli-go/go.sum +++ b/apps/cli-go/go.sum @@ -941,8 +941,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= -github.com/posthog/posthog-go v1.16.1 h1:uEbaaYT361a3ImI0D1DYUyNLWN7Y9V9gLqCbQ/z5SxQ= -github.com/posthog/posthog-go v1.16.1/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= +github.com/posthog/posthog-go v1.16.2 h1:zjbAZZELbt8/GNTWyFmZuVNURf8prGdlo1BcJOX9RK4= +github.com/posthog/posthog-go v1.16.2/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= From 4c6ff8a1e807330d7bc25919f9f3a16825480fc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:14:25 +0000 Subject: [PATCH 16/48] chore(deps): bump the npm-major group with 9 updates (#5760) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-major group with 9 updates: | Package | From | To | | --- | --- | --- | | [@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript) | `0.3.187` | `0.3.191` | | [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript) | `0.105.0` | `0.106.0` | | [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.38.4` | `5.38.5` | | [@effect/atom-react](https://github.com/Effect-TS/effect-smol/tree/HEAD/packages/atom/react) | `4.0.0-beta.87` | `4.0.0-beta.88` | | [@effect/platform-bun](https://github.com/Effect-TS/effect/tree/HEAD/packages/platform-bun) | `4.0.0-beta.87` | `4.0.0-beta.88` | | [@effect/platform-node](https://github.com/Effect-TS/effect/tree/HEAD/packages/platform-node) | `4.0.0-beta.87` | `4.0.0-beta.88` | | [@effect/sql-pg](https://github.com/Effect-TS/effect/tree/HEAD/packages/sql-pg) | `4.0.0-beta.87` | `4.0.0-beta.88` | | [@typescript/native-preview](https://github.com/microsoft/typescript-go) | `7.0.0-dev.20260623.1` | `7.0.0-dev.20260624.1` | | [effect](https://github.com/Effect-TS/effect/tree/HEAD/packages/effect) | `4.0.0-beta.87` | `4.0.0-beta.88` | Updates `@anthropic-ai/claude-agent-sdk` from 0.3.187 to 0.3.191
Release notes

Sourced from @​anthropic-ai/claude-agent-sdk's releases.

v0.3.191

What's changed

  • Added old_source field to NotebookEdit tool results for replace and delete operations, enabling inline diffs
  • Added seven_day_overage_included to SDKRateLimitInfo.rateLimitType for per-model weekly usage limits
  • Added model_scoped array to usage response for per-model weekly limit windows with utilization and reset times
  • Fixed fast mode reverting to standard after the first turn when settingSources includes user/project settings

Update

npm install @anthropic-ai/claude-agent-sdk@0.3.191
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.191
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.191
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.191

v0.3.190

What's changed

  • Updated to parity with Claude Code v2.1.190

Update

npm install @anthropic-ai/claude-agent-sdk@0.3.190
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.190
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.190
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.190
Changelog

Sourced from @​anthropic-ai/claude-agent-sdk's changelog.

0.3.191

  • Added old_source field to NotebookEdit tool results for replace and delete operations, enabling inline diffs
  • Added seven_day_overage_included to SDKRateLimitInfo.rateLimitType for per-model weekly usage limits
  • Added model_scoped array to usage response for per-model weekly limit windows with utilization and reset times
  • Fixed fast mode reverting to standard after the first turn when settingSources includes user/project settings

0.3.190

  • Updated to parity with Claude Code v2.1.190

0.3.188

  • Updated to parity with Claude Code v2.1.188
Commits

Updates `@anthropic-ai/sdk` from 0.105.0 to 0.106.0
Release notes

Sourced from @​anthropic-ai/sdk's releases.

sdk: v0.106.0

0.106.0 (2026-06-24)

Full Changelog: sdk-v0.105.0...sdk-v0.106.0

Features

  • client: add support for system.message streaming events (a7c14b7)

Bug Fixes

  • helpers: single source for x-stainless-helper, append semantics, and tag the fallback middleware (#107) (106bea0)

Chores

  • api: add support for new refusal category (b38fd01)
  • api: add support for sending User Profile ID in request headers (198583e)
Changelog

Sourced from @​anthropic-ai/sdk's changelog.

0.106.0 (2026-06-24)

Full Changelog: sdk-v0.105.0...sdk-v0.106.0

Features

  • client: add support for system.message streaming events (a7c14b7)

Bug Fixes

  • helpers: single source for x-stainless-helper, append semantics, and tag the fallback middleware (#107) (106bea0)

Chores

  • api: add support for new refusal category (b38fd01)
  • api: add support for sending User Profile ID in request headers (198583e)
Commits

Updates `posthog-node` from 5.38.4 to 5.38.5
Release notes

Sourced from posthog-node's releases.

posthog-node@5.38.5

5.38.5

Patch Changes

Changelog

Sourced from posthog-node's changelog.

5.38.5

Patch Changes

Commits
  • 5c4838f chore: update versions and lockfile [version bump]
  • 8e9689a fix: Dedupe feature flag called events by response (#3955)
  • See full diff in compare view

Updates `@effect/atom-react` from 4.0.0-beta.87 to 4.0.0-beta.88
Changelog

Sourced from @​effect/atom-react's changelog.

4.0.0-beta.88

Patch Changes

Commits

Updates `@effect/platform-bun` from 4.0.0-beta.87 to 4.0.0-beta.88
Commits

Updates `@effect/platform-node` from 4.0.0-beta.87 to 4.0.0-beta.88
Commits

Updates `@effect/sql-pg` from 4.0.0-beta.87 to 4.0.0-beta.88
Commits

Updates `@typescript/native-preview` from 7.0.0-dev.20260623.1 to 7.0.0-dev.20260624.1
Commits

Updates `effect` from 4.0.0-beta.87 to 4.0.0-beta.88
Commits

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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli/package.json | 6 +- pnpm-lock.yaml | 298 +++++++++++++++++++++--------------------- pnpm-workspace.yaml | 12 +- 3 files changed, 158 insertions(+), 158 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 0056938c90..0fc1982f26 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -43,8 +43,8 @@ "jose": "^6.2.3" }, "devDependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.3.187", - "@anthropic-ai/sdk": "^0.105.0", + "@anthropic-ai/claude-agent-sdk": "^0.3.191", + "@anthropic-ai/sdk": "^0.106.0", "@clack/prompts": "^1.6.0", "@effect/atom-react": "catalog:", "@effect/platform-bun": "catalog:", @@ -76,7 +76,7 @@ "oxlint-tsgolint": "catalog:", "pg": "^8.22.0", "pg-copy-streams": "^7.0.0", - "posthog-node": "^5.38.4", + "posthog-node": "^5.38.5", "react": "^19.2.7", "react-devtools-core": "^7.0.1", "semantic-release": "^25.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df0ad7f58c..da19326ba5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,17 +7,17 @@ settings: catalogs: default: '@effect/atom-react': - specifier: 4.0.0-beta.87 - version: 4.0.0-beta.87 + specifier: 4.0.0-beta.88 + version: 4.0.0-beta.88 '@effect/platform-bun': - specifier: 4.0.0-beta.87 - version: 4.0.0-beta.87 + specifier: 4.0.0-beta.88 + version: 4.0.0-beta.88 '@effect/platform-node': - specifier: 4.0.0-beta.87 - version: 4.0.0-beta.87 + specifier: 4.0.0-beta.88 + version: 4.0.0-beta.88 '@effect/sql-pg': - specifier: 4.0.0-beta.87 - version: 4.0.0-beta.87 + specifier: 4.0.0-beta.88 + version: 4.0.0-beta.88 '@effect/vitest': specifier: ^4.0.0-beta.85 version: 4.0.0-beta.91 @@ -37,14 +37,14 @@ catalogs: specifier: ^1.3.14 version: 1.3.14 '@typescript/native-preview': - specifier: 7.0.0-dev.20260623.1 - version: 7.0.0-dev.20260623.1 + specifier: 7.0.0-dev.20260624.1 + version: 7.0.0-dev.20260624.1 '@vitest/coverage-istanbul': specifier: ^4.1.9 version: 4.1.9 effect: - specifier: 4.0.0-beta.87 - version: 4.0.0-beta.87 + specifier: 4.0.0-beta.88 + version: 4.0.0-beta.88 knip: specifier: ^6.17.1 version: 6.23.0 @@ -97,26 +97,26 @@ importers: version: 6.2.3 devDependencies: '@anthropic-ai/claude-agent-sdk': - specifier: ^0.3.187 - version: 0.3.187(@anthropic-ai/sdk@0.105.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) + specifier: ^0.3.191 + version: 0.3.191(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) '@anthropic-ai/sdk': - specifier: ^0.105.0 - version: 0.105.0(zod@4.4.3) + specifier: ^0.106.0 + version: 0.106.0(zod@4.4.3) '@clack/prompts': specifier: ^1.6.0 version: 1.6.0 '@effect/atom-react': specifier: 'catalog:' - version: 4.0.0-beta.87(effect@4.0.0-beta.87)(react@19.2.7)(scheduler@0.27.0) + version: 4.0.0-beta.88(effect@4.0.0-beta.88)(react@19.2.7)(scheduler@0.27.0) '@effect/platform-bun': specifier: 'catalog:' - version: 4.0.0-beta.87(effect@4.0.0-beta.87) + version: 4.0.0-beta.88(effect@4.0.0-beta.88) '@effect/sql-pg': specifier: 'catalog:' - version: 4.0.0-beta.87(effect@4.0.0-beta.87) + version: 4.0.0-beta.88(effect@4.0.0-beta.88) '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.91(effect@4.0.0-beta.87)(vitest@4.1.9) + version: 4.0.0-beta.91(effect@4.0.0-beta.88)(vitest@4.1.9) '@modelcontextprotocol/sdk': specifier: ^1.29.0 version: 1.29.0(zod@4.4.3) @@ -155,7 +155,7 @@ importers: version: 19.2.17 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260624.1 '@vercel/detect-agent': specifier: ^1.2.3 version: 1.2.3 @@ -167,7 +167,7 @@ importers: version: 17.4.2 effect: specifier: 'catalog:' - version: 4.0.0-beta.87 + version: 4.0.0-beta.88 esbuild: specifier: ^0.28.1 version: 0.28.1 @@ -196,8 +196,8 @@ importers: specifier: ^7.0.0 version: 7.0.0 posthog-node: - specifier: ^5.38.4 - version: 5.38.4 + specifier: ^5.38.5 + version: 5.38.5 react: specifier: ^19.2.7 version: 19.2.7 @@ -259,7 +259,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260624.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -320,13 +320,13 @@ importers: dependencies: '@effect/platform-bun': specifier: 'catalog:' - version: 4.0.0-beta.87(effect@4.0.0-beta.87) + version: 4.0.0-beta.88(effect@4.0.0-beta.88) '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.87(effect@4.0.0-beta.87)(ioredis@5.11.0) + version: 4.0.0-beta.88(effect@4.0.0-beta.88)(ioredis@5.11.0) effect: specifier: 'catalog:' - version: 4.0.0-beta.87 + version: 4.0.0-beta.88 undici: specifier: ^8.5.0 version: 8.5.0 @@ -339,7 +339,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260624.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -381,7 +381,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260624.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -409,16 +409,16 @@ importers: dependencies: '@effect/platform-bun': specifier: 'catalog:' - version: 4.0.0-beta.87(effect@4.0.0-beta.87) + version: 4.0.0-beta.88(effect@4.0.0-beta.88) '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.87(effect@4.0.0-beta.87)(ioredis@5.11.0) + version: 4.0.0-beta.88(effect@4.0.0-beta.88)(ioredis@5.11.0) dedent: specifier: ^1.7.2 version: 1.7.2 effect: specifier: 'catalog:' - version: 4.0.0-beta.87 + version: 4.0.0-beta.88 smol-toml: specifier: ^1.7.0 version: 1.7.0 @@ -431,7 +431,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260624.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -455,14 +455,14 @@ importers: dependencies: '@effect/platform-bun': specifier: 'catalog:' - version: 4.0.0-beta.87(effect@4.0.0-beta.87) + version: 4.0.0-beta.88(effect@4.0.0-beta.88) effect: specifier: 'catalog:' - version: 4.0.0-beta.87 + version: 4.0.0-beta.88 devDependencies: '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.91(effect@4.0.0-beta.87)(vitest@4.1.9) + version: 4.0.0-beta.91(effect@4.0.0-beta.88)(vitest@4.1.9) '@tsconfig/bun': specifier: 'catalog:' version: 1.0.10 @@ -471,7 +471,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260624.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -495,10 +495,10 @@ importers: dependencies: '@effect/platform-bun': specifier: 'catalog:' - version: 4.0.0-beta.87(effect@4.0.0-beta.87) + version: 4.0.0-beta.88(effect@4.0.0-beta.88) '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.87(effect@4.0.0-beta.87)(ioredis@5.11.0) + version: 4.0.0-beta.88(effect@4.0.0-beta.88)(ioredis@5.11.0) '@supabase/config': specifier: workspace:* version: link:../config @@ -507,11 +507,11 @@ importers: version: link:../process-compose effect: specifier: 'catalog:' - version: 4.0.0-beta.87 + version: 4.0.0-beta.88 devDependencies: '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.91(effect@4.0.0-beta.87)(vitest@4.1.9) + version: 4.0.0-beta.91(effect@4.0.0-beta.88)(vitest@4.1.9) '@supabase/supabase-js': specifier: ^2.108.2 version: 2.108.2 @@ -523,7 +523,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260623.1 + version: 7.0.0-dev.20260624.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -570,60 +570,60 @@ packages: resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} engines: {node: '>=18'} - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.187': - resolution: {integrity: sha512-0DNzATzaRmjS/Q1T4T9SLP/VT67gRX7J4reYPnEdcTm91lJwjqOPkHr17+sv+7DoerZ421ZG38dPsQd/V67qQw==} + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.191': + resolution: {integrity: sha512-rmpi5ONfuuw7oXdzyr9QJB5sK9Rr1MmdOUsiGutVFpcAqqSXpVYLmMvfaarmXvItgfwgGprzrGDfcAeAQHSUGw==} cpu: [arm64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.187': - resolution: {integrity: sha512-Va3O8lTDa9IHq/DbSJwU63JJknNV3YCBh4PEznOHXJtyFoXHgRjrks4QQvN3baZm79avVq2sn+6tvpTz9CuY8A==} + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.191': + resolution: {integrity: sha512-b/4pfSFTC2gkc0RoGSJ5B3DWo77qZFMi2hRlx8kwaRDiefeIOnbJu42gDBrs4F8nSJJug77HM9AsG6XHXvt5Fg==} cpu: [x64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.187': - resolution: {integrity: sha512-XQ7w2pX0mjPYUC7ayTKiHeAbePOny/ezBATxTWt5JVDZZPfljzvHA0jyXHsN8aLphbgRUfbUpdrtt5b57Bhx5g==} + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.191': + resolution: {integrity: sha512-+Gg7lKwYDr8uiA37EBMYI63if+YGdbrFt2tcRFBB4IhdVf5r1IkKwQ8rEYV6Y0nqGTKoXLh3bRc5QWOe5JwgbQ==} cpu: [arm64] os: [linux] libc: [musl] - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.187': - resolution: {integrity: sha512-VEvrs3MgwckdWRNa7+2rJdyOBbCWGoRaM6OnL+/n9qi4gKlZnXZlj/90Tw9o8ttcN260Opz120/SE1fYwzu/JA==} + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.191': + resolution: {integrity: sha512-vm526e3InpItmh+Ua8a49eGTpFJvZTYLURX9ppKrtFb+Wo7rOgoXmK/Hzf/crEU7h72xtaD7PIOXm4P+psxO3w==} cpu: [arm64] os: [linux] libc: [glibc] - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.187': - resolution: {integrity: sha512-yi2/L5oztFqTDnAZl4mWOINr+r7WBot70gYUxG2jOoqdYUl6j50vG/ME605X80v+l2qBmKjSGxb5trY/6DkRzQ==} + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.191': + resolution: {integrity: sha512-tUwwIXpPBrzJge3E4SQJQX8PuC+lxPb5xQBmyLsWxyjttlgpFpTG/jWPVQO5qH0HgYaEJS5JALHFiYcXwVrEkg==} cpu: [x64] os: [linux] libc: [musl] - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.187': - resolution: {integrity: sha512-s/Fmg29tzMrbdeCbseTpL3DlzfWineSQ3bDPvhdKw4jcsgLC1YgQhIkAyE8WlceUyhQyw82NAUgYoPwfGny6hQ==} + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.191': + resolution: {integrity: sha512-wj43/l0Behb4UWYt6Lomq2W7jhmuU7ARsEP0el6gLGCR348PDeqLGcB4dMOcYMGRZv8TYKfnhDwJnX4pJBT1vQ==} cpu: [x64] os: [linux] libc: [glibc] - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.187': - resolution: {integrity: sha512-HjEl3cDRLAWKopdlrLI54fxTVWq4lZpWA4bTTBVc9UDdDj6AmJIRHKxaCCYLQRvnoswbAUF1sJmJ8EFJ+b6lfw==} + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.191': + resolution: {integrity: sha512-J8aTOi7n6AM7tHFlKt6hlJ4rue0TbY2glqcI2r6K3iK7Fmzvt9zfg00+l8fWKOais4K+8jqJCC4WSCUlz6+tKQ==} cpu: [arm64] os: [win32] - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.187': - resolution: {integrity: sha512-FnrLhoZ8y/+gkZynL12UeQ8pWKCu6B/8myyRrYMNJRZqFw4DZlPvPCywAjFZf1e1hiXCoiSK0Orl5wzKfAeQsQ==} + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.191': + resolution: {integrity: sha512-1rYlPuM9PS37IPCE/sAG7A7oCrWnIb0J7EY6eQBdImdXHEjwBngZ3E8KbtEM79fUTA3C810LqrnFpjOmNExLMA==} cpu: [x64] os: [win32] - '@anthropic-ai/claude-agent-sdk@0.3.187': - resolution: {integrity: sha512-HHkuC3he/Wi8fX/WjfS8mJr4TFPGoAd1QyxOuTwjnyOLboGkX1H+UhBYLxHX1ouFvHnYVJglB2wsuWemVQgahQ==} + '@anthropic-ai/claude-agent-sdk@0.3.191': + resolution: {integrity: sha512-DM9oYbt+WAb/CiD8l/CO6akqPKfyZ4sL1e+lP2O2eS9IR0f58sf90xWeji7V+/zfnIp+jyH3hJ3V6r7odCdB8g==} engines: {node: '>=18.0.0'} peerDependencies: '@anthropic-ai/sdk': '>=0.93.0' '@modelcontextprotocol/sdk': ^1.29.0 zod: ^4.0.0 - '@anthropic-ai/sdk@0.105.0': - resolution: {integrity: sha512-sDyu+aM9cE6uZE+HgRjjHRb+qqb87GHZOx+8bE0YlWetdL1YcVLxn8h9ltxGOflyChTe6PMEo50kMQV4cw0hfg==} + '@anthropic-ai/sdk@0.106.0': + resolution: {integrity: sha512-ufwVvYNDBj2dzOGupBCTaNzBLxqcTnGOzI4z8Wouxlt+mT3J3HuOmatgCy1VmwCHOUueqZ41ERhm0O99OUcbWA==} hasBin: true peerDependencies: zod: ^3.25.0 || ^4.0.0 @@ -724,17 +724,17 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 - '@effect/atom-react@4.0.0-beta.87': - resolution: {integrity: sha512-obmHFW1PQDsb5vet/qaQuByGZL9htoZ7TgShh1E9xpKPZ2JoQNMibkytDyAg48E/s02FVGJxGlvCdEU8PBnTkA==} + '@effect/atom-react@4.0.0-beta.88': + resolution: {integrity: sha512-uHzeUaW1jQNdMXs7qOGW5/PF6UthQzIF5I3IkmlTjUM+1+dSX0oM3Bwy4nIh5UnZQr7sSKw1JE1Zy/8fwiBsIg==} peerDependencies: - effect: ^4.0.0-beta.87 + effect: ^4.0.0-beta.88 react: ^19.2.4 scheduler: '*' - '@effect/platform-bun@4.0.0-beta.87': - resolution: {integrity: sha512-pbV1Nos/RNVuHxpW5aigoASyXgRDKuCc6NxzH3my5RqZVV8DeJ7NhgQrRnlBDX0qBJYdfG8KSYDwj+2qgZ3W8Q==} + '@effect/platform-bun@4.0.0-beta.88': + resolution: {integrity: sha512-BS0s9ecvPcAM6XjVutXb+eYoejepk3gr+G6GoT8vRqxSrhFrdDIY1hTPb4XyvkP9acm4ubIOm3+SSjgtRCLmCw==} peerDependencies: - effect: ^4.0.0-beta.87 + effect: ^4.0.0-beta.88 '@effect/platform-node-shared@4.0.0-beta.91': resolution: {integrity: sha512-JX++yhomaOEpQmnTYuhn+pKAgKg5MQvycAf1rXGtyVKi202Nc5nK37xbywDcugfwrc1FrPxrxa3BY3lbSc74cw==} @@ -742,17 +742,17 @@ packages: peerDependencies: effect: ^4.0.0-beta.91 - '@effect/platform-node@4.0.0-beta.87': - resolution: {integrity: sha512-bmW72GSCWcvsQStO39BmRgwcQ+311qXm9/MjllS6MgtYP0Id0Ivlysj5L+YTjCRwfJAimmlZEWBphWTwbA+bXw==} + '@effect/platform-node@4.0.0-beta.88': + resolution: {integrity: sha512-k05jVJ8uYFm5VNfFFndM0kePKz9+RNW1r4ofiTMRadWzH6YqlJcuAU7Gj6RXCg2cKV+Ad/CsDqBD6QTi44ZKow==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.87 + effect: ^4.0.0-beta.88 ioredis: ^5.7.0 - '@effect/sql-pg@4.0.0-beta.87': - resolution: {integrity: sha512-lKWYzw+zwzycfbrU3SQRwshKHt7LIotO6u/KyLwsk/oqNDbqQExhVkIKE6OrM3NvfjqyF6SJMWquBRsnANKkcg==} + '@effect/sql-pg@4.0.0-beta.88': + resolution: {integrity: sha512-aZNElnvf/RUU7wavt21tOGcqlaSYHdltcam5B4tSIA2Indq0wTnIvTEaKcwoFocFcwE23CxR59a7o3zmUQ+u/Q==} peerDependencies: - effect: ^4.0.0-beta.87 + effect: ^4.0.0-beta.88 '@effect/vitest@4.0.0-beta.91': resolution: {integrity: sha512-RTowh92EawJWk+PewFrbqmPSOjbJ9xcq4BcWGV4t0aaYIvZj5g5dubXUvCioh6aq4lkOnTEJmC6seGZjDFO/MQ==} @@ -2107,8 +2107,8 @@ packages: resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==} engines: {node: '>=12'} - '@posthog/core@1.39.2': - resolution: {integrity: sha512-G/TL5Xykl+9xCDtBwHHPRSToS/43UjzyxKOfhlTwu/hJHt3v39Q2OFu8hdum71Iu7+tuxxISGVcFMlIST7ksjA==} + '@posthog/core@1.39.3': + resolution: {integrity: sha512-oR+B8Q5O61N+W2+HVOBG9dbxAT/+OVxX+XvpNj6KYgptN2EB14JiKQ9Rm7DNpErNTLV7WApZEK/URkZgldOxfg==} '@posthog/types@1.392.0': resolution: {integrity: sha512-nctNujXL3FC1v99FktaTMSugSD9ZOZekEpahUSafkU2TSvW+XGKNkQZbokuJtiWvPBK208dwMJva8UfBkChqpw==} @@ -2864,50 +2864,50 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260623.1': - resolution: {integrity: sha512-8AX9NwC+G6Sbh5hNLnx8YgxoRV/8BH8FQRtZ86OTtUQfESMRvwszOGTtfcC32g86O5jsEQPDHXKVSnsIzWh6lg==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260624.1': + resolution: {integrity: sha512-g8CqDkYCHTCYdhBHXs5cMraBurOS+KrcMFxE0SsaKZoI6Tnp+le1aWvxUBbzNKJYyThHJqb/1mLopzEJxJCuKA==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260623.1': - resolution: {integrity: sha512-ef+JmOjtTu0BRdhAq69WbzCWjZsG6gqS8xDI4fKABdWXlrVojPkrD2OObog3PC7Qte8us6QspQvvnKTf7yKFSQ==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260624.1': + resolution: {integrity: sha512-P00JVvSV90eioYDuINAKmOSA8yhFTWLq6RvS5lrCfUuDlcgr2kSOgZAfFHIksHBVz6ZXpAXpa0dHPmc5SJ3Ymw==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260623.1': - resolution: {integrity: sha512-xb9m0WXvIPHOogGTC02iBv3cwRJ7q+Ql2ABcsu4kHYc1p02JSbIBEfu3g58TpcFZyODsO+8WaDylx/hBiGzlVw==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260624.1': + resolution: {integrity: sha512-cppM2yTZ/Gd1hOXy8NEJcUBxJ0O0zl9CU3OU1ZWZ/OHWWX/ukEzCCr94SUwJhjIWOylBCpIYkrvYoTwxNa94XQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260623.1': - resolution: {integrity: sha512-8f9dT1RaOK3Wy/puboAcCik4HBX6mfWt5hOMipWehDfInNFI6XP3ZYErShowBQ0Tkln5qdGqDIoq452PO/ZwCA==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260624.1': + resolution: {integrity: sha512-eWHELvfQMkVRjafMd+3ATgM9p9yAergJaM4AOY8AekCNWnHFwUrp/ohh+ryyMUIqque5jjb/kuTiOiGj728I2Q==} engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260623.1': - resolution: {integrity: sha512-AZRp6q0SyVw9xe9kpyfTmavqkQKizDhmOLglDHbxAu4Y3inL6r2GSE7q3w/eO4R259dIwl5TM52DZKK8onoT6Q==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260624.1': + resolution: {integrity: sha512-FaB8rS+rKYz4nDrEsHsF3b4cn7eCKCYroMJReA375OuQ6PHcmCNQ6QlVetA0dfFBxTTgejmoKyfw9xgAA5P4Yw==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260623.1': - resolution: {integrity: sha512-DXRMXOe/JEmf2CULZZn1xvRuJrnZ9DxzDubxtW85pE0aSLgl+VuplWCsZrBxaCZDrWMRIfMDV8Y0y+Yftx/jeQ==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260624.1': + resolution: {integrity: sha512-BgkqbCmSHDb5UxqWaFlFFJ/DHNT3lEUO4W8627ap6+QthJZuXk2imiHAX3PgYXC6en9fLLyR6jjcseAa4CCshg==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260623.1': - resolution: {integrity: sha512-kYB1I6tdxWHjhSt8B2v9CHmpOJwJvD88LUCxiBS9C59pwrGe+LAwZvbTTJxvULNaUEjxdiMomYerWWhD/09Y3g==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260624.1': + resolution: {integrity: sha512-WaZ+ue63NgB2j/lqjirfevh/TqcsCxSqnKhGGiRnlxHyYIBcoq+x7KngyEnyGIaywJE1PcFeXA+2EMSIPlSEiQ==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260623.1': - resolution: {integrity: sha512-33AkAhmRu1/w4nRFLnJ9lic7FSzW4zWfU1u5DP7A2+j1445SSNvf3Q4WGtDzdAR5HSlk8vXEa0B4xUHR5vM1tg==} + '@typescript/native-preview@7.0.0-dev.20260624.1': + resolution: {integrity: sha512-ogwfNo1xuAutOF8RbTCo3Ut0q/65u2ucOeHizi6O14q+3vnelNS+u8qVC2QWXubMcwtuN5E9cbfPslvGC4kdwA==} engines: {node: '>=16.20.0'} hasBin: true @@ -3670,8 +3670,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@4.0.0-beta.87: - resolution: {integrity: sha512-GANlkUQo9pPN4tIOeUhF/gdgBq4OA1w5lUstT/GjS3Csd48Y6KV1FbxsEtq4/SNRBkl6JE0nU9pP9fi6/+GU7A==} + effect@4.0.0-beta.88: + resolution: {integrity: sha512-ml7Xr2BhU12RnNk5xX0lPqHv5pIXg7E161ooGlYncFd17Z5NKXL26DnfvwZsNBxYLS0ToGVMhb4Em7Z0A4dTDA==} ejs@5.0.1: resolution: {integrity: sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==} @@ -5659,8 +5659,8 @@ packages: postgres-range@1.1.4: resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} - posthog-node@5.38.4: - resolution: {integrity: sha512-Jti056f+5C9y1MXb5yhxwwYBITxypWZHWcAp+UevcZ7/AKEQdSB9FiJP3WzEznw4vH3Bz0IlRKVgsofizRAoEA==} + posthog-node@5.38.5: + resolution: {integrity: sha512-ZqMK2MFXQWkfdc0PDzPMRBrZXHCZLvl9jbvaaIyXTgbfn802/hMJ8M24BJiChPrMpZejsTzfAq8ZsQYF8zcOoA==} engines: {node: ^20.20.0 || >=22.22.0} peerDependencies: rxjs: ^7.0.0 @@ -6849,46 +6849,46 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.187': + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.191': optional: true - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.187': + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.191': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.187': + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.191': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.187': + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.191': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.187': + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.191': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.187': + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.191': optional: true - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.187': + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.191': optional: true - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.187': + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.191': optional: true - '@anthropic-ai/claude-agent-sdk@0.3.187(@anthropic-ai/sdk@0.105.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': + '@anthropic-ai/claude-agent-sdk@0.3.191(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': dependencies: - '@anthropic-ai/sdk': 0.105.0(zod@4.4.3) + '@anthropic-ai/sdk': 0.106.0(zod@4.4.3) '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) zod: 4.4.3 optionalDependencies: - '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.187 - '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.187 - '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.187 - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.187 - '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.187 - '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.187 - '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.187 - '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.187 + '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.191 + '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.191 + '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.191 + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.191 + '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.191 + '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.191 + '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.191 + '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.191 - '@anthropic-ai/sdk@0.105.0(zod@4.4.3)': + '@anthropic-ai/sdk@0.106.0(zod@4.4.3)': dependencies: json-schema-to-ts: 3.1.1 standardwebhooks: 1.0.0 @@ -7037,33 +7037,33 @@ snapshots: dependencies: '@noble/ciphers': 1.3.0 - '@effect/atom-react@4.0.0-beta.87(effect@4.0.0-beta.87)(react@19.2.7)(scheduler@0.27.0)': + '@effect/atom-react@4.0.0-beta.88(effect@4.0.0-beta.88)(react@19.2.7)(scheduler@0.27.0)': dependencies: - effect: 4.0.0-beta.87 + effect: 4.0.0-beta.88 react: 19.2.7 scheduler: 0.27.0 - '@effect/platform-bun@4.0.0-beta.87(effect@4.0.0-beta.87)': + '@effect/platform-bun@4.0.0-beta.88(effect@4.0.0-beta.88)': dependencies: - '@effect/platform-node-shared': 4.0.0-beta.91(effect@4.0.0-beta.87) - effect: 4.0.0-beta.87 + '@effect/platform-node-shared': 4.0.0-beta.91(effect@4.0.0-beta.88) + effect: 4.0.0-beta.88 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node-shared@4.0.0-beta.91(effect@4.0.0-beta.87)': + '@effect/platform-node-shared@4.0.0-beta.91(effect@4.0.0-beta.88)': dependencies: '@types/ws': 8.18.1 - effect: 4.0.0-beta.87 + effect: 4.0.0-beta.88 ws: 8.21.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node@4.0.0-beta.87(effect@4.0.0-beta.87)(ioredis@5.11.0)': + '@effect/platform-node@4.0.0-beta.88(effect@4.0.0-beta.88)(ioredis@5.11.0)': dependencies: - '@effect/platform-node-shared': 4.0.0-beta.91(effect@4.0.0-beta.87) - effect: 4.0.0-beta.87 + '@effect/platform-node-shared': 4.0.0-beta.91(effect@4.0.0-beta.88) + effect: 4.0.0-beta.88 ioredis: 5.11.0 mime: 4.1.0 undici: 8.5.0 @@ -7071,9 +7071,9 @@ snapshots: - bufferutil - utf-8-validate - '@effect/sql-pg@4.0.0-beta.87(effect@4.0.0-beta.87)': + '@effect/sql-pg@4.0.0-beta.88(effect@4.0.0-beta.88)': dependencies: - effect: 4.0.0-beta.87 + effect: 4.0.0-beta.88 pg: 8.22.0 pg-connection-string: 2.12.0 pg-cursor: 2.20.0(pg@8.22.0) @@ -7082,9 +7082,9 @@ snapshots: transitivePeerDependencies: - pg-native - '@effect/vitest@4.0.0-beta.91(effect@4.0.0-beta.87)(vitest@4.1.9)': + '@effect/vitest@4.0.0-beta.91(effect@4.0.0-beta.88)(vitest@4.1.9)': dependencies: - effect: 4.0.0-beta.87 + effect: 4.0.0-beta.88 vitest: 4.1.9(@types/node@26.0.1)(@vitest/coverage-istanbul@4.1.9)(vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) '@emnapi/core@1.10.0': @@ -8002,7 +8002,7 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@posthog/core@1.39.2': + '@posthog/core@1.39.3': dependencies: '@posthog/types': 1.392.0 @@ -8742,36 +8742,36 @@ snapshots: dependencies: '@types/node': 26.0.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260623.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260624.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260623.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260624.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260623.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260624.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260623.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260624.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260623.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260624.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260623.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260624.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260623.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260624.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260623.1': + '@typescript/native-preview@7.0.0-dev.20260624.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260623.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260623.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260623.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260623.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260623.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260623.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260623.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260624.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260624.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260624.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260624.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260624.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260624.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260624.1 '@ungap/structured-clone@1.3.2': {} @@ -9587,7 +9587,7 @@ snapshots: ee-first@1.1.1: {} - effect@4.0.0-beta.87: + effect@4.0.0-beta.88: dependencies: '@standard-schema/spec': 1.1.0 fast-check: 4.8.0 @@ -12060,9 +12060,9 @@ snapshots: postgres-range@1.1.4: {} - posthog-node@5.38.4: + posthog-node@5.38.5: dependencies: - '@posthog/core': 1.39.2 + '@posthog/core': 1.39.3 pretty-ms@9.3.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 314ddf0419..3df78107c9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,19 +12,19 @@ allowBuilds: sharp: true catalog: - "@effect/atom-react": "4.0.0-beta.87" - "@effect/platform-bun": "4.0.0-beta.87" - "@effect/platform-node": "4.0.0-beta.87" - "@effect/sql-pg": "4.0.0-beta.87" + "@effect/atom-react": "4.0.0-beta.88" + "@effect/platform-bun": "4.0.0-beta.88" + "@effect/platform-node": "4.0.0-beta.88" + "@effect/sql-pg": "4.0.0-beta.88" "@effect/vitest": "^4.0.0-beta.85" "@nx/devkit": "^23.0.0" "@swc-node/register": "^1.10.9" "@swc/core": "^1.15.43" "@tsconfig/bun": "^1.0.10" "@types/bun": "^1.3.14" - "@typescript/native-preview": "7.0.0-dev.20260623.1" + "@typescript/native-preview": "7.0.0-dev.20260624.1" "@vitest/coverage-istanbul": "^4.1.9" - "effect": "4.0.0-beta.87" + "effect": "4.0.0-beta.88" "knip": "^6.17.1" "nx": "^23.0.0" "oxfmt": "^0.56.0" From edbc8cd7fc85ca0f3be73cb2091cbf03f0445e12 Mon Sep 17 00:00:00 2001 From: Vaibhav <117663341+7ttp@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:47:56 +0530 Subject: [PATCH 17/48] fix(cli): query output (#5763) ## TL;DR makes `db query --output-format json` resolve to JSON even when `--agent no` is used ## ref: - Closes https://github.com/supabase/cli/issues/5762 --- .../legacy/commands/db/query/query.handler.ts | 21 +++++-- .../db/query/query.integration.test.ts | 60 +++++++++++++++++++ 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/apps/cli/src/legacy/commands/db/query/query.handler.ts b/apps/cli/src/legacy/commands/db/query/query.handler.ts index 0224799cec..4a51f9e301 100644 --- a/apps/cli/src/legacy/commands/db/query/query.handler.ts +++ b/apps/cli/src/legacy/commands/db/query/query.handler.ts @@ -121,7 +121,15 @@ export const legacyDbQuery = Effect.fn("legacy.db.query")(function* (flags: Lega const jsonData = fieldTypeIds === undefined ? data : legacyCoerceLocalJsonRows(data, fieldTypeIds); const boundary = agentMode ? yield* random.randomHex(BOUNDARY_BYTES) : ""; - yield* output.raw(legacyRenderJson(cols, jsonData, agentMode, boundary, advisory)); + const rendered = legacyRenderJson(cols, jsonData, agentMode, boundary, advisory); + if (output.format === "stream-json" && Option.getOrUndefined(outputFlag) !== "json") { + const compactRendered = rendered.trimEnd().replaceAll("\n", ""); + yield* output.raw( + `{"type":"result","data":${compactRendered},"timestamp":${JSON.stringify(new Date().toISOString())}}\n`, + ); + return; + } + yield* output.raw(rendered); }); const runLocal = ( @@ -356,9 +364,10 @@ export const legacyDbQuery = Effect.fn("legacy.db.query")(function* (flags: Lega // 2. Agent mode + the resolved payload format, mirroring Go's resolution // (`cmd/db.go:316-325`): an explicit `-o json|table|csv` always wins; // otherwise default to JSON for agents and a table for humans. The global - // `-o` choice is a union (see `query.command.ts`), so values outside Go's - // `json|table|csv` enum (`pretty|yaml|toml|env`) fall through to the - // agent-mode default rather than erroring. + // `-o` choice is a union (see `query.command.ts`), while TS + // `--output-format json|stream-json` must also resolve to JSON here, so + // values outside Go's `json|table|csv` enum (`pretty|yaml|toml|env`) + // fall through to the agent/machine default rather than erroring. const agentMode = legacyResolveAgentMode(agentFlag, aiTool.name); const explicit = Option.getOrUndefined(outputFlag); const format: LegacyResolvedFormat = @@ -366,9 +375,9 @@ export const legacyDbQuery = Effect.fn("legacy.db.query")(function* (flags: Lega ? "json" : explicit === "csv" ? "csv" - : explicit === "table" + : explicit === "table" || explicit === "pretty" ? "table" - : agentMode + : explicit === undefined && (output.format !== "text" || agentMode) ? "json" : "table"; diff --git a/apps/cli/src/legacy/commands/db/query/query.integration.test.ts b/apps/cli/src/legacy/commands/db/query/query.integration.test.ts index 8779891687..591a3efb4b 100644 --- a/apps/cli/src/legacy/commands/db/query/query.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/query/query.integration.test.ts @@ -432,6 +432,66 @@ describe("legacy db query integration", () => { }).pipe(Effect.provide(layer)); }); + it.live("renders plain JSON for --output-format json with --agent no", () => { + const { layer, out } = setup({ result: SELECT_RESULT, agent: "no", format: "json" }); + return Effect.gen(function* () { + yield* legacyDbQuery(flags({ sql: Option.some("select 1"), local: Option.some(true) })); + expect(JSON.parse(out.stdoutText)).toEqual([ + { id: 1, name: "alice" }, + { id: 2, name: "bob" }, + ]); + }).pipe(Effect.provide(layer)); + }); + + it.live("emits a result event for --output-format stream-json with --agent no", () => { + const { layer, out } = setup({ result: SELECT_RESULT, agent: "no", format: "stream-json" }); + return Effect.gen(function* () { + yield* legacyDbQuery(flags({ sql: Option.some("select 1"), local: Option.some(true) })); + expect(out.stdoutText.trimEnd().split("\n")).toHaveLength(1); + expect(JSON.parse(out.stdoutText)).toEqual( + expect.objectContaining({ + type: "result", + data: [ + { id: 1, name: "alice" }, + { id: 2, name: "bob" }, + ], + }), + ); + }).pipe(Effect.provide(layer)); + }); + + it.live("preserves exact bigint tokens in --output-format stream-json", () => { + const { layer, out } = setup({ + result: { + fields: ["n"], + fieldTypeIds: [20], + rows: [["9223372036854775807"]], + commandTag: "SELECT 1", + }, + agent: "no", + format: "stream-json", + }); + return Effect.gen(function* () { + yield* legacyDbQuery(flags({ sql: Option.some("select 1"), local: Option.some(true) })); + expect(out.stdoutText.trimEnd().split("\n")).toHaveLength(1); + expect(out.stdoutText).toContain('"n": 9223372036854775807'); + }).pipe(Effect.provide(layer)); + }); + + it.live("lets --output pretty win over --output-format json", () => { + const { layer, out } = setup({ + result: SELECT_RESULT, + agent: "no", + format: "json", + goOutput: "pretty", + }); + return Effect.gen(function* () { + yield* legacyDbQuery(flags({ sql: Option.some("select 1"), local: Option.some(true) })); + expect(out.stdoutText).toContain("│ id │ name │"); + expect(out.messages.find((message) => message.type === "success")).toBeUndefined(); + }).pipe(Effect.provide(layer)); + }); + it.live("fails JSON output on a non-finite float (Go's json.Encoder error), no stdout", () => { // select 'NaN'::float8 -o json — Go fails to encode and exits non-zero with empty // stdout, rather than emitting `null` like JSON.stringify. From 2060d72c4c58d0bf530f91d05d1d4b732e6b2e4c Mon Sep 17 00:00:00 2001 From: Vaibhav <117663341+7ttp@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:06:09 +0530 Subject: [PATCH 18/48] fix(cli): function deploy paths (#5755) ## TL;DR `functions deploy` was excluding monorepo imports outside `supabase/` this fixes it by using the nearest git root as the source boundary, following existing pattern ## ref: - Closes https://github.com/supabase/cli/issues/3467 - Related https://github.com/supabase/cli/issues/5735 - Related https://github.com/supabase/cli/issues/4550 --- .../functions/serve/serve.integration.test.ts | 97 ++++++++ .../gen/signing-key/signing-key.handler.ts | 27 +-- .../deploy/deploy.integration.test.ts | 212 +++++++++++++++++- apps/cli/src/next/config/project-gitignore.ts | 27 +-- apps/cli/src/shared/functions/deploy.ts | 77 ++++--- apps/cli/src/shared/git/git-root.ts | 21 ++ 6 files changed, 378 insertions(+), 83 deletions(-) create mode 100644 apps/cli/src/shared/git/git-root.ts diff --git a/apps/cli/src/legacy/commands/functions/serve/serve.integration.test.ts b/apps/cli/src/legacy/commands/functions/serve/serve.integration.test.ts index be575f1409..370af5aacf 100644 --- a/apps/cli/src/legacy/commands/functions/serve/serve.integration.test.ts +++ b/apps/cli/src/legacy/commands/functions/serve/serve.integration.test.ts @@ -859,6 +859,103 @@ describe("legacy functions serve integration", () => { }); }); + it.live("binds git-root workspace imports for serve", () => { + deployMockState.runHandler = (command, args) => { + if (command !== "docker") { + throw new Error(`unexpected process: ${command}`); + } + if (args[0] === "container" && args[1] === "inspect") { + return { exitCode: 0, stdout: "", stderr: "" }; + } + if (args[0] === "container" && args[1] === "rm") { + return { exitCode: 0, stdout: "", stderr: "" }; + } + if (args[0] === "run") { + return { exitCode: 0, stdout: "edge-runtime-id\n", stderr: "" }; + } + if (args[0] === "exec") { + return { exitCode: 0, stdout: "", stderr: "" }; + } + throw new Error(`unexpected docker args: ${args.join(" ")}`); + }; + + const childSpawner = mockDockerLogSpawner([ + { + exitCode: 1, + stderr: "workspace import logs failed", + }, + ]); + + return Effect.gen(function* () { + const sharedPath = join(tempRoot.current, "packages", "shared", "src", "index.ts"); + + yield* Effect.promise(() => mkdir(join(tempRoot.current, ".git"), { recursive: true })); + yield* Effect.promise(() => + writeProjectConfig( + [ + 'project_id = "test-project"', + "[functions.hello]", + 'entrypoint = "./functions/hello/index.ts"', + 'import_map = "./functions/hello/deno.json"', + "", + ].join("\n"), + ), + ); + yield* Effect.promise(() => + writeProjectFile("packages/shared/src/index.ts", 'export const shared = "hello"\n'), + ); + yield* Effect.promise(() => + writeFunctionFile( + "hello", + "index.ts", + [ + 'import { shared } from "@repo/shared"', + "Deno.serve(() => new Response(shared))", + "", + ].join("\n"), + ), + ); + yield* Effect.promise(() => + writeFunctionFile( + "hello", + "deno.json", + JSON.stringify({ + imports: { + "@repo/shared": "../../../packages/shared/src/index.ts", + }, + }), + ), + ); + + const { layer } = setupServe({ childSpawner }); + const error = yield* legacyFunctionsServe(baseFlags()).pipe( + Effect.provide(layer), + Effect.flip, + ); + + expect(error).toBeInstanceOf(Error); + if (error instanceof Error) { + expect(error.message).toContain("workspace import logs failed"); + } + + const dockerRun = deployMockState.runCalls.find( + (call) => call.command === "docker" && call.args[0] === "run", + ); + expect(dockerRun).toBeDefined(); + if (dockerRun === undefined) { + throw new Error("expected docker run invocation"); + } + const resolvedSharedPath = realpathSync(sharedPath); + expect( + extractFlagValues(dockerRun.args, "-v").some( + (value) => + value.startsWith(`${resolvedSharedPath}:`) && + value.endsWith("/packages/shared/src/index.ts:ro"), + ), + ).toBe(true); + }); + }); + it.live("restarts the runtime when watched files change", () => { deployMockState.runHandler = (command, args) => { if (command !== "docker") { diff --git a/apps/cli/src/legacy/commands/gen/signing-key/signing-key.handler.ts b/apps/cli/src/legacy/commands/gen/signing-key/signing-key.handler.ts index d760643230..6396c658c6 100644 --- a/apps/cli/src/legacy/commands/gen/signing-key/signing-key.handler.ts +++ b/apps/cli/src/legacy/commands/gen/signing-key/signing-key.handler.ts @@ -5,6 +5,7 @@ import { Effect, FileSystem, Option, Path } from "effect"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { LegacyCliConfig } from "../../../config/legacy-cli-config.service.ts"; +import { findGitRootPath } from "../../../../shared/git/git-root.ts"; import { LegacyDebugLogger } from "../../../shared/legacy-debug-logger.service.ts"; import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; import { LegacyYesFlag } from "../../../../shared/legacy/global-flags.ts"; @@ -219,37 +220,19 @@ const loadSigningKeysConfig = Effect.fnUntraced(function* (cwd: string) { } satisfies ResolvedSigningKeysConfig; }); -const findGitRoot = Effect.fnUntraced(function* (start: string) { - const fs = yield* FileSystem.FileSystem; - const path = yield* Path.Path; - - let current = path.resolve(start); - const root = path.parse(current).root; - - while (true) { - if (yield* fs.exists(path.join(current, ".git")).pipe(Effect.orElseSucceed(() => false))) { - return Option.some(current); - } - if (current === root) { - return Option.none(); - } - current = path.dirname(current); - } -}); - const isGitIgnored = Effect.fnUntraced(function* (filePath: string, searchFrom: string) { const path = yield* Path.Path; - const gitRoot = yield* findGitRoot(searchFrom); - if (Option.isNone(gitRoot)) { + const gitRoot = yield* Effect.tryPromise(() => findGitRootPath(searchFrom)).pipe(Effect.orDie); + if (gitRoot === undefined) { return Option.none(); } const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; - const relative = path.relative(gitRoot.value, filePath).replaceAll("\\", "/"); + const relative = path.relative(gitRoot, filePath).replaceAll("\\", "/"); const command = ChildProcess.make( "git", // `--` terminates flag parsing so a path beginning with `-` is never read as a git option. - ["-C", gitRoot.value, "check-ignore", "--quiet", "--", relative], + ["-C", gitRoot, "check-ignore", "--quiet", "--", relative], { detached: true, stdin: "ignore", diff --git a/apps/cli/src/next/commands/functions/deploy/deploy.integration.test.ts b/apps/cli/src/next/commands/functions/deploy/deploy.integration.test.ts index 0b73309418..3710bdeacb 100644 --- a/apps/cli/src/next/commands/functions/deploy/deploy.integration.test.ts +++ b/apps/cli/src/next/commands/functions/deploy/deploy.integration.test.ts @@ -1176,10 +1176,114 @@ describe("functions deploy", () => { expect(api.multiparts[0]?.fileNames).not.toContain(secretPath); expect(api.multiparts[0]?.fileNames).not.toContain("access-token.txt"); - expect(out.stderrText).toContain("WARN: Skipping import path outside project root:"); + expect(out.stderrText).toContain("WARN: Skipping import path outside source root:"); }).pipe(Effect.ensuring(Effect.all([cleanupTempDir(tempDir), cleanupTempDir(outsideDir)]))); }); + it.live("uploads git-root workspace imports through the API", () => { + const repoRoot = makeTempDir(); + const projectRoot = join(repoRoot, "app"); + const sharedPath = join(repoRoot, "packages", "shared", "src", "index.ts"); + + return Effect.gen(function* () { + yield* Effect.promise(() => mkdir(join(repoRoot, ".git"), { recursive: true })); + yield* Effect.promise(() => writeProjectConfig(projectRoot)); + yield* Effect.promise(() => + writeLocalFunction( + projectRoot, + "hello-world", + [ + 'import { shared } from "@repo/shared"', + "Deno.serve(() => new Response(shared))", + "", + ].join("\n"), + ), + ); + yield* Effect.promise(() => mkdir(dirname(sharedPath), { recursive: true })); + yield* Effect.promise(() => writeFile(sharedPath, 'export const shared = "ok"\n')); + yield* Effect.promise(() => + writeFile( + join(projectRoot, "supabase", "functions", "hello-world", "deno.json"), + JSON.stringify({ + imports: { "@repo/shared": "../../../../packages/shared/src/index.ts" }, + }), + ), + ); + + const { out, api, layer } = setup(projectRoot, { + projectRoot, + rawArgs: ["functions", "deploy", "hello-world"], + }); + + yield* functionsDeploy({ + ...BASE_FLAGS, + functionNames: ["hello-world"], + }).pipe(Effect.provide(layer)); + + expect(api.multiparts[0]?.fileNames).toContain("app/supabase/functions/hello-world/index.ts"); + expect(api.multiparts[0]?.fileNames).toContain( + "app/supabase/functions/hello-world/deno.json", + ); + expect(api.multiparts[0]?.fileNames).toContain("packages/shared/src/index.ts"); + expect(api.multiparts[0]?.metadata).toContain( + '"entrypoint_path":"app/supabase/functions/hello-world/index.ts"', + ); + expect(api.multiparts[0]?.metadata).toContain( + '"import_map_path":"app/supabase/functions/hello-world/deno.json"', + ); + expect(out.stderrText).not.toContain("WARN: Skipping import path outside source root:"); + }).pipe(Effect.ensuring(cleanupTempDir(repoRoot))); + }); + + it.live("treats a .git file as the repo root marker for API uploads", () => { + const repoRoot = makeTempDir(); + const projectRoot = join(repoRoot, "app"); + const sharedPath = join(repoRoot, "packages", "shared", "src", "index.ts"); + + return Effect.gen(function* () { + yield* Effect.promise(() => + writeFile(join(repoRoot, ".git"), "gitdir: /tmp/worktree/.git\n"), + ); + yield* Effect.promise(() => writeProjectConfig(projectRoot)); + yield* Effect.promise(() => + writeLocalFunction( + projectRoot, + "hello-world", + [ + 'import { shared } from "@repo/shared"', + "Deno.serve(() => new Response(shared))", + "", + ].join("\n"), + ), + ); + yield* Effect.promise(() => mkdir(dirname(sharedPath), { recursive: true })); + yield* Effect.promise(() => writeFile(sharedPath, 'export const shared = "ok"\n')); + yield* Effect.promise(() => + writeFile( + join(projectRoot, "supabase", "functions", "hello-world", "deno.json"), + JSON.stringify({ + imports: { "@repo/shared": "../../../../packages/shared/src/index.ts" }, + }), + ), + ); + + const { api, layer } = setup(projectRoot, { + projectRoot, + rawArgs: ["functions", "deploy", "hello-world"], + }); + + yield* functionsDeploy({ + ...BASE_FLAGS, + functionNames: ["hello-world"], + }).pipe(Effect.provide(layer)); + + expect(api.multiparts[0]?.fileNames).toContain("packages/shared/src/index.ts"); + expect(api.multiparts[0]?.metadata).toContain( + '"entrypoint_path":"app/supabase/functions/hello-world/index.ts"', + ); + }).pipe(Effect.ensuring(cleanupTempDir(repoRoot))); + }); + it.live("falls back to source upload and warns when explicit Docker is not running", () => { const tempDir = makeTempDir(); const child = mockChildProcessSpawner({ exitCode: 1 }); @@ -1216,6 +1320,112 @@ describe("functions deploy", () => { }).pipe(Effect.ensuring(cleanupTempDir(tempDir))); }); + it.live("binds git-root workspace imports for Docker bundling", () => { + const repoRoot = makeTempDir(); + const projectRoot = join(repoRoot, "app"); + const sharedPath = join(repoRoot, "packages", "shared", "src", "index.ts"); + const child = mockChildProcessSpawner({ + exitCode: 0, + onSpawn: (record) => { + if (record.command !== "docker" || record.args[0] !== "run") { + return; + } + const outputPath = resolveDockerOutputPath(record.args); + mkdirSync(dirname(outputPath), { recursive: true }); + writeFileSync(outputPath, "eszip-test-output"); + }, + }); + + return Effect.gen(function* () { + yield* Effect.promise(() => mkdir(join(repoRoot, ".git"), { recursive: true })); + yield* Effect.promise(() => writeProjectConfig(projectRoot)); + yield* Effect.promise(() => + writeLocalFunction( + projectRoot, + "hello-world", + [ + 'import { shared } from "@repo/shared"', + "Deno.serve(() => new Response(shared))", + "", + ].join("\n"), + ), + ); + yield* Effect.promise(() => mkdir(dirname(sharedPath), { recursive: true })); + yield* Effect.promise(() => writeFile(sharedPath, 'export const shared = "ok"\n')); + yield* Effect.promise(() => + writeFile( + join(projectRoot, "supabase", "functions", "hello-world", "deno.json"), + JSON.stringify({ + imports: { "@repo/shared": "../../../../packages/shared/src/index.ts" }, + }), + ), + ); + + const { layer } = setup(projectRoot, { + projectRoot, + rawArgs: ["functions", "deploy", "hello-world", "--use-docker"], + childLayer: child.layer, + }); + + yield* functionsDeploy({ + ...BASE_FLAGS, + functionNames: ["hello-world"], + useDocker: true, + }).pipe(Effect.provide(layer)); + + expect(child.spawned.at(-1)?.args).toContain( + yield* Effect.promise(() => expectedDockerBind(sharedPath)), + ); + }).pipe(Effect.ensuring(cleanupTempDir(repoRoot))); + }); + + it.live("keeps the nearest git root as the source boundary", () => { + const outerRoot = makeTempDir(); + const repoRoot = join(outerRoot, "nested-repo"); + const projectRoot = join(repoRoot, "app"); + const sharedPath = join(outerRoot, "packages", "shared", "src", "index.ts"); + + return Effect.gen(function* () { + yield* Effect.promise(() => mkdir(repoRoot, { recursive: true })); + yield* Effect.promise(() => mkdir(join(repoRoot, ".git"), { recursive: true })); + yield* Effect.promise(() => writeProjectConfig(projectRoot)); + yield* Effect.promise(() => + writeLocalFunction( + projectRoot, + "hello-world", + [ + 'import { shared } from "@repo/shared"', + "Deno.serve(() => new Response(shared))", + "", + ].join("\n"), + ), + ); + yield* Effect.promise(() => mkdir(dirname(sharedPath), { recursive: true })); + yield* Effect.promise(() => writeFile(sharedPath, 'export const shared = "blocked"\n')); + yield* Effect.promise(() => + writeFile( + join(projectRoot, "supabase", "functions", "hello-world", "deno.json"), + JSON.stringify({ + imports: { "@repo/shared": "../../../../../packages/shared/src/index.ts" }, + }), + ), + ); + + const { out, api, layer } = setup(projectRoot, { + projectRoot, + rawArgs: ["functions", "deploy", "hello-world"], + }); + + yield* functionsDeploy({ + ...BASE_FLAGS, + functionNames: ["hello-world"], + }).pipe(Effect.provide(layer)); + + expect(api.multiparts[0]?.fileNames).not.toContain("packages/shared/src/index.ts"); + expect(out.stderrText).toContain("WARN: Skipping import path outside source root:"); + }).pipe(Effect.ensuring(cleanupTempDir(outerRoot))); + }); + it.live("emits a structured success payload in json mode", () => { const tempDir = makeTempDir(); const child = mockChildProcessSpawner({ exitCode: 1 }); diff --git a/apps/cli/src/next/config/project-gitignore.ts b/apps/cli/src/next/config/project-gitignore.ts index e82e824822..38a6045857 100644 --- a/apps/cli/src/next/config/project-gitignore.ts +++ b/apps/cli/src/next/config/project-gitignore.ts @@ -1,40 +1,19 @@ import { Effect, FileSystem, Path } from "effect"; +import { findGitRootPath } from "../../shared/git/git-root.ts"; const GITIGNORE_ENTRY = ".supabase/"; const normalizeGitignoreEntry = (entry: string): string => entry.replaceAll("\\", "/"); -const findGitRoot = ( - start: string, -): Effect.Effect => - Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem; - const path = yield* Path.Path; - - let current = path.resolve(start); - const root = path.parse(current).root; - - while (true) { - const gitPath = path.join(current, ".git"); - if (yield* fs.exists(gitPath).pipe(Effect.orDie)) { - return current; - } - if (current === root) { - return null; - } - current = path.dirname(current); - } - }); - export const ensureProjectStateIgnored = ( projectRoot: string, ): Effect.Effect => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; - const gitRoot = yield* findGitRoot(projectRoot); + const gitRoot = yield* Effect.tryPromise(() => findGitRootPath(projectRoot)).pipe(Effect.orDie); - if (gitRoot === null) { + if (gitRoot === undefined) { return; } diff --git a/apps/cli/src/shared/functions/deploy.ts b/apps/cli/src/shared/functions/deploy.ts index 0e2fb6b28c..46bab06023 100644 --- a/apps/cli/src/shared/functions/deploy.ts +++ b/apps/cli/src/shared/functions/deploy.ts @@ -15,6 +15,7 @@ import * as HttpClientError from "effect/unstable/http/HttpClientError"; import { Output } from "../output/output.service.ts"; import { spawnContainerCli } from "../../legacy/shared/legacy-container-cli.ts"; import { legacyGetRegistryImageUrl } from "../../legacy/shared/legacy-docker-registry.ts"; +import { findGitRootPath } from "../git/git-root.ts"; import { invalidFunctionSlugDetail, validateFunctionSlugMessage } from "./functions.shared.ts"; import { ConflictingFunctionDeployFlagsError, @@ -325,6 +326,10 @@ async function realpathIfExists(pathname: string) { } } +async function resolveFunctionsSourceRoot(projectRoot: string) { + return (await findGitRootPath(projectRoot)) ?? resolve(projectRoot); +} + function humanSize(bytes: number) { if (bytes < 1000) { return `${bytes} B`; @@ -649,7 +654,7 @@ async function walkImportPaths( try { const resolvedCurrent = await realpath(resolve(current)); if (!isContainedInAnyPath(allowedRoots, resolvedCurrent)) { - await onWarning(`WARN: Skipping import path outside project root: ${current}\n`); + await onWarning(`WARN: Skipping import path outside source root: ${current}\n`); continue; } contents = await readFile(resolvedCurrent); @@ -700,7 +705,7 @@ async function walkImportPaths( const resolvedModule = resolve(modulePath); const containmentPath = await realpathIfExists(resolvedModule); if (!isContainedInAnyPath(allowedRoots, containmentPath)) { - await onWarning(`WARN: Skipping import path outside project root: ${modulePath}\n`); + await onWarning(`WARN: Skipping import path outside source root: ${modulePath}\n`); continue; } queue.push(toSlash(resolvedModule)); @@ -887,16 +892,15 @@ async function resolveImportMapAllowedRoots(projectRoot: string, importMapPath: } async function writeSourceDeployForm( - cwd: string, - projectRoot: string, + sourceRoot: string, config: ResolvedDeployFunctionConfig, metadata: SourceDeployMetadata, outputRaw: (text: string) => Effect.Effect, ) { const form = new FormData(); form.append("metadata", JSON.stringify(metadata)); - const realProjectRoot = await realpath(projectRoot); - const importMapAllowedRoots = await resolveImportMapAllowedRoots(projectRoot, config.importMap); + const realSourceRoot = await realpath(sourceRoot); + const importMapAllowedRoots = await resolveImportMapAllowedRoots(sourceRoot, config.importMap); const uploadedAssets = new Set(); const appendAsset = async (pathname: string, contents: Uint8Array, realPathname: string) => { @@ -904,15 +908,15 @@ async function writeSourceDeployForm( return; } uploadedAssets.add(realPathname); - const relativePath = toApiRelativePath(cwd, pathname); + const relativePath = toApiRelativePath(sourceRoot, pathname); await Effect.runPromise(outputRaw(`Uploading asset (${config.slug}): ${relativePath}\n`)); form.append("file", new File([contents], relativePath)); }; const uploadAsset = async (pathname: string, contents: Uint8Array) => { const realPathname = await realpath(pathname); - if (!isContainedPath(realProjectRoot, realPathname)) { - throw new Error(`refusing to upload asset outside project root: ${pathname}`); + if (!isContainedPath(realSourceRoot, realPathname)) { + throw new Error(`refusing to upload asset outside source root: ${pathname}`); } await appendAsset(pathname, contents, realPathname); }; @@ -929,7 +933,7 @@ async function writeSourceDeployForm( const realPathname = await realpath(pathname); if (!isContainedInAnyPath(importMapAllowedRoots, realPathname)) { await Effect.runPromise( - outputRaw(`WARN: Skipping import path outside project root: ${pathname}\n`), + outputRaw(`WARN: Skipping import path outside source root: ${pathname}\n`), ); return; } @@ -940,7 +944,7 @@ async function writeSourceDeployForm( const resolvedPath = await realpath(pathname); if (!isContainedInAnyPath(importMapAllowedRoots, resolvedPath)) { await Effect.runPromise( - outputRaw(`WARN: Skipping import path outside project root: ${pathname}\n`), + outputRaw(`WARN: Skipping import path outside source root: ${pathname}\n`), ); return; } @@ -951,7 +955,7 @@ async function writeSourceDeployForm( importMap, pathname, importMapAllowedRoots, - projectRoot, + sourceRoot, uploadImportMapTargetAsset, async (message) => { await Effect.runPromise(outputRaw(message)); @@ -967,7 +971,7 @@ async function writeSourceDeployForm( const resolvedNestedPath = await realpath(nestedPath); if (!isContainedInAnyPath(importMapAllowedRoots, resolvedNestedPath)) { await Effect.runPromise( - outputRaw(`WARN: Skipping import path outside project root: ${nestedPath}\n`), + outputRaw(`WARN: Skipping import path outside source root: ${nestedPath}\n`), ); continue; } @@ -1004,8 +1008,8 @@ async function writeSourceDeployForm( await walkImportPaths( importMap, config.entrypoint, - [realProjectRoot], - projectRoot, + [realSourceRoot], + sourceRoot, uploadAsset, async (message) => { await Effect.runPromise(outputRaw(message)); @@ -1017,7 +1021,7 @@ async function writeSourceDeployForm( } function createSourceMetadata( - cwd: string, + sourceRoot: string, config: ResolvedDeployFunctionConfig, remote?: RemoteFunction, ): SourceDeployMetadata { @@ -1025,9 +1029,10 @@ function createSourceMetadata( return { name: config.slug, ...(verifyJwt === undefined ? {} : { verify_jwt: verifyJwt }), - entrypoint_path: toApiRelativePath(cwd, config.entrypoint), - import_map_path: config.importMap.length > 0 ? toApiRelativePath(cwd, config.importMap) : "", - static_patterns: config.staticFiles.map((pathname) => toApiRelativePath(cwd, pathname)), + entrypoint_path: toApiRelativePath(sourceRoot, config.entrypoint), + import_map_path: + config.importMap.length > 0 ? toApiRelativePath(sourceRoot, config.importMap) : "", + static_patterns: config.staticFiles.map((pathname) => toApiRelativePath(sourceRoot, pathname)), }; } @@ -1098,9 +1103,10 @@ export async function buildDockerBinds( const hostFunctionsDir = resolve(functionsDir); const hostOutputDir = resolve(outputDir); const projectRoot = resolve(functionsDir, "..", ".."); - const realProjectRoot = await realpath(projectRoot); + const sourceRoot = await resolveFunctionsSourceRoot(projectRoot); + const realSourceRoot = await realpath(sourceRoot); const moduleRoots = [ - realProjectRoot, + realSourceRoot, ...( await Promise.all( (options.additionalModuleRoots ?? []).map(async (root) => { @@ -1113,7 +1119,7 @@ export async function buildDockerBinds( ) ).flatMap((root) => (root === undefined ? [] : [root])), ]; - const importMapAllowedRoots = await resolveImportMapAllowedRoots(projectRoot, config.importMap); + const importMapAllowedRoots = await resolveImportMapAllowedRoots(sourceRoot, config.importMap); const binds = [`${hostFunctionsDir}:${toDockerPath(hostFunctionsDir)}:ro`]; if (process.env["BITBUCKET_CLONE_DIR"] === undefined) { binds.unshift(`${localDockerId("edge_runtime", projectId)}:/root/.cache/deno:rw`); @@ -1132,7 +1138,7 @@ export async function buildDockerBinds( extraBinds.push(`${hostPath}:${toDockerPath(hostPath)}:ro`); }; const appendProjectBind = async (pathname: string, _contents: Uint8Array) => - appendBindWithinRoots([realProjectRoot], pathname); + appendBindWithinRoots([realSourceRoot], pathname); const appendModuleBind = async (pathname: string, _contents: Uint8Array) => appendBindWithinRoots(moduleRoots, pathname); const appendImportMapBind = async (pathname: string, _contents: Uint8Array) => @@ -1145,7 +1151,7 @@ export async function buildDockerBinds( importMap, config.entrypoint, moduleRoots, - projectRoot, + sourceRoot, appendModuleBind, options.onWarning ?? (async () => {}), ); @@ -1159,7 +1165,7 @@ export async function buildDockerBinds( importMap, target, importMapAllowedRoots, - projectRoot, + sourceRoot, appendImportMapBind, async () => {}, ); @@ -1539,8 +1545,7 @@ const rateLimitedRequest = Effect.fnUntraced(function* ( const uploadFunctionSource = Effect.fnUntraced(function* ( api: ApiClient, projectRef: string, - cwd: string, - projectRoot: string, + sourceRoot: string, config: ResolvedDeployFunctionConfig, metadata: SourceDeployMetadata, bundleOnly: boolean, @@ -1548,7 +1553,7 @@ const uploadFunctionSource = Effect.fnUntraced(function* ( const output = yield* Output; const files = yield* Effect.tryPromise({ try: async () => { - const form = await writeSourceDeployForm(cwd, projectRoot, config, metadata, (text) => + const form = await writeSourceDeployForm(sourceRoot, config, metadata, (text) => output.raw(text, "stderr"), ); return form.getAll("file").flatMap((part) => (part instanceof Blob ? [part] : [])); @@ -1929,13 +1934,16 @@ export const resolveFunctionConfigs = Effect.fnUntraced(function* (input: { const deployViaApi = Effect.fnUntraced(function* ( projectRef: string, - cwd: string, projectRoot: string, configs: ReadonlyArray, api: ApiClient, jobs: number, ) { const output = yield* Output; + const sourceRoot = yield* Effect.tryPromise({ + try: () => resolveFunctionsSourceRoot(projectRoot), + catch: (error) => (error instanceof Error ? error : new Error(String(error))), + }); const enabled = configs.filter((config) => config.enabled); for (const skipped of configs.filter((config) => !config.enabled)) { yield* output.raw(`Skipping disabled Function: ${skipped.slug}\n`, "stderr"); @@ -1956,10 +1964,9 @@ const deployViaApi = Effect.fnUntraced(function* ( yield* uploadFunctionSource( api, projectRef, - cwd, - projectRoot, + sourceRoot, config, - createSourceMetadata(cwd, config, remoteBySlug.get(config.slug)), + createSourceMetadata(sourceRoot, config, remoteBySlug.get(config.slug)), false, ); return; @@ -1974,10 +1981,9 @@ const deployViaApi = Effect.fnUntraced(function* ( yield* uploadFunctionSource( api, projectRef, - cwd, - projectRoot, + sourceRoot, config, - createSourceMetadata(cwd, config, remoteBySlug.get(config.slug)), + createSourceMetadata(sourceRoot, config, remoteBySlug.get(config.slug)), true, ), ); @@ -2191,7 +2197,6 @@ export function deployFunctions( const deployWithApi = deployViaApi( projectRef, - dependencies.cwd, dependencies.projectRoot, configs, dependencies.api, diff --git a/apps/cli/src/shared/git/git-root.ts b/apps/cli/src/shared/git/git-root.ts new file mode 100644 index 0000000000..062fb14c4d --- /dev/null +++ b/apps/cli/src/shared/git/git-root.ts @@ -0,0 +1,21 @@ +import { stat } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; + +export async function findGitRootPath(startPath: string) { + let current = resolve(startPath); + + for (;;) { + try { + await stat(resolve(current, ".git")); + return current; + } catch { + // Keep walking until we hit the filesystem root. + } + + const parent = dirname(current); + if (parent === current) { + return undefined; + } + current = parent; + } +} From 1fe9b81d30691230758d85c4fbe75c3eb18db4d0 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Thu, 2 Jul 2026 11:36:49 +0200 Subject: [PATCH 19/48] ci: auto approve API sync PRs (#5756) Follows up on #5753 by letting API sync PRs satisfy the approval requirement automatically. This adds a dedicated approval step after the generated API sync PR is created. The step uses the shared account PAT stored in `AUTO_APPROVE_PR_PAT`, while the existing GitHub App token continues to own PR creation and auto-merge setup. --- .github/workflows/cli-go-api-sync.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/cli-go-api-sync.yml b/.github/workflows/cli-go-api-sync.yml index f65a73c6e2..4c7d3a7fbe 100644 --- a/.github/workflows/cli-go-api-sync.yml +++ b/.github/workflows/cli-go-api-sync.yml @@ -61,6 +61,13 @@ jobs: branch: sync/api-types base: develop + - name: Approve Pull Request + if: steps.check.outputs.has_changes == 'true' + run: gh pr review --approve --repo "${{ github.repository }}" "${STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER}" + env: + GH_TOKEN: ${{ secrets.AUTO_APPROVE_PR_PAT }} + STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER: ${{ steps.cpr.outputs.pull-request-number }} + - name: Enable Pull Request Automerge if: steps.check.outputs.has_changes == 'true' run: gh pr merge --auto --squash --repo "${{ github.repository }}" "${STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER}" From 957566df52543b455db7a1735c4b0b08232f2143 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 2 Jul 2026 12:18:50 +0100 Subject: [PATCH 20/48] fix(cli): CSV-split sso and postgres-config StringSlice flags (#5764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Current Behavior Go registers `sso add/update --domains/--add-domains/--remove-domains` and `postgres-config update/delete --config` as pflag `StringSliceVar`, which CSV-splits each occurrence (via `encoding/csv`) and accumulates across repeats. The legacy TS port only supported flag repetition (`Flag.atLeast(0)`), so a single comma-separated value like `--domains a.com,b.com` produced one malformed string (`"a.com,b.com"`) instead of two domains — sent to the API as-is, or (for `postgres-config`) failing the `key=value` split validation. Fixes #CLI-1853 ## Expected Behavior `--domains a.com,b.com` (and `--config foo=bar,baz=qux`) now CSV-split per-occurrence and accumulate across repeats, matching Go's `pflag.StringSliceVar`/`encoding/csv` semantics exactly — including quoted-comma handling (`'"a,b",c'` → `["a,b", "c"]`) and Go's `csv.Reader` malformed-input error messages. The CSV-parsing algorithm already existed for `--schema` flags (`gen types`, `db lint`, `db dump`, `db pull`, `db diff`, `db schema {generate,sync}`) in `legacy-schema-flags.ts`. This extracts the generic algorithm into a new shared `legacy-string-slice-flag.ts` module (`legacyParseStringSliceFlag`) so the four newly-fixed flags reuse it instead of duplicating it; `legacy-schema-flags.ts` now delegates to the shared module while keeping its schema-specific CSV re-encoder (`legacySchemaToCsvField`) untouched. Out of scope: the ticket's "bonus" item (aligning `sso update`'s domains mutex error message with cobra's group-error template) is deferred — it depends on the separate functions-mutex-template fix (CLI-1860), which hasn't landed yet. CLOSES CLI-1853 --- .../postgres-config/delete/delete.command.ts | 15 ++- .../delete/delete.command.unit.test.ts | 46 +++++++ .../postgres-config/update/update.command.ts | 15 ++- .../update/update.command.unit.test.ts | 46 +++++++ .../legacy/commands/sso/add/add.command.ts | 21 ++- .../commands/sso/add/add.command.unit.test.ts | 59 +++++++++ .../commands/sso/update/update.command.ts | 55 +++++--- .../sso/update/update.command.unit.test.ts | 63 +++++++++ .../src/legacy/shared/legacy-schema-flags.ts | 122 ++---------------- .../legacy/shared/legacy-string-slice-flag.ts | 116 +++++++++++++++++ .../legacy-string-slice-flag.unit.test.ts | 77 +++++++++++ 11 files changed, 492 insertions(+), 143 deletions(-) create mode 100644 apps/cli/src/legacy/commands/postgres-config/delete/delete.command.unit.test.ts create mode 100644 apps/cli/src/legacy/commands/postgres-config/update/update.command.unit.test.ts create mode 100644 apps/cli/src/legacy/commands/sso/add/add.command.unit.test.ts create mode 100644 apps/cli/src/legacy/commands/sso/update/update.command.unit.test.ts create mode 100644 apps/cli/src/legacy/shared/legacy-string-slice-flag.ts create mode 100644 apps/cli/src/legacy/shared/legacy-string-slice-flag.unit.test.ts 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 b757be3ccb..903558e9e9 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 @@ -3,18 +3,25 @@ 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 { legacyParseStringSliceFlag } from "../../../shared/legacy-string-slice-flag.ts"; import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyPostgresConfigDelete } from "./delete.handler.ts"; +export const legacyPostgresConfigDeleteConfigFlag = Flag.string("config").pipe( + Flag.withDescription("Config keys to delete (comma-separated)"), + Flag.atLeast(0), + Flag.mapTryCatch( + (rawValues) => legacyParseStringSliceFlag(rawValues), + (err) => (err instanceof Error ? err.message : String(err)), + ), +); + const config = { projectRef: Flag.string("project-ref").pipe( Flag.withDescription("Project ref of the Supabase project."), Flag.optional, ), - config: Flag.string("config").pipe( - Flag.withDescription("Config keys to delete (comma-separated)"), - Flag.atLeast(0), - ), + config: legacyPostgresConfigDeleteConfigFlag, noRestart: Flag.boolean("no-restart").pipe( Flag.withDescription("Do not restart the database after deleting config."), ), diff --git a/apps/cli/src/legacy/commands/postgres-config/delete/delete.command.unit.test.ts b/apps/cli/src/legacy/commands/postgres-config/delete/delete.command.unit.test.ts new file mode 100644 index 0000000000..79b7d0df2a --- /dev/null +++ b/apps/cli/src/legacy/commands/postgres-config/delete/delete.command.unit.test.ts @@ -0,0 +1,46 @@ +import { BunServices } from "@effect/platform-bun"; +import { Effect, Exit } from "effect"; +import { describe, expect, test } from "vitest"; +import { legacyPostgresConfigDeleteConfigFlag } from "./delete.command.ts"; + +describe("legacy postgres-config delete --config flag (pflag StringSlice parity)", () => { + test("splits a comma-separated value into multiple keys", async () => { + const [, values] = await Effect.runPromise( + legacyPostgresConfigDeleteConfigFlag + .parse({ + flags: { config: ["max_connections,statement_timeout"] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)), + ); + + expect(values).toEqual(["max_connections", "statement_timeout"]); + }); + + test("accumulates repeated occurrences, each CSV-split", async () => { + const [, values] = await Effect.runPromise( + legacyPostgresConfigDeleteConfigFlag + .parse({ + flags: { config: ["max_connections,statement_timeout", "custom_key"] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)), + ); + + expect(values).toEqual(["max_connections", "statement_timeout", "custom_key"]); + }); + + test("rejects malformed CSV (bare quote)", async () => { + const exit = await Effect.runPromise( + legacyPostgresConfigDeleteConfigFlag + .parse({ + flags: { config: ['max"connections'] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)) + .pipe(Effect.exit), + ); + + expect(Exit.isFailure(exit)).toBe(true); + }); +}); 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 09b6be8303..1ab52d9021 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 @@ -3,18 +3,25 @@ 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 { legacyParseStringSliceFlag } from "../../../shared/legacy-string-slice-flag.ts"; import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyPostgresConfigUpdate } from "./update.handler.ts"; +export const legacyPostgresConfigUpdateConfigFlag = Flag.string("config").pipe( + Flag.withDescription("Config overrides specified as a 'key=value' pair"), + Flag.atLeast(0), + Flag.mapTryCatch( + (rawValues) => legacyParseStringSliceFlag(rawValues), + (err) => (err instanceof Error ? err.message : String(err)), + ), +); + const config = { projectRef: Flag.string("project-ref").pipe( Flag.withDescription("Project ref of the Supabase project."), Flag.optional, ), - config: Flag.string("config").pipe( - Flag.withDescription("Config overrides specified as a 'key=value' pair"), - Flag.atLeast(0), - ), + config: legacyPostgresConfigUpdateConfigFlag, replaceExistingOverrides: Flag.boolean("replace-existing-overrides").pipe( Flag.withDescription( "If true, replaces all existing overrides with the ones provided. If false (default), merges existing overrides with the ones provided.", diff --git a/apps/cli/src/legacy/commands/postgres-config/update/update.command.unit.test.ts b/apps/cli/src/legacy/commands/postgres-config/update/update.command.unit.test.ts new file mode 100644 index 0000000000..75a028729e --- /dev/null +++ b/apps/cli/src/legacy/commands/postgres-config/update/update.command.unit.test.ts @@ -0,0 +1,46 @@ +import { BunServices } from "@effect/platform-bun"; +import { Effect, Exit } from "effect"; +import { describe, expect, test } from "vitest"; +import { legacyPostgresConfigUpdateConfigFlag } from "./update.command.ts"; + +describe("legacy postgres-config update --config flag (pflag StringSlice parity)", () => { + test("splits a comma-separated value into multiple key=value pairs", async () => { + const [, values] = await Effect.runPromise( + legacyPostgresConfigUpdateConfigFlag + .parse({ + flags: { config: ["max_connections=100,statement_timeout=600"] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)), + ); + + expect(values).toEqual(["max_connections=100", "statement_timeout=600"]); + }); + + test("accumulates repeated occurrences, each CSV-split", async () => { + const [, values] = await Effect.runPromise( + legacyPostgresConfigUpdateConfigFlag + .parse({ + flags: { config: ["max_connections=100,statement_timeout=600", "custom_key=alpha"] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)), + ); + + expect(values).toEqual(["max_connections=100", "statement_timeout=600", "custom_key=alpha"]); + }); + + test("rejects malformed CSV (unterminated quote)", async () => { + const exit = await Effect.runPromise( + legacyPostgresConfigUpdateConfigFlag + .parse({ + flags: { config: ['"max_connections=100'] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)) + .pipe(Effect.exit), + ); + + expect(Exit.isFailure(exit)).toBe(true); + }); +}); diff --git a/apps/cli/src/legacy/commands/sso/add/add.command.ts b/apps/cli/src/legacy/commands/sso/add/add.command.ts index 190a6ed155..4030c121a3 100644 --- a/apps/cli/src/legacy/commands/sso/add/add.command.ts +++ b/apps/cli/src/legacy/commands/sso/add/add.command.ts @@ -3,6 +3,7 @@ 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 { legacyParseStringSliceFlag } from "../../../shared/legacy-string-slice-flag.ts"; import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacySsoAdd } from "./add.handler.ts"; @@ -13,6 +14,18 @@ const NAME_ID_FORMATS = [ "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", ] as const; +export const legacySsoAddDomainsFlag = Flag.string("domains").pipe( + Flag.atLeast(0), + Flag.withDescription( + "Comma separated list of email domains to associate with the added identity provider.", + ), + Flag.mapTryCatch( + (rawValues) => legacyParseStringSliceFlag(rawValues), + (err) => (err instanceof Error ? err.message : String(err)), + ), + Flag.withDefault([] as ReadonlyArray), +); + const config = { projectRef: Flag.string("project-ref").pipe( Flag.withDescription("Project ref of the Supabase project."), @@ -24,13 +37,7 @@ const config = { Flag.withAlias("t"), Flag.withDescription("Type of identity provider (according to supported protocol)."), ), - domains: Flag.string("domains").pipe( - Flag.atLeast(0), - Flag.withDescription( - "Comma separated list of email domains to associate with the added identity provider.", - ), - Flag.withDefault([] as ReadonlyArray), - ), + domains: legacySsoAddDomainsFlag, metadataFile: Flag.string("metadata-file").pipe( Flag.withDescription( "File containing a SAML 2.0 Metadata XML document describing the identity provider.", diff --git a/apps/cli/src/legacy/commands/sso/add/add.command.unit.test.ts b/apps/cli/src/legacy/commands/sso/add/add.command.unit.test.ts new file mode 100644 index 0000000000..9b96a497e7 --- /dev/null +++ b/apps/cli/src/legacy/commands/sso/add/add.command.unit.test.ts @@ -0,0 +1,59 @@ +import { BunServices } from "@effect/platform-bun"; +import { Effect, Exit } from "effect"; +import { describe, expect, test } from "vitest"; +import { legacySsoAddDomainsFlag } from "./add.command.ts"; + +describe("legacy sso add --domains flag (pflag StringSlice parity)", () => { + test("splits a comma-separated value into multiple domains", async () => { + const [, domains] = await Effect.runPromise( + legacySsoAddDomainsFlag + .parse({ + flags: { domains: ["example.com,example.org"] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)), + ); + + expect(domains).toEqual(["example.com", "example.org"]); + }); + + test("accumulates repeated occurrences, each CSV-split", async () => { + const [, domains] = await Effect.runPromise( + legacySsoAddDomainsFlag + .parse({ + flags: { domains: ["example.com,example.org", "example.net"] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)), + ); + + expect(domains).toEqual(["example.com", "example.org", "example.net"]); + }); + + test("defaults to an empty array when unset", async () => { + const [, domains] = await Effect.runPromise( + legacySsoAddDomainsFlag + .parse({ + flags: {}, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)), + ); + + expect(domains).toEqual([]); + }); + + test("rejects malformed CSV (unterminated quote)", async () => { + const exit = await Effect.runPromise( + legacySsoAddDomainsFlag + .parse({ + flags: { domains: ['"example.com'] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)) + .pipe(Effect.exit), + ); + + expect(Exit.isFailure(exit)).toBe(true); + }); +}); diff --git a/apps/cli/src/legacy/commands/sso/update/update.command.ts b/apps/cli/src/legacy/commands/sso/update/update.command.ts index 05d838f2f3..cf6732ddca 100644 --- a/apps/cli/src/legacy/commands/sso/update/update.command.ts +++ b/apps/cli/src/legacy/commands/sso/update/update.command.ts @@ -3,6 +3,7 @@ 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 { legacyParseStringSliceFlag } from "../../../shared/legacy-string-slice-flag.ts"; import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacySsoUpdate } from "./update.handler.ts"; @@ -13,30 +14,46 @@ const NAME_ID_FORMATS = [ "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", ] as const; +export const legacySsoUpdateDomainsFlag = Flag.string("domains").pipe( + Flag.atLeast(0), + Flag.withDescription("Replace domains with this comma separated list of email domains."), + Flag.mapTryCatch( + (rawValues) => legacyParseStringSliceFlag(rawValues), + (err) => (err instanceof Error ? err.message : String(err)), + ), + Flag.withDefault([] as ReadonlyArray), +); + +export const legacySsoUpdateAddDomainsFlag = Flag.string("add-domains").pipe( + Flag.atLeast(0), + Flag.withDescription("Add this comma separated list of email domains to the identity provider."), + Flag.mapTryCatch( + (rawValues) => legacyParseStringSliceFlag(rawValues), + (err) => (err instanceof Error ? err.message : String(err)), + ), + Flag.withDefault([] as ReadonlyArray), +); + +export const legacySsoUpdateRemoveDomainsFlag = Flag.string("remove-domains").pipe( + Flag.atLeast(0), + Flag.withDescription( + "Remove this comma separated list of email domains from the identity provider.", + ), + Flag.mapTryCatch( + (rawValues) => legacyParseStringSliceFlag(rawValues), + (err) => (err instanceof Error ? err.message : String(err)), + ), + Flag.withDefault([] as ReadonlyArray), +); + const config = { projectRef: Flag.string("project-ref").pipe( Flag.withDescription("Project ref of the Supabase project."), Flag.optional, ), - domains: Flag.string("domains").pipe( - Flag.atLeast(0), - Flag.withDescription("Replace domains with this comma separated list of email domains."), - Flag.withDefault([] as ReadonlyArray), - ), - addDomains: Flag.string("add-domains").pipe( - Flag.atLeast(0), - Flag.withDescription( - "Add this comma separated list of email domains to the identity provider.", - ), - Flag.withDefault([] as ReadonlyArray), - ), - removeDomains: Flag.string("remove-domains").pipe( - Flag.atLeast(0), - Flag.withDescription( - "Remove this comma separated list of email domains from the identity provider.", - ), - Flag.withDefault([] as ReadonlyArray), - ), + domains: legacySsoUpdateDomainsFlag, + addDomains: legacySsoUpdateAddDomainsFlag, + removeDomains: legacySsoUpdateRemoveDomainsFlag, metadataFile: Flag.string("metadata-file").pipe( Flag.withDescription( "File containing a SAML 2.0 Metadata XML document describing the identity provider.", diff --git a/apps/cli/src/legacy/commands/sso/update/update.command.unit.test.ts b/apps/cli/src/legacy/commands/sso/update/update.command.unit.test.ts new file mode 100644 index 0000000000..0afeb0e4d2 --- /dev/null +++ b/apps/cli/src/legacy/commands/sso/update/update.command.unit.test.ts @@ -0,0 +1,63 @@ +import { BunServices } from "@effect/platform-bun"; +import { Effect, Exit } from "effect"; +import { describe, expect, test } from "vitest"; +import { + legacySsoUpdateAddDomainsFlag, + legacySsoUpdateDomainsFlag, + legacySsoUpdateRemoveDomainsFlag, +} from "./update.command.ts"; + +describe("legacy sso update domain flags (pflag StringSlice parity)", () => { + test("--domains splits a comma-separated value into multiple domains", async () => { + const [, domains] = await Effect.runPromise( + legacySsoUpdateDomainsFlag + .parse({ + flags: { domains: ["example.com,example.org"] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)), + ); + + expect(domains).toEqual(["example.com", "example.org"]); + }); + + test("--add-domains splits a comma-separated value into multiple domains", async () => { + const [, addDomains] = await Effect.runPromise( + legacySsoUpdateAddDomainsFlag + .parse({ + flags: { "add-domains": ["example.com,example.org"] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)), + ); + + expect(addDomains).toEqual(["example.com", "example.org"]); + }); + + test("--remove-domains splits a comma-separated value into multiple domains", async () => { + const [, removeDomains] = await Effect.runPromise( + legacySsoUpdateRemoveDomainsFlag + .parse({ + flags: { "remove-domains": ["example.com,example.org"] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)), + ); + + expect(removeDomains).toEqual(["example.com", "example.org"]); + }); + + test("rejects malformed CSV (bare quote)", async () => { + const exit = await Effect.runPromise( + legacySsoUpdateDomainsFlag + .parse({ + flags: { domains: ['example"com'] }, + arguments: [], + }) + .pipe(Effect.provide(BunServices.layer)) + .pipe(Effect.exit), + ); + + expect(Exit.isFailure(exit)).toBe(true); + }); +}); diff --git a/apps/cli/src/legacy/shared/legacy-schema-flags.ts b/apps/cli/src/legacy/shared/legacy-schema-flags.ts index 6c6a56b758..26983442c0 100644 --- a/apps/cli/src/legacy/shared/legacy-schema-flags.ts +++ b/apps/cli/src/legacy/shared/legacy-schema-flags.ts @@ -3,119 +3,23 @@ * * Go defines `--schema` as a Cobra `StringSliceVarP` on both `gen types` * (`apps/cli-go/cmd/gen.go:155`) and `db lint` (`apps/cli-go/cmd/db.go:506`). - * pflag's `StringSlice.Set` parses each value via `encoding/csv` (`readAsCSV` - * → `csv.NewReader`), so a quoted value like `"tenant,one"` is ONE element - * (`tenant,one`) while `public,private` is two elements. Plain `split(",")` wrongly - * breaks quoted commas. + * The CSV-per-occurrence parsing itself lives in `legacy-string-slice-flag.ts` + * (shared with every other Go `StringSliceVar` flag ported to the legacy + * shell); this module re-exports it under the `--schema`-specific names and + * adds `legacySchemaToCsvField`, the CSV re-encoder used when forwarding + * `--schema` back to a delegated Go subprocess. * - * Whitespace is NOT trimmed and empty fields are NOT dropped: Go's csv.Reader - * returns raw field values; pflag appends them directly to the slice. - * - * Shared by `gen types` and `db lint` (two command families). + * Shared by `gen types`, `db lint`, `db dump`, `db pull`, `db diff`, and + * `db schema {generate,sync}`. */ +import { + legacyParseStringSliceFlag, + LegacyStringSliceFlagParseError, +} from "./legacy-string-slice-flag.ts"; -/** Thrown by `legacyParseSchemaFlags` when a `--schema` value is not valid CSV. */ -export class LegacySchemaFlagParseError extends Error { - readonly value: string; - readonly detail: string; - constructor(value: string, detail: string) { - super(`parse error on line 1, column 0: ${detail}`); - this.name = "LegacySchemaFlagParseError"; - this.value = value; - this.detail = detail; - } -} +export { LegacyStringSliceFlagParseError as LegacySchemaFlagParseError }; -/** - * Parses one CSV record from `val`, matching Go's `encoding/csv` defaults used by - * pflag's `StringSlice.Set` (`readAsCSV` → `csv.NewReader`). - * - * Rules: comma delimiter, double-quote quoting, `""` escapes a literal quote. - * Whitespace is preserved (Go does not trim). An empty string returns `[]`. - * - * **Throws `LegacySchemaFlagParseError`** on any of the three malformed-CSV conditions - * that Go's `csv.Reader` rejects: - * - Quoted field with no closing quote (`"tenant`) → "extraneous or missing \" in quoted-field" - * - Extra non-comma bytes after a closing quote (`"a"b`) → "extraneous or missing \" in quoted-field" - * - A bare `"` inside an unquoted field (`a"b`) → "bare \" in non-quoted-field" - */ -function readAsCSVStrict(val: string): string[] { - if (val === "") return []; - const fields: string[] = []; - let i = 0; - while (i < val.length) { - if (val[i] === '"') { - // Quoted field: accumulate until the closing (unescaped) quote. - i++; // skip opening quote - let field = ""; - let closed = false; - while (i < val.length) { - if (val[i] === '"') { - if (i + 1 < val.length && val[i + 1] === '"') { - field += '"'; - i += 2; // "" → single " - } else { - i++; // skip closing quote - closed = true; - break; - } - } else { - field += val[i++]; - } - } - if (!closed) { - // Ran off the end without finding a closing quote. - throw new LegacySchemaFlagParseError(val, `extraneous or missing " in quoted-field`); - } - // After the closing quote only a comma or end-of-string is allowed. - if (i < val.length && val[i] !== ",") { - throw new LegacySchemaFlagParseError(val, `extraneous or missing " in quoted-field`); - } - fields.push(field); - } else { - // Unquoted field: a bare `"` anywhere inside is illegal. - const start = i; - while (i < val.length && val[i] !== ",") { - if (val[i] === '"') { - throw new LegacySchemaFlagParseError(val, `bare " in non-quoted-field`); - } - i++; - } - fields.push(val.slice(start, i)); - } - // Consume the delimiter; a trailing comma produces one more empty field. - if (i < val.length && val[i] === ",") { - i++; - if (i === val.length) { - fields.push(""); // trailing comma → empty trailing field - } - } - } - return fields; -} - -/** - * CSV-parses and flattens all raw `--schema` occurrences. - * - * **Throws `LegacySchemaFlagParseError`** on the first malformed value, matching - * Go's pflag parse-time behaviour where a bad `--schema` value fails the command - * before it runs (Go: `invalid argument "..." for "-s, --schema" flag: parse error ...`). - * - * Valid behaviour: - * - `"tenant,one"` → `["tenant,one"]` (quoted comma stays one field) - * - `public,private` → `["public", "private"]` - * - no trimming, `""` escapes a literal quote inside a quoted field - * - empty string → no field - */ -export function legacyParseSchemaFlags(rawValues: ReadonlyArray): ReadonlyArray { - const schemas: string[] = []; - for (const value of rawValues) { - for (const field of readAsCSVStrict(value)) { - schemas.push(field); - } - } - return schemas; -} +export const legacyParseSchemaFlags = legacyParseStringSliceFlag; /** * Whether a CSV field must be quoted. Mirrors Go's `encoding/csv` diff --git a/apps/cli/src/legacy/shared/legacy-string-slice-flag.ts b/apps/cli/src/legacy/shared/legacy-string-slice-flag.ts new file mode 100644 index 0000000000..dafb3df022 --- /dev/null +++ b/apps/cli/src/legacy/shared/legacy-string-slice-flag.ts @@ -0,0 +1,116 @@ +/** + * Parses a pflag `StringSliceVar` flag: CSV-splits each occurrence via + * `encoding/csv` and accumulates across repeats, matching `readAsCSV` in + * `github.com/spf13/pflag/string_slice.go`'s `stringSliceValue.Set`. A naive + * `.split(",")` diverges on quoted/embedded commas (`'"a,b",c'`). Effect V4 + * CLI has no CSV/list primitive, so every Go `StringSliceVar` flag ported to + * the legacy shell needs this (e.g. `--domains`, `--config`). + * + * Whitespace is NOT trimmed and empty fields are NOT dropped: Go's csv.Reader + * returns raw field values; pflag appends them directly to the slice. + */ + +/** Thrown by `legacyParseStringSliceFlag` when a value is not valid CSV. */ +export class LegacyStringSliceFlagParseError extends Error { + readonly value: string; + readonly detail: string; + constructor(value: string, detail: string) { + super(`parse error on line 1, column 0: ${detail}`); + this.name = "LegacyStringSliceFlagParseError"; + this.value = value; + this.detail = detail; + } +} + +/** + * Parses one CSV record from `val`, matching Go's `encoding/csv` defaults used by + * pflag's `StringSlice.Set` (`readAsCSV` → `csv.NewReader`). + * + * Rules: comma delimiter, double-quote quoting, `""` escapes a literal quote. + * Whitespace is preserved (Go does not trim). An empty string returns `[]`. + * + * **Throws `LegacyStringSliceFlagParseError`** on any of the three malformed-CSV + * conditions that Go's `csv.Reader` rejects: + * - Quoted field with no closing quote (`"tenant`) → "extraneous or missing \" in quoted-field" + * - Extra non-comma bytes after a closing quote (`"a"b`) → "extraneous or missing \" in quoted-field" + * - A bare `"` inside an unquoted field (`a"b`) → "bare \" in non-quoted-field" + */ +function readAsCSVStrict(val: string): string[] { + if (val === "") return []; + const fields: string[] = []; + let i = 0; + while (i < val.length) { + if (val[i] === '"') { + // Quoted field: accumulate until the closing (unescaped) quote. + i++; // skip opening quote + let field = ""; + let closed = false; + while (i < val.length) { + if (val[i] === '"') { + if (i + 1 < val.length && val[i + 1] === '"') { + field += '"'; + i += 2; // "" → single " + } else { + i++; // skip closing quote + closed = true; + break; + } + } else { + field += val[i++]; + } + } + if (!closed) { + // Ran off the end without finding a closing quote. + throw new LegacyStringSliceFlagParseError(val, `extraneous or missing " in quoted-field`); + } + // After the closing quote only a comma or end-of-string is allowed. + if (i < val.length && val[i] !== ",") { + throw new LegacyStringSliceFlagParseError(val, `extraneous or missing " in quoted-field`); + } + fields.push(field); + } else { + // Unquoted field: a bare `"` anywhere inside is illegal. + const start = i; + while (i < val.length && val[i] !== ",") { + if (val[i] === '"') { + throw new LegacyStringSliceFlagParseError(val, `bare " in non-quoted-field`); + } + i++; + } + fields.push(val.slice(start, i)); + } + // Consume the delimiter; a trailing comma produces one more empty field. + if (i < val.length && val[i] === ",") { + i++; + if (i === val.length) { + fields.push(""); // trailing comma → empty trailing field + } + } + } + return fields; +} + +/** + * CSV-parses and flattens all raw occurrences of a repeated pflag `StringSlice` flag. + * + * **Throws `LegacyStringSliceFlagParseError`** on the first malformed value, matching + * Go's pflag parse-time behaviour where a bad value fails the command before it + * runs (Go: `invalid argument "..." for "--" flag: parse error ...`). + * + * Valid behaviour: + * - `"tenant,one"` → `["tenant,one"]` (quoted comma stays one field) + * - `public,private` → `["public", "private"]` + * - no trimming, `""` escapes a literal quote inside a quoted field + * - empty string → no field + */ +export function legacyParseStringSliceFlag( + rawValues: ReadonlyArray, +): ReadonlyArray { + const values: string[] = []; + for (const value of rawValues) { + for (const field of readAsCSVStrict(value)) { + values.push(field); + } + } + return values; +} diff --git a/apps/cli/src/legacy/shared/legacy-string-slice-flag.unit.test.ts b/apps/cli/src/legacy/shared/legacy-string-slice-flag.unit.test.ts new file mode 100644 index 0000000000..af6fb4f41c --- /dev/null +++ b/apps/cli/src/legacy/shared/legacy-string-slice-flag.unit.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, it } from "vitest"; +import { + legacyParseStringSliceFlag, + LegacyStringSliceFlagParseError, +} from "./legacy-string-slice-flag.ts"; + +describe("legacyParseStringSliceFlag (pflag StringSlice CSV parity)", () => { + it("splits unquoted comma-separated values", () => { + expect(legacyParseStringSliceFlag(["public,private"])).toEqual(["public", "private"]); + }); + + it("keeps a quoted value with embedded comma as a single element", () => { + // pflag TestSSWithComma: `"tenant,one"` → one element "tenant,one" + expect(legacyParseStringSliceFlag(['"tenant,one"'])).toEqual(["tenant,one"]); + }); + + it("single value with no comma", () => { + expect(legacyParseStringSliceFlag(["public"])).toEqual(["public"]); + }); + + it("accumulates repeated flags", () => { + expect(legacyParseStringSliceFlag(["public", "private"])).toEqual(["public", "private"]); + }); + + it("accumulates repeated flags mixed with csv", () => { + expect(legacyParseStringSliceFlag(["public,private", "staging"])).toEqual([ + "public", + "private", + "staging", + ]); + }); + + it("unescapes doubled double-quote inside quoted field", () => { + // Go csv: `"a""b"` → field is `a"b` + expect(legacyParseStringSliceFlag(['"a""b"'])).toEqual(['a"b']); + }); + + it("empty input returns empty array", () => { + expect(legacyParseStringSliceFlag([])).toEqual([]); + }); + + it("preserves whitespace (Go does not trim)", () => { + // Go csv passes raw field values; pflag does not trim + expect(legacyParseStringSliceFlag([" public , private "])).toEqual([" public ", " private "]); + }); + + // --- malformed inputs: must THROW --- + + it("throws on an unterminated quoted field", () => { + // `"tenant` — opening quote but no closing quote + expect(() => legacyParseStringSliceFlag(['"tenant'])).toThrow(LegacyStringSliceFlagParseError); + expect(() => legacyParseStringSliceFlag(['"tenant'])).toThrow( + /extraneous or missing " in quoted-field/, + ); + }); + + it("throws on extra bytes after a closing quote", () => { + // `"a"b` — closing quote followed by a non-comma character + expect(() => legacyParseStringSliceFlag(['"a"b'])).toThrow(LegacyStringSliceFlagParseError); + expect(() => legacyParseStringSliceFlag(['"a"b'])).toThrow( + /extraneous or missing " in quoted-field/, + ); + }); + + it("throws on a bare quote inside an unquoted field", () => { + // `a"b` — bare " in a field that did not start with a quote + expect(() => legacyParseStringSliceFlag(['a"b'])).toThrow(LegacyStringSliceFlagParseError); + expect(() => legacyParseStringSliceFlag(['a"b'])).toThrow(/bare " in non-quoted-field/); + }); + + it("throws on the first malformed value in a multi-value list", () => { + // The valid "public" comes before the malformed one; the error is still thrown + expect(() => legacyParseStringSliceFlag(["public", '"broken'])).toThrow( + LegacyStringSliceFlagParseError, + ); + }); +}); From 35874122271a0a343ca32ae7115332d27ce04aba Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 2 Jul 2026 12:38:38 +0100 Subject: [PATCH 21/48] fix(cli): port db pull initial pull to native pg_dump; fix non-interactive confirm hangs (#5725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What changed `db pull`'s initial-migra path (no local migrations, migra engine) is now native — it no longer delegates to the bundled Go binary. It seeds the migration file with a native `pg_dump` of the remote schema, then appends the migra diff, mirroring Go's `dumpRemoteSchema` + `diffRemoteSchema(nil)` + `swallowInitialInSync` / `ensureMigrationWritten`. The pg_dump machinery is hoisted out of `db/dump/` into `db/shared/legacy-pg-dump.{scripts,env,run}.ts` and shared by `db dump` and `db pull`; `db dump` is refactored onto the new shared `legacyStreamPgDump` runner (no behavior change). ## Non-interactive confirm-prompt hang fix A new `db pull` live test surfaced a real, pre-existing bug: in **text mode on a non-TTY stdin** (CI/piped), a confirm prompt **blocks forever** — clack's `confirm` never resolves without a keypress, so the intended `Effect.orElseSucceed` default never fires. Go's `console.PromptYesNo` returns the prompt's default on a non-terminal without blocking. The handlers now gate on the `Tty` service and take Go's default before prompting, matching the canonical legacy pattern (`secrets/unset`, `projects/delete`). Fixed in the three commands that shared this latent hang: | Command | Prompt | Non-TTY default | | --- | --- | --- | | `db pull` | "Update remote migration history table?" | `true` (proceeds) | | `config push` | per-service "Keep it enabled?" / "push `` config?" | `true` (proceeds) | | `logout` | "Do you want to log out?" | `false` (cancels) | Each gets non-TTY integration coverage; the `db pull` no-hang behavior is also proven end-to-end by the live test. ## Why `db dump` is already a native pg_dump port, so the dependency `db pull` was waiting on (its `"needs pg_dump — delegate to Go"` comment) is gone. This removes the last Go delegation from the common `db pull` path. ## Still delegated `db pull --experimental` (structured dump) continues to delegate to Go. Its blocker is not pg_dump but `format.WriteStructuredSchemas`, which parses each dumped statement with a PostgreSQL DDL AST parser (`multigres`, ~50 node types) to route objects into structured files — there is no TS equivalent yet, so it is tracked separately. (The statement splitter `parser.Split` and the structured-file writer are already ported; only the AST router is missing.) ## Reviewer notes - Parity-sensitive points in `db pull`: stderr ordering (`Dumping schema…` before `Creating shadow…`), append-vs-overwrite on the diff pass, `swallowInitialInSync` (an empty diff after a non-empty dump is success), and the IPv4 transaction-pooler fallback on the dump pass (warning printed once, not re-printed on the retry — matching Go's `RunWithPoolerFallback`). - Adds `*.live.test.ts` for `db dump` and `db pull` that exercise the real data plane via the `supabase/cli-e2e-ci` harness (Linux only; gated by `describeLiveProject`). These are the first data-plane live tests in the repo, and the `db pull` test is what caught the non-TTY hang. --- apps/cli/docs/go-cli-porting-status.md | 300 ++++++------- .../bootstrap/bootstrap.integration.test.ts | 2 + ...ootstrap.workdir-cache.integration.test.ts | 2 + .../commands/config/push/push.command.ts | 4 +- .../commands/config/push/push.handler.ts | 39 +- .../config/push/push.integration.test.ts | 121 +++++- .../legacy/commands/db/dump/dump.handler.ts | 148 +++---- .../legacy/commands/db/dump/dump.live.test.ts | 48 +++ .../legacy/commands/db/pull/SIDE_EFFECTS.md | 38 +- .../legacy/commands/db/pull/pull.errors.ts | 12 + .../legacy/commands/db/pull/pull.handler.ts | 294 ++++++++++--- .../commands/db/pull/pull.integration.test.ts | 393 ++++++++++++++++-- .../legacy/commands/db/pull/pull.layers.ts | 2 + .../legacy/commands/db/pull/pull.live.test.ts | 69 +++ .../legacy-pg-dump.env.ts} | 4 +- .../legacy-pg-dump.env.unit.test.ts} | 4 +- .../commands/db/shared/legacy-pg-dump.run.ts | 54 +++ .../legacy-pg-dump.scripts.ts} | 4 +- .../db/shared/legacy-pooler-fallback.ts | 118 ++++++ .../legacy/commands/logout/logout.handler.ts | 31 +- .../logout/logout.integration.test.ts | 60 ++- .../legacy/commands/logout/logout.layers.ts | 8 +- .../commands/seed/buckets/buckets.command.ts | 7 +- .../seed/buckets/buckets.integration.test.ts | 7 +- .../legacy/commands/storage/rm/rm.command.ts | 5 +- .../storage/rm/rm.integration.test.ts | 46 ++ .../shared/legacy-db-config.toml-read.ts | 78 +++- .../legacy-db-config.toml-read.unit.test.ts | 88 ++++ .../src/legacy/shared/legacy-prompt-yes-no.ts | 58 ++- .../shared/legacy-prompt-yes-no.unit.test.ts | 24 ++ apps/cli/src/shared/legacy/global-flags.ts | 37 +- .../shared/runtime/stdin.integration.test.ts | 57 +++ apps/cli/src/shared/runtime/stdin.layer.ts | 62 ++- apps/cli/src/shared/runtime/stdin.service.ts | 16 +- apps/cli/tests/helpers/legacy-mocks.ts | 4 + apps/cli/tests/helpers/legacy-storage.ts | 18 +- apps/cli/tests/helpers/mocks.ts | 25 +- 37 files changed, 1849 insertions(+), 438 deletions(-) create mode 100644 apps/cli/src/legacy/commands/db/dump/dump.live.test.ts create mode 100644 apps/cli/src/legacy/commands/db/pull/pull.live.test.ts rename apps/cli/src/legacy/commands/db/{dump/dump.env.ts => shared/legacy-pg-dump.env.ts} (98%) rename apps/cli/src/legacy/commands/db/{dump/dump.env.unit.test.ts => shared/legacy-pg-dump.env.unit.test.ts} (98%) create mode 100644 apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.run.ts rename apps/cli/src/legacy/commands/db/{dump/dump.scripts.ts => shared/legacy-pg-dump.scripts.ts} (96%) create mode 100644 apps/cli/src/legacy/commands/db/shared/legacy-pooler-fallback.ts create mode 100644 apps/cli/src/legacy/shared/legacy-prompt-yes-no.unit.test.ts create mode 100644 apps/cli/src/shared/runtime/stdin.integration.test.ts diff --git a/apps/cli/docs/go-cli-porting-status.md b/apps/cli/docs/go-cli-porting-status.md index f5969bd199..2780e1c396 100644 --- a/apps/cli/docs/go-cli-porting-status.md +++ b/apps/cli/docs/go-cli-porting-status.md @@ -80,51 +80,51 @@ These commands exist in the TS CLI today but have no direct top-level equivalent ## Database -| Old command | TS status | TS command path or `missing` | Missing flags/params | Extra TS flags/params | Notes | -| --------------------------------- | --------- | -------------------------------------------------- | -------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `db diff` | `ported` | `legacy/commands/db/diff/` | `n/a` | `n/a` | Native TS port. Native pg-delta / migra diff via edge-runtime against a Go-seam-provisioned live shadow (`db __shadow`); `--use-pgadmin` / `--use-pg-schema` delegate to the Go binary. | -| `db dump` | `missing` | `missing` | `n/a` | `n/a` | No native TS implementation yet. Wrapped in legacy shell. | -| `db lint` | `ported` | `legacy/commands/db/lint/` | `n/a` | `n/a` | Native TS port. Runs `plpgsql_check` in a rolled-back transaction via LegacyDbConnection; emits Go-parity pretty JSON. | -| `db pull` | `ported` | `legacy/commands/db/pull/` | `n/a` | `n/a` | Native TS port. Native pg-delta / migra migration + `--declarative` pg-delta export; reconciles `schema_migrations`. `--experimental` dump + initial-pull `pg_dump` (migra) delegate to the Go binary. | -| `db push` | `missing` | `missing` | `n/a` | `n/a` | No native TS implementation yet. Wrapped in legacy shell. | -| `db reset` | `missing` | `missing` | `n/a` | `n/a` | No native TS implementation yet. Wrapped in legacy shell. | -| `db start` | `missing` | `missing` | `n/a` | `n/a` | No native TS implementation yet. Wrapped in legacy shell. | -| `inspect report` | `ported` | `legacy/commands/inspect/report/` | `n/a` | `n/a` | Native TS port. Runs every inspect query via server-side `COPY ... CSV`, writes 14 CSVs under `//`, then renders a Go-parity Glamour rules summary (bounded csvq-subset evaluator; custom `[experimental.inspect.rules]` supported). | -| `inspect db db-stats` | `ported` | `legacy/commands/inspect/db/db-stats/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db replication-slots` | `ported` | `legacy/commands/inspect/db/replication-slots/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db locks` | `ported` | `legacy/commands/inspect/db/locks/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db blocking` | `ported` | `legacy/commands/inspect/db/blocking/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db outliers` | `ported` | `legacy/commands/inspect/db/outliers/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db calls` | `ported` | `legacy/commands/inspect/db/calls/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db index-stats` | `ported` | `legacy/commands/inspect/db/index-stats/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db long-running-queries` | `ported` | `legacy/commands/inspect/db/long-running-queries/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db bloat` | `ported` | `legacy/commands/inspect/db/bloat/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db role-stats` | `ported` | `legacy/commands/inspect/db/role-stats/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db vacuum-stats` | `ported` | `legacy/commands/inspect/db/vacuum-stats/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db table-stats` | `ported` | `legacy/commands/inspect/db/table-stats/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db traffic-profile` | `ported` | `legacy/commands/inspect/db/traffic-profile/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | -| `inspect db cache-hit` | `ported` | `legacy/commands/inspect/db/cache-hit/` | `n/a` | `n/a` | Native TS port. Deprecated (use db-stats); routes to the active query. | -| `inspect db index-usage` | `ported` | `legacy/commands/inspect/db/index-usage/` | `n/a` | `n/a` | Native TS port. Deprecated (use index-stats); routes to the active query. | -| `inspect db total-index-size` | `ported` | `legacy/commands/inspect/db/total-index-size/` | `n/a` | `n/a` | Native TS port. Deprecated (use index-stats); routes to the active query. | -| `inspect db index-sizes` | `ported` | `legacy/commands/inspect/db/index-sizes/` | `n/a` | `n/a` | Native TS port. Deprecated (use index-stats); routes to the active query. | -| `inspect db table-sizes` | `ported` | `legacy/commands/inspect/db/table-sizes/` | `n/a` | `n/a` | Native TS port. Deprecated (use table-stats); routes to the active query. | -| `inspect db table-index-sizes` | `ported` | `legacy/commands/inspect/db/table-index-sizes/` | `n/a` | `n/a` | Native TS port. Deprecated (use table-stats); routes to the active query. | -| `inspect db total-table-sizes` | `ported` | `legacy/commands/inspect/db/total-table-sizes/` | `n/a` | `n/a` | Native TS port. Deprecated (use table-stats); routes to the active query. | -| `inspect db unused-indexes` | `ported` | `legacy/commands/inspect/db/unused-indexes/` | `n/a` | `n/a` | Native TS port. Deprecated (use index-stats); routes to the active query. | -| `inspect db table-record-counts` | `ported` | `legacy/commands/inspect/db/table-record-counts/` | `n/a` | `n/a` | Native TS port. Deprecated (use table-stats); routes to the active query. | -| `inspect db seq-scans` | `ported` | `legacy/commands/inspect/db/seq-scans/` | `n/a` | `n/a` | Native TS port. Deprecated (use index-stats); routes to the active query. | -| `inspect db role-configs` | `ported` | `legacy/commands/inspect/db/role-configs/` | `n/a` | `n/a` | Native TS port. Deprecated (use role-stats); routes to the active query. | -| `inspect db role-connections` | `ported` | `legacy/commands/inspect/db/role-connections/` | `n/a` | `n/a` | Native TS port. Deprecated (use role-stats); routes to the active query. | -| `migration down` | `ported` | `legacy/commands/migration/down/` | `n/a` | `n/a` | Native TS port. Revert prompt → drop user schemas → vault upsert → migrate&seed to the target version; defaults to `--local`. Skips Go's pgcache catalog write. | -| `migration fetch` | `ported` | `legacy/commands/migration/fetch/` | `n/a` | `n/a` | Native TS port. Reads `schema_migrations` and writes `supabase/migrations/_.sql`; overwrite prompt for a non-empty dir. | -| `migration list` | `ported` | `legacy/commands/migration/list/` | `n/a` | `n/a` | Native TS port. Merges remote `schema_migrations` with local files into a Glamour ASCII table (Local / Remote / Time-UTC columns); defaults to `--linked`. | -| `migration new` | `ported` | `legacy/commands/migration/new/` | `n/a` | `n/a` | Native TS port. Writes `supabase/migrations/_.sql` (mode 0644) from piped stdin; no DB/API. | -| `migration repair` | `ported` | `legacy/commands/migration/repair/` | `n/a` | `n/a` | Native TS port. Transactional create-table + TRUNCATE/UPSERT/DELETE; applied mode reads local files; repair-all prompt; defaults to `--linked`. | -| `migration squash` | `missing` | `missing` | `n/a` | `n/a` | Deliberate Go-proxy delegate (parity-preserving). A native port would emit pg-delta diff format instead of Go's `pg_dump` bytes (an accepted divergence, CLI-1597) and needs a bare-baseline shadow the seam does not yet expose; kept on the proxy for byte parity until CLI-1597's squash rewrite lands. | -| `migration up` | `ported` | `legacy/commands/migration/up/` | `n/a` | `n/a` | Native TS port. Computes pending migrations, upserts `[db.vault]`, applies each transactionally; `--include-all` for out-of-order; defaults to `--local`. Does not seed (matches Go). | -| `seed buckets` | `ported` | `legacy/commands/seed/buckets/` | `n/a` | `n/a` | Native TS port. Local-only (Go's `seed` defines no `--project-ref`, so the ref is always empty): seeds `[storage.buckets]` + `[storage.vector]` against the local Storage service gateway; remote/analytics paths are unreachable and omitted. `--linked`/`--local` accepted for surface parity (both seed local). Vector graceful-skip WARNINGs ported. | -| `test db` | `ported` | `legacy/commands/test/db/` | `n/a` | `n/a` | Native TS port. `--db-url`/`--local`/`--linked` + variadic paths; runs `supabase/pg_prove:3.36` via `docker run`; pgTAP enable/disable via `@effect/sql-pg`. `--network-id` override and `[images]` config override not modeled (documented divergences). | -| `test new` | `ported` | `legacy/commands/test/new/` | `n/a` | `n/a` | Native TS port. Writes `supabase/tests/_test.sql` from the embedded pgtap template; `--template` (pgtap). | +| Old command | TS status | TS command path or `missing` | Missing flags/params | Extra TS flags/params | Notes | +| --------------------------------- | --------- | -------------------------------------------------- | -------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `db diff` | `ported` | `legacy/commands/db/diff/` | `n/a` | `n/a` | Native TS port. Native pg-delta / migra diff via edge-runtime against a Go-seam-provisioned live shadow (`db __shadow`); `--use-pgadmin` / `--use-pg-schema` delegate to the Go binary. | +| `db dump` | `ported` | `legacy/commands/db/dump/` | `n/a` | `n/a` | Native TS port. Streams `pg_dump`/`pg_dumpall` via a Docker container (`LegacyDockerRun`); schema/data/role modes, `--dry-run` script print, IPv4 transaction-pooler fallback. | +| `db lint` | `ported` | `legacy/commands/db/lint/` | `n/a` | `n/a` | Native TS port. Runs `plpgsql_check` in a rolled-back transaction via LegacyDbConnection; emits Go-parity pretty JSON. | +| `db pull` | `ported` | `legacy/commands/db/pull/` | `n/a` | `n/a` | Native TS port. Native pg-delta / migra migration + `--declarative` pg-delta export; reconciles `schema_migrations`. The initial-migra pull dumps the remote schema natively (`pg_dump`) then appends the migra diff. Only `--experimental` (structured dump) still delegates to Go, pending a TS PostgreSQL DDL parser for `format.WriteStructuredSchemas`. | +| `db push` | `missing` | `missing` | `n/a` | `n/a` | No native TS implementation yet. Wrapped in legacy shell. | +| `db reset` | `missing` | `missing` | `n/a` | `n/a` | No native TS implementation yet. Wrapped in legacy shell. | +| `db start` | `missing` | `missing` | `n/a` | `n/a` | No native TS implementation yet. Wrapped in legacy shell. | +| `inspect report` | `ported` | `legacy/commands/inspect/report/` | `n/a` | `n/a` | Native TS port. Runs every inspect query via server-side `COPY ... CSV`, writes 14 CSVs under `//`, then renders a Go-parity Glamour rules summary (bounded csvq-subset evaluator; custom `[experimental.inspect.rules]` supported). | +| `inspect db db-stats` | `ported` | `legacy/commands/inspect/db/db-stats/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db replication-slots` | `ported` | `legacy/commands/inspect/db/replication-slots/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db locks` | `ported` | `legacy/commands/inspect/db/locks/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db blocking` | `ported` | `legacy/commands/inspect/db/blocking/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db outliers` | `ported` | `legacy/commands/inspect/db/outliers/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db calls` | `ported` | `legacy/commands/inspect/db/calls/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db index-stats` | `ported` | `legacy/commands/inspect/db/index-stats/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db long-running-queries` | `ported` | `legacy/commands/inspect/db/long-running-queries/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db bloat` | `ported` | `legacy/commands/inspect/db/bloat/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db role-stats` | `ported` | `legacy/commands/inspect/db/role-stats/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db vacuum-stats` | `ported` | `legacy/commands/inspect/db/vacuum-stats/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db table-stats` | `ported` | `legacy/commands/inspect/db/table-stats/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db traffic-profile` | `ported` | `legacy/commands/inspect/db/traffic-profile/` | `n/a` | `n/a` | Native TS port. Queries Postgres directly via LegacyDbConnection; renders Go-parity Glamour tables. | +| `inspect db cache-hit` | `ported` | `legacy/commands/inspect/db/cache-hit/` | `n/a` | `n/a` | Native TS port. Deprecated (use db-stats); routes to the active query. | +| `inspect db index-usage` | `ported` | `legacy/commands/inspect/db/index-usage/` | `n/a` | `n/a` | Native TS port. Deprecated (use index-stats); routes to the active query. | +| `inspect db total-index-size` | `ported` | `legacy/commands/inspect/db/total-index-size/` | `n/a` | `n/a` | Native TS port. Deprecated (use index-stats); routes to the active query. | +| `inspect db index-sizes` | `ported` | `legacy/commands/inspect/db/index-sizes/` | `n/a` | `n/a` | Native TS port. Deprecated (use index-stats); routes to the active query. | +| `inspect db table-sizes` | `ported` | `legacy/commands/inspect/db/table-sizes/` | `n/a` | `n/a` | Native TS port. Deprecated (use table-stats); routes to the active query. | +| `inspect db table-index-sizes` | `ported` | `legacy/commands/inspect/db/table-index-sizes/` | `n/a` | `n/a` | Native TS port. Deprecated (use table-stats); routes to the active query. | +| `inspect db total-table-sizes` | `ported` | `legacy/commands/inspect/db/total-table-sizes/` | `n/a` | `n/a` | Native TS port. Deprecated (use table-stats); routes to the active query. | +| `inspect db unused-indexes` | `ported` | `legacy/commands/inspect/db/unused-indexes/` | `n/a` | `n/a` | Native TS port. Deprecated (use index-stats); routes to the active query. | +| `inspect db table-record-counts` | `ported` | `legacy/commands/inspect/db/table-record-counts/` | `n/a` | `n/a` | Native TS port. Deprecated (use table-stats); routes to the active query. | +| `inspect db seq-scans` | `ported` | `legacy/commands/inspect/db/seq-scans/` | `n/a` | `n/a` | Native TS port. Deprecated (use index-stats); routes to the active query. | +| `inspect db role-configs` | `ported` | `legacy/commands/inspect/db/role-configs/` | `n/a` | `n/a` | Native TS port. Deprecated (use role-stats); routes to the active query. | +| `inspect db role-connections` | `ported` | `legacy/commands/inspect/db/role-connections/` | `n/a` | `n/a` | Native TS port. Deprecated (use role-stats); routes to the active query. | +| `migration down` | `ported` | `legacy/commands/migration/down/` | `n/a` | `n/a` | Native TS port. Revert prompt → drop user schemas → vault upsert → migrate&seed to the target version; defaults to `--local`. Skips Go's pgcache catalog write. | +| `migration fetch` | `ported` | `legacy/commands/migration/fetch/` | `n/a` | `n/a` | Native TS port. Reads `schema_migrations` and writes `supabase/migrations/_.sql`; overwrite prompt for a non-empty dir. | +| `migration list` | `ported` | `legacy/commands/migration/list/` | `n/a` | `n/a` | Native TS port. Merges remote `schema_migrations` with local files into a Glamour ASCII table (Local / Remote / Time-UTC columns); defaults to `--linked`. | +| `migration new` | `ported` | `legacy/commands/migration/new/` | `n/a` | `n/a` | Native TS port. Writes `supabase/migrations/_.sql` (mode 0644) from piped stdin; no DB/API. | +| `migration repair` | `ported` | `legacy/commands/migration/repair/` | `n/a` | `n/a` | Native TS port. Transactional create-table + TRUNCATE/UPSERT/DELETE; applied mode reads local files; repair-all prompt; defaults to `--linked`. | +| `migration squash` | `missing` | `missing` | `n/a` | `n/a` | No native TS implementation yet. Wrapped in legacy shell. | +| `migration up` | `ported` | `legacy/commands/migration/up/` | `n/a` | `n/a` | Native TS port. Computes pending migrations, upserts `[db.vault]`, applies each transactionally; `--include-all` for out-of-order; defaults to `--local`. Does not seed (matches Go). | +| `seed buckets` | `ported` | `legacy/commands/seed/buckets/` | `n/a` | `n/a` | Native TS port. Local-only (Go's `seed` defines no `--project-ref`, so the ref is always empty): seeds `[storage.buckets]` + `[storage.vector]` against the local Storage service gateway; remote/analytics paths are unreachable and omitted. `--linked`/`--local` accepted for surface parity (both seed local). Vector graceful-skip WARNINGs ported. | +| `test db` | `ported` | `legacy/commands/test/db/` | `n/a` | `n/a` | Native TS port. `--db-url`/`--local`/`--linked` + variadic paths; runs `supabase/pg_prove:3.36` via `docker run`; pgTAP enable/disable via `@effect/sql-pg`. `--network-id` override and `[images]` config override not modeled (documented divergences). | +| `test new` | `ported` | `legacy/commands/test/new/` | `n/a` | `n/a` | Native TS port. Writes `supabase/tests/_test.sql` from the embedded pgtap template; `--template` (pgtap). | ## Code Generation @@ -211,111 +211,111 @@ Legend: - `wrapped`: Phase 0 proxy wrapper exists in the legacy shell - `missing`: no legacy shell command yet -| Command | Legacy status | Legacy command path | -| -------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `orgs list` | `ported` | [`../src/legacy/commands/orgs/list/list.command.ts`](../src/legacy/commands/orgs/list/list.command.ts) | -| `orgs create` | `ported` | [`../src/legacy/commands/orgs/create/create.command.ts`](../src/legacy/commands/orgs/create/create.command.ts) | -| `projects list` | `ported` | [`../src/legacy/commands/projects/list/list.command.ts`](../src/legacy/commands/projects/list/list.command.ts) | -| `projects create` | `ported` | [`../src/legacy/commands/projects/create/create.command.ts`](../src/legacy/commands/projects/create/create.command.ts) | -| `projects delete` | `ported` | [`../src/legacy/commands/projects/delete/delete.command.ts`](../src/legacy/commands/projects/delete/delete.command.ts) | -| `projects api-keys` | `ported` | [`../src/legacy/commands/projects/api-keys/api-keys.command.ts`](../src/legacy/commands/projects/api-keys/api-keys.command.ts) | -| `branches list` | `ported` | [`../src/legacy/commands/branches/list/list.command.ts`](../src/legacy/commands/branches/list/list.command.ts) | -| `branches create` | `ported` | [`../src/legacy/commands/branches/create/create.command.ts`](../src/legacy/commands/branches/create/create.command.ts) | -| `branches get` | `ported` | [`../src/legacy/commands/branches/get/get.command.ts`](../src/legacy/commands/branches/get/get.command.ts) | -| `branches update` | `ported` | [`../src/legacy/commands/branches/update/update.command.ts`](../src/legacy/commands/branches/update/update.command.ts) | -| `branches pause` | `ported` | [`../src/legacy/commands/branches/pause/pause.command.ts`](../src/legacy/commands/branches/pause/pause.command.ts) | -| `branches unpause` | `ported` | [`../src/legacy/commands/branches/unpause/unpause.command.ts`](../src/legacy/commands/branches/unpause/unpause.command.ts) | -| `branches delete` | `ported` | [`../src/legacy/commands/branches/delete/delete.command.ts`](../src/legacy/commands/branches/delete/delete.command.ts) | -| `branches disable` | `ported` | [`../src/legacy/commands/branches/disable/disable.command.ts`](../src/legacy/commands/branches/disable/disable.command.ts) | -| `secrets list` | `ported` | [`../src/legacy/commands/secrets/list/list.command.ts`](../src/legacy/commands/secrets/list/list.command.ts) | -| `secrets set` | `ported` | [`../src/legacy/commands/secrets/set/set.command.ts`](../src/legacy/commands/secrets/set/set.command.ts) | -| `secrets unset` | `ported` | [`../src/legacy/commands/secrets/unset/unset.command.ts`](../src/legacy/commands/secrets/unset/unset.command.ts) | -| `config push` | `ported` | [`../src/legacy/commands/config/push/push.command.ts`](../src/legacy/commands/config/push/push.command.ts) | -| `backups list` | `ported` | [`../src/legacy/commands/backups/list/list.command.ts`](../src/legacy/commands/backups/list/list.command.ts) | -| `backups restore` | `ported` | [`../src/legacy/commands/backups/restore/restore.command.ts`](../src/legacy/commands/backups/restore/restore.command.ts) | -| `snippets list` | `ported` | [`../src/legacy/commands/snippets/list/list.command.ts`](../src/legacy/commands/snippets/list/list.command.ts) | -| `snippets download` | `ported` | [`../src/legacy/commands/snippets/download/download.command.ts`](../src/legacy/commands/snippets/download/download.command.ts) | -| `sso list` | `ported` | [`../src/legacy/commands/sso/list/list.command.ts`](../src/legacy/commands/sso/list/list.command.ts) | -| `sso add` | `ported` | [`../src/legacy/commands/sso/add/add.command.ts`](../src/legacy/commands/sso/add/add.command.ts) | -| `sso remove` | `ported` | [`../src/legacy/commands/sso/remove/remove.command.ts`](../src/legacy/commands/sso/remove/remove.command.ts) | -| `sso update` | `ported` | [`../src/legacy/commands/sso/update/update.command.ts`](../src/legacy/commands/sso/update/update.command.ts) | -| `sso show` | `ported` | [`../src/legacy/commands/sso/show/show.command.ts`](../src/legacy/commands/sso/show/show.command.ts) | -| `sso info` | `ported` | [`../src/legacy/commands/sso/info/info.command.ts`](../src/legacy/commands/sso/info/info.command.ts) | -| `domains create` | `ported` | [`../src/legacy/commands/domains/create/create.command.ts`](../src/legacy/commands/domains/create/create.command.ts) | -| `domains get` | `ported` | [`../src/legacy/commands/domains/get/get.command.ts`](../src/legacy/commands/domains/get/get.command.ts) | -| `domains reverify` | `ported` | [`../src/legacy/commands/domains/reverify/reverify.command.ts`](../src/legacy/commands/domains/reverify/reverify.command.ts) | -| `domains activate` | `ported` | [`../src/legacy/commands/domains/activate/activate.command.ts`](../src/legacy/commands/domains/activate/activate.command.ts) | -| `domains delete` | `ported` | [`../src/legacy/commands/domains/delete/delete.command.ts`](../src/legacy/commands/domains/delete/delete.command.ts) | -| `vanity-subdomains get` | `ported` | [`../src/legacy/commands/vanity-subdomains/get/get.command.ts`](../src/legacy/commands/vanity-subdomains/get/get.command.ts) | -| `vanity-subdomains check-availability` | `ported` | [`../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts`](../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts) | -| `vanity-subdomains activate` | `ported` | [`../src/legacy/commands/vanity-subdomains/activate/activate.command.ts`](../src/legacy/commands/vanity-subdomains/activate/activate.command.ts) | -| `vanity-subdomains delete` | `ported` | [`../src/legacy/commands/vanity-subdomains/delete/delete.command.ts`](../src/legacy/commands/vanity-subdomains/delete/delete.command.ts) | -| `network-bans get` | `ported` | [`../src/legacy/commands/network-bans/get/get.command.ts`](../src/legacy/commands/network-bans/get/get.command.ts) | -| `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` | `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) | -| `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` | `ported` | [`../src/legacy/commands/login/login.command.ts`](../src/legacy/commands/login/login.command.ts) | -| `logout` | `ported` | [`../src/legacy/commands/logout/logout.command.ts`](../src/legacy/commands/logout/logout.command.ts) | -| `link` | `ported` | [`../src/legacy/commands/link/link.command.ts`](../src/legacy/commands/link/link.command.ts) | -| `unlink` | `ported` | [`../src/legacy/commands/unlink/unlink.command.ts`](../src/legacy/commands/unlink/unlink.command.ts) | -| `bootstrap` | `ported` | [`../src/legacy/commands/bootstrap/bootstrap.command.ts`](../src/legacy/commands/bootstrap/bootstrap.command.ts) (native; `db push` step delegated to the Go binary — interim) | -| `init` | `ported` | [`../src/legacy/commands/init/init.command.ts`](../src/legacy/commands/init/init.command.ts) | -| `services` | `ported` | [`../src/legacy/commands/services/services.command.ts`](../src/legacy/commands/services/services.command.ts) | -| `start` | `wrapped` | [`../src/legacy/commands/start/start.command.ts`](../src/legacy/commands/start/start.command.ts) | -| `stop` | `wrapped` | [`../src/legacy/commands/stop/stop.command.ts`](../src/legacy/commands/stop/stop.command.ts) | -| `status` | `wrapped` | [`../src/legacy/commands/status/status.command.ts`](../src/legacy/commands/status/status.command.ts) | -| `telemetry enable` | `ported` | [`../src/legacy/commands/telemetry/enable/enable.command.ts`](../src/legacy/commands/telemetry/enable/enable.command.ts) | -| `telemetry disable` | `ported` | [`../src/legacy/commands/telemetry/disable/disable.command.ts`](../src/legacy/commands/telemetry/disable/disable.command.ts) | -| `telemetry status` | `ported` | [`../src/legacy/commands/telemetry/status/status.command.ts`](../src/legacy/commands/telemetry/status/status.command.ts) | -| `migration list` | `ported` | [`../src/legacy/commands/migration/list/list.command.ts`](../src/legacy/commands/migration/list/list.command.ts) — native; merged Local/Remote/Time-UTC Glamour table | -| `migration new` | `ported` | [`../src/legacy/commands/migration/new/new.command.ts`](../src/legacy/commands/migration/new/new.command.ts) — native; writes `supabase/migrations/_.sql` from piped stdin | -| `migration repair` | `ported` | [`../src/legacy/commands/migration/repair/repair.command.ts`](../src/legacy/commands/migration/repair/repair.command.ts) — native; transactional TRUNCATE/UPSERT/DELETE, repair-all prompt | -| `migration squash` | `wrapped` | [`../src/legacy/commands/migration/squash/squash.command.ts`](../src/legacy/commands/migration/squash/squash.command.ts) — deliberate Go delegate for byte parity (native pg-delta squash deferred to CLI-1597) | -| `migration up` | `ported` | [`../src/legacy/commands/migration/up/up.command.ts`](../src/legacy/commands/migration/up/up.command.ts) — native; pending compute + vault upsert + per-file apply | -| `migration down` | `ported` | [`../src/legacy/commands/migration/down/down.command.ts`](../src/legacy/commands/migration/down/down.command.ts) — native; drop + vault + migrate&seed to target version | -| `migration fetch` | `ported` | [`../src/legacy/commands/migration/fetch/fetch.command.ts`](../src/legacy/commands/migration/fetch/fetch.command.ts) — native; writes history rows to `supabase/migrations/` | -| `gen types` | `ported` | [`../src/legacy/commands/gen/types/types.command.ts`](../src/legacy/commands/gen/types/types.command.ts) | -| `gen signing-key` | `ported` | [`../src/legacy/commands/gen/signing-key/signing-key.command.ts`](../src/legacy/commands/gen/signing-key/signing-key.command.ts) | -| `gen bearer-jwt` | `wrapped` | [`../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts`](../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts) | -| `gen keys` | `wrapped` | [`../src/legacy/commands/gen/keys/keys.command.ts`](../src/legacy/commands/gen/keys/keys.command.ts) | -| `functions list` | `wrapped` | [`../src/legacy/commands/functions/list/list.command.ts`](../src/legacy/commands/functions/list/list.command.ts) | -| `functions delete` | `ported` | [`../src/legacy/commands/functions/delete/delete.command.ts`](../src/legacy/commands/functions/delete/delete.command.ts) | -| `functions download` | `ported` | [`../src/legacy/commands/functions/download/download.command.ts`](../src/legacy/commands/functions/download/download.command.ts) | -| `functions deploy` | `ported` | [`../src/legacy/commands/functions/deploy/deploy.command.ts`](../src/legacy/commands/functions/deploy/deploy.command.ts) | -| `functions new` | `ported` | [`../src/legacy/commands/functions/new/new.command.ts`](../src/legacy/commands/functions/new/new.command.ts) | -| `functions serve` | `ported` | [`../src/legacy/commands/functions/serve/serve.command.ts`](../src/legacy/commands/functions/serve/serve.command.ts) | -| `storage ls` | `ported` | [`../src/legacy/commands/storage/ls/ls.command.ts`](../src/legacy/commands/storage/ls/ls.command.ts) | -| `storage cp` | `ported` | [`../src/legacy/commands/storage/cp/cp.command.ts`](../src/legacy/commands/storage/cp/cp.command.ts) | -| `storage mv` | `ported` | [`../src/legacy/commands/storage/mv/mv.command.ts`](../src/legacy/commands/storage/mv/mv.command.ts) | -| `storage rm` | `ported` | [`../src/legacy/commands/storage/rm/rm.command.ts`](../src/legacy/commands/storage/rm/rm.command.ts) | -| `test db` | `ported` | [`../src/legacy/commands/test/db/db.command.ts`](../src/legacy/commands/test/db/db.command.ts) | -| `test new` | `ported` | [`../src/legacy/commands/test/new/new.command.ts`](../src/legacy/commands/test/new/new.command.ts) | -| `seed buckets` | `ported` | [`../src/legacy/commands/seed/buckets/buckets.command.ts`](../src/legacy/commands/seed/buckets/buckets.command.ts) | -| `db diff` | `ported` | [`../src/legacy/commands/db/diff/diff.command.ts`](../src/legacy/commands/db/diff/diff.command.ts) — native pg-delta / migra; `--use-pgadmin` / `--use-pg-schema` delegate to Go | -| `db dump` | `ported` | [`../src/legacy/commands/db/dump/dump.command.ts`](../src/legacy/commands/db/dump/dump.command.ts) | -| `db push` | `wrapped` | [`../src/legacy/commands/db/push/push.command.ts`](../src/legacy/commands/db/push/push.command.ts) | -| `db pull` | `ported` | [`../src/legacy/commands/db/pull/pull.command.ts`](../src/legacy/commands/db/pull/pull.command.ts) — native pg-delta / migra; `--declarative` (deprecated alias `--use-pg-delta`) + `--diff-engine` (migra\|pg-delta); `--experimental` / initial `pg_dump` delegate to Go | -| `db reset` | `wrapped` | [`../src/legacy/commands/db/reset/reset.command.ts`](../src/legacy/commands/db/reset/reset.command.ts) — includes Go-parity `--sql-paths` override for `[db.seed].sql_paths` | -| `db lint` | `ported` | [`../src/legacy/commands/db/lint/lint.command.ts`](../src/legacy/commands/db/lint/lint.command.ts) | -| `db start` | `wrapped` | [`../src/legacy/commands/db/start/start.command.ts`](../src/legacy/commands/db/start/start.command.ts) | -| `db query` | `ported` | [`../src/legacy/commands/db/query/query.command.ts`](../src/legacy/commands/db/query/query.command.ts) | -| `db advisors` | `ported` | [`../src/legacy/commands/db/advisors/advisors.command.ts`](../src/legacy/commands/db/advisors/advisors.command.ts) | -| `db test` | `wrapped` | [`../src/legacy/commands/db/test/test.command.ts`](../src/legacy/commands/db/test/test.command.ts) | -| `db branch create` | `wrapped` | [`../src/legacy/commands/db/branch/create/create.command.ts`](../src/legacy/commands/db/branch/create/create.command.ts) | -| `db branch delete` | `wrapped` | [`../src/legacy/commands/db/branch/delete/delete.command.ts`](../src/legacy/commands/db/branch/delete/delete.command.ts) | -| `db branch list` | `wrapped` | [`../src/legacy/commands/db/branch/list/list.command.ts`](../src/legacy/commands/db/branch/list/list.command.ts) | -| `db branch switch` | `wrapped` | [`../src/legacy/commands/db/branch/switch/switch.command.ts`](../src/legacy/commands/db/branch/switch/switch.command.ts) | -| `db remote changes` | `wrapped` | [`../src/legacy/commands/db/remote/changes/changes.command.ts`](../src/legacy/commands/db/remote/changes/changes.command.ts) | -| `db remote commit` | `wrapped` | [`../src/legacy/commands/db/remote/commit/commit.command.ts`](../src/legacy/commands/db/remote/commit/commit.command.ts) | -| `db schema declarative sync` | `ported` | [`../src/legacy/commands/db/schema/declarative/sync/sync.command.ts`](../src/legacy/commands/db/schema/declarative/sync/sync.command.ts) | -| `db schema declarative generate` | `ported` | [`../src/legacy/commands/db/schema/declarative/generate/generate.command.ts`](../src/legacy/commands/db/schema/declarative/generate/generate.command.ts) | +| Command | Legacy status | Legacy command path | +| -------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `orgs list` | `ported` | [`../src/legacy/commands/orgs/list/list.command.ts`](../src/legacy/commands/orgs/list/list.command.ts) | +| `orgs create` | `ported` | [`../src/legacy/commands/orgs/create/create.command.ts`](../src/legacy/commands/orgs/create/create.command.ts) | +| `projects list` | `ported` | [`../src/legacy/commands/projects/list/list.command.ts`](../src/legacy/commands/projects/list/list.command.ts) | +| `projects create` | `ported` | [`../src/legacy/commands/projects/create/create.command.ts`](../src/legacy/commands/projects/create/create.command.ts) | +| `projects delete` | `ported` | [`../src/legacy/commands/projects/delete/delete.command.ts`](../src/legacy/commands/projects/delete/delete.command.ts) | +| `projects api-keys` | `ported` | [`../src/legacy/commands/projects/api-keys/api-keys.command.ts`](../src/legacy/commands/projects/api-keys/api-keys.command.ts) | +| `branches list` | `ported` | [`../src/legacy/commands/branches/list/list.command.ts`](../src/legacy/commands/branches/list/list.command.ts) | +| `branches create` | `ported` | [`../src/legacy/commands/branches/create/create.command.ts`](../src/legacy/commands/branches/create/create.command.ts) | +| `branches get` | `ported` | [`../src/legacy/commands/branches/get/get.command.ts`](../src/legacy/commands/branches/get/get.command.ts) | +| `branches update` | `ported` | [`../src/legacy/commands/branches/update/update.command.ts`](../src/legacy/commands/branches/update/update.command.ts) | +| `branches pause` | `ported` | [`../src/legacy/commands/branches/pause/pause.command.ts`](../src/legacy/commands/branches/pause/pause.command.ts) | +| `branches unpause` | `ported` | [`../src/legacy/commands/branches/unpause/unpause.command.ts`](../src/legacy/commands/branches/unpause/unpause.command.ts) | +| `branches delete` | `ported` | [`../src/legacy/commands/branches/delete/delete.command.ts`](../src/legacy/commands/branches/delete/delete.command.ts) | +| `branches disable` | `ported` | [`../src/legacy/commands/branches/disable/disable.command.ts`](../src/legacy/commands/branches/disable/disable.command.ts) | +| `secrets list` | `ported` | [`../src/legacy/commands/secrets/list/list.command.ts`](../src/legacy/commands/secrets/list/list.command.ts) | +| `secrets set` | `ported` | [`../src/legacy/commands/secrets/set/set.command.ts`](../src/legacy/commands/secrets/set/set.command.ts) | +| `secrets unset` | `ported` | [`../src/legacy/commands/secrets/unset/unset.command.ts`](../src/legacy/commands/secrets/unset/unset.command.ts) | +| `config push` | `ported` | [`../src/legacy/commands/config/push/push.command.ts`](../src/legacy/commands/config/push/push.command.ts) | +| `backups list` | `ported` | [`../src/legacy/commands/backups/list/list.command.ts`](../src/legacy/commands/backups/list/list.command.ts) | +| `backups restore` | `ported` | [`../src/legacy/commands/backups/restore/restore.command.ts`](../src/legacy/commands/backups/restore/restore.command.ts) | +| `snippets list` | `ported` | [`../src/legacy/commands/snippets/list/list.command.ts`](../src/legacy/commands/snippets/list/list.command.ts) | +| `snippets download` | `ported` | [`../src/legacy/commands/snippets/download/download.command.ts`](../src/legacy/commands/snippets/download/download.command.ts) | +| `sso list` | `ported` | [`../src/legacy/commands/sso/list/list.command.ts`](../src/legacy/commands/sso/list/list.command.ts) | +| `sso add` | `ported` | [`../src/legacy/commands/sso/add/add.command.ts`](../src/legacy/commands/sso/add/add.command.ts) | +| `sso remove` | `ported` | [`../src/legacy/commands/sso/remove/remove.command.ts`](../src/legacy/commands/sso/remove/remove.command.ts) | +| `sso update` | `ported` | [`../src/legacy/commands/sso/update/update.command.ts`](../src/legacy/commands/sso/update/update.command.ts) | +| `sso show` | `ported` | [`../src/legacy/commands/sso/show/show.command.ts`](../src/legacy/commands/sso/show/show.command.ts) | +| `sso info` | `ported` | [`../src/legacy/commands/sso/info/info.command.ts`](../src/legacy/commands/sso/info/info.command.ts) | +| `domains create` | `ported` | [`../src/legacy/commands/domains/create/create.command.ts`](../src/legacy/commands/domains/create/create.command.ts) | +| `domains get` | `ported` | [`../src/legacy/commands/domains/get/get.command.ts`](../src/legacy/commands/domains/get/get.command.ts) | +| `domains reverify` | `ported` | [`../src/legacy/commands/domains/reverify/reverify.command.ts`](../src/legacy/commands/domains/reverify/reverify.command.ts) | +| `domains activate` | `ported` | [`../src/legacy/commands/domains/activate/activate.command.ts`](../src/legacy/commands/domains/activate/activate.command.ts) | +| `domains delete` | `ported` | [`../src/legacy/commands/domains/delete/delete.command.ts`](../src/legacy/commands/domains/delete/delete.command.ts) | +| `vanity-subdomains get` | `ported` | [`../src/legacy/commands/vanity-subdomains/get/get.command.ts`](../src/legacy/commands/vanity-subdomains/get/get.command.ts) | +| `vanity-subdomains check-availability` | `ported` | [`../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts`](../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts) | +| `vanity-subdomains activate` | `ported` | [`../src/legacy/commands/vanity-subdomains/activate/activate.command.ts`](../src/legacy/commands/vanity-subdomains/activate/activate.command.ts) | +| `vanity-subdomains delete` | `ported` | [`../src/legacy/commands/vanity-subdomains/delete/delete.command.ts`](../src/legacy/commands/vanity-subdomains/delete/delete.command.ts) | +| `network-bans get` | `ported` | [`../src/legacy/commands/network-bans/get/get.command.ts`](../src/legacy/commands/network-bans/get/get.command.ts) | +| `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` | `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) | +| `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` | `ported` | [`../src/legacy/commands/login/login.command.ts`](../src/legacy/commands/login/login.command.ts) | +| `logout` | `ported` | [`../src/legacy/commands/logout/logout.command.ts`](../src/legacy/commands/logout/logout.command.ts) | +| `link` | `ported` | [`../src/legacy/commands/link/link.command.ts`](../src/legacy/commands/link/link.command.ts) | +| `unlink` | `ported` | [`../src/legacy/commands/unlink/unlink.command.ts`](../src/legacy/commands/unlink/unlink.command.ts) | +| `bootstrap` | `ported` | [`../src/legacy/commands/bootstrap/bootstrap.command.ts`](../src/legacy/commands/bootstrap/bootstrap.command.ts) (native; `db push` step delegated to the Go binary — interim) | +| `init` | `ported` | [`../src/legacy/commands/init/init.command.ts`](../src/legacy/commands/init/init.command.ts) | +| `services` | `ported` | [`../src/legacy/commands/services/services.command.ts`](../src/legacy/commands/services/services.command.ts) | +| `start` | `wrapped` | [`../src/legacy/commands/start/start.command.ts`](../src/legacy/commands/start/start.command.ts) | +| `stop` | `wrapped` | [`../src/legacy/commands/stop/stop.command.ts`](../src/legacy/commands/stop/stop.command.ts) | +| `status` | `wrapped` | [`../src/legacy/commands/status/status.command.ts`](../src/legacy/commands/status/status.command.ts) | +| `telemetry enable` | `ported` | [`../src/legacy/commands/telemetry/enable/enable.command.ts`](../src/legacy/commands/telemetry/enable/enable.command.ts) | +| `telemetry disable` | `ported` | [`../src/legacy/commands/telemetry/disable/disable.command.ts`](../src/legacy/commands/telemetry/disable/disable.command.ts) | +| `telemetry status` | `ported` | [`../src/legacy/commands/telemetry/status/status.command.ts`](../src/legacy/commands/telemetry/status/status.command.ts) | +| `migration list` | `ported` | [`../src/legacy/commands/migration/list/list.command.ts`](../src/legacy/commands/migration/list/list.command.ts) — native; merged Local/Remote/Time-UTC Glamour table | +| `migration new` | `ported` | [`../src/legacy/commands/migration/new/new.command.ts`](../src/legacy/commands/migration/new/new.command.ts) — native; writes `supabase/migrations/_.sql` from piped stdin | +| `migration repair` | `ported` | [`../src/legacy/commands/migration/repair/repair.command.ts`](../src/legacy/commands/migration/repair/repair.command.ts) — native; transactional TRUNCATE/UPSERT/DELETE, repair-all prompt | +| `migration squash` | `wrapped` | [`../src/legacy/commands/migration/squash/squash.command.ts`](../src/legacy/commands/migration/squash/squash.command.ts) | +| `migration up` | `ported` | [`../src/legacy/commands/migration/up/up.command.ts`](../src/legacy/commands/migration/up/up.command.ts) — native; pending compute + vault upsert + per-file apply | +| `migration down` | `ported` | [`../src/legacy/commands/migration/down/down.command.ts`](../src/legacy/commands/migration/down/down.command.ts) — native; drop + vault + migrate&seed to target version | +| `migration fetch` | `ported` | [`../src/legacy/commands/migration/fetch/fetch.command.ts`](../src/legacy/commands/migration/fetch/fetch.command.ts) — native; writes history rows to `supabase/migrations/` | +| `gen types` | `ported` | [`../src/legacy/commands/gen/types/types.command.ts`](../src/legacy/commands/gen/types/types.command.ts) | +| `gen signing-key` | `ported` | [`../src/legacy/commands/gen/signing-key/signing-key.command.ts`](../src/legacy/commands/gen/signing-key/signing-key.command.ts) | +| `gen bearer-jwt` | `wrapped` | [`../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts`](../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts) | +| `gen keys` | `wrapped` | [`../src/legacy/commands/gen/keys/keys.command.ts`](../src/legacy/commands/gen/keys/keys.command.ts) | +| `functions list` | `wrapped` | [`../src/legacy/commands/functions/list/list.command.ts`](../src/legacy/commands/functions/list/list.command.ts) | +| `functions delete` | `ported` | [`../src/legacy/commands/functions/delete/delete.command.ts`](../src/legacy/commands/functions/delete/delete.command.ts) | +| `functions download` | `ported` | [`../src/legacy/commands/functions/download/download.command.ts`](../src/legacy/commands/functions/download/download.command.ts) | +| `functions deploy` | `ported` | [`../src/legacy/commands/functions/deploy/deploy.command.ts`](../src/legacy/commands/functions/deploy/deploy.command.ts) | +| `functions new` | `ported` | [`../src/legacy/commands/functions/new/new.command.ts`](../src/legacy/commands/functions/new/new.command.ts) | +| `functions serve` | `ported` | [`../src/legacy/commands/functions/serve/serve.command.ts`](../src/legacy/commands/functions/serve/serve.command.ts) | +| `storage ls` | `ported` | [`../src/legacy/commands/storage/ls/ls.command.ts`](../src/legacy/commands/storage/ls/ls.command.ts) | +| `storage cp` | `ported` | [`../src/legacy/commands/storage/cp/cp.command.ts`](../src/legacy/commands/storage/cp/cp.command.ts) | +| `storage mv` | `ported` | [`../src/legacy/commands/storage/mv/mv.command.ts`](../src/legacy/commands/storage/mv/mv.command.ts) | +| `storage rm` | `ported` | [`../src/legacy/commands/storage/rm/rm.command.ts`](../src/legacy/commands/storage/rm/rm.command.ts) | +| `test db` | `ported` | [`../src/legacy/commands/test/db/db.command.ts`](../src/legacy/commands/test/db/db.command.ts) | +| `test new` | `ported` | [`../src/legacy/commands/test/new/new.command.ts`](../src/legacy/commands/test/new/new.command.ts) | +| `seed buckets` | `ported` | [`../src/legacy/commands/seed/buckets/buckets.command.ts`](../src/legacy/commands/seed/buckets/buckets.command.ts) | +| `db diff` | `ported` | [`../src/legacy/commands/db/diff/diff.command.ts`](../src/legacy/commands/db/diff/diff.command.ts) — native pg-delta / migra; `--use-pgadmin` / `--use-pg-schema` delegate to Go | +| `db dump` | `ported` | [`../src/legacy/commands/db/dump/dump.command.ts`](../src/legacy/commands/db/dump/dump.command.ts) | +| `db push` | `wrapped` | [`../src/legacy/commands/db/push/push.command.ts`](../src/legacy/commands/db/push/push.command.ts) | +| `db pull` | `ported` | [`../src/legacy/commands/db/pull/pull.command.ts`](../src/legacy/commands/db/pull/pull.command.ts) — native pg-delta / migra; `--declarative` (deprecated alias `--use-pg-delta`) + `--diff-engine` (migra\|pg-delta); initial-migra pull dumps the schema natively (`pg_dump`) + appends the diff; only `--experimental` structured dump still delegates to Go (needs a TS DDL parser for `WriteStructuredSchemas`) | +| `db reset` | `wrapped` | [`../src/legacy/commands/db/reset/reset.command.ts`](../src/legacy/commands/db/reset/reset.command.ts) — includes Go-parity `--sql-paths` override for `[db.seed].sql_paths` | +| `db lint` | `ported` | [`../src/legacy/commands/db/lint/lint.command.ts`](../src/legacy/commands/db/lint/lint.command.ts) | +| `db start` | `wrapped` | [`../src/legacy/commands/db/start/start.command.ts`](../src/legacy/commands/db/start/start.command.ts) | +| `db query` | `ported` | [`../src/legacy/commands/db/query/query.command.ts`](../src/legacy/commands/db/query/query.command.ts) | +| `db advisors` | `ported` | [`../src/legacy/commands/db/advisors/advisors.command.ts`](../src/legacy/commands/db/advisors/advisors.command.ts) | +| `db test` | `wrapped` | [`../src/legacy/commands/db/test/test.command.ts`](../src/legacy/commands/db/test/test.command.ts) | +| `db branch create` | `wrapped` | [`../src/legacy/commands/db/branch/create/create.command.ts`](../src/legacy/commands/db/branch/create/create.command.ts) | +| `db branch delete` | `wrapped` | [`../src/legacy/commands/db/branch/delete/delete.command.ts`](../src/legacy/commands/db/branch/delete/delete.command.ts) | +| `db branch list` | `wrapped` | [`../src/legacy/commands/db/branch/list/list.command.ts`](../src/legacy/commands/db/branch/list/list.command.ts) | +| `db branch switch` | `wrapped` | [`../src/legacy/commands/db/branch/switch/switch.command.ts`](../src/legacy/commands/db/branch/switch/switch.command.ts) | +| `db remote changes` | `wrapped` | [`../src/legacy/commands/db/remote/changes/changes.command.ts`](../src/legacy/commands/db/remote/changes/changes.command.ts) | +| `db remote commit` | `wrapped` | [`../src/legacy/commands/db/remote/commit/commit.command.ts`](../src/legacy/commands/db/remote/commit/commit.command.ts) | +| `db schema declarative sync` | `ported` | [`../src/legacy/commands/db/schema/declarative/sync/sync.command.ts`](../src/legacy/commands/db/schema/declarative/sync/sync.command.ts) | +| `db schema declarative generate` | `ported` | [`../src/legacy/commands/db/schema/declarative/generate/generate.command.ts`](../src/legacy/commands/db/schema/declarative/generate/generate.command.ts) | Flag divergences from the Go reference: diff --git a/apps/cli/src/legacy/commands/bootstrap/bootstrap.integration.test.ts b/apps/cli/src/legacy/commands/bootstrap/bootstrap.integration.test.ts index 41e386c16e..56c8fa1878 100644 --- a/apps/cli/src/legacy/commands/bootstrap/bootstrap.integration.test.ts +++ b/apps/cli/src/legacy/commands/bootstrap/bootstrap.integration.test.ts @@ -33,6 +33,7 @@ import { LegacyOutputFlag, } from "../../../shared/legacy/global-flags.ts"; import { LegacyGoProxy } from "../../../shared/legacy/go-proxy.service.ts"; +import { CliArgs } from "../../../shared/cli/cli-args.service.ts"; import { LegacyTemplateService, type LegacyStarterTemplate } from "./bootstrap.templates.ts"; import { legacyBootstrap } from "./bootstrap.handler.ts"; import type { LegacyBootstrapFlags } from "./bootstrap.command.ts"; @@ -175,6 +176,7 @@ function setup(opts: SetupOpts = {}) { Layer.succeed(LegacyWorkdirFlag, opts.workdir ?? Option.some(tempRoot.current)), Layer.succeed(LegacyYesFlag, opts.yes ?? false), Layer.succeed(LegacyDebugFlag, opts.debug ?? false), + Layer.succeed(CliArgs, { args: [] }), ); return { diff --git a/apps/cli/src/legacy/commands/bootstrap/bootstrap.workdir-cache.integration.test.ts b/apps/cli/src/legacy/commands/bootstrap/bootstrap.workdir-cache.integration.test.ts index db5b08d01b..285a19900e 100644 --- a/apps/cli/src/legacy/commands/bootstrap/bootstrap.workdir-cache.integration.test.ts +++ b/apps/cli/src/legacy/commands/bootstrap/bootstrap.workdir-cache.integration.test.ts @@ -33,6 +33,7 @@ import { LegacyYesFlag, } from "../../../shared/legacy/global-flags.ts"; import { LegacyGoProxy } from "../../../shared/legacy/go-proxy.service.ts"; +import { CliArgs } from "../../../shared/cli/cli-args.service.ts"; import { legacyDebugLoggerLayer } from "../../shared/legacy-debug-logger.layer.ts"; import { legacyIdentityStitchLayer } from "../../shared/legacy-identity-stitch.ts"; import { legacyCliConfigLayer } from "../../config/legacy-cli-config.layer.ts"; @@ -117,6 +118,7 @@ describe("legacy bootstrap linked-project cache location", () => { Layer.succeed(LegacyYesFlag, false), Layer.succeed(LegacyOutputFlag, Option.none()), Layer.succeed(LegacyDebugFlag, false), + Layer.succeed(CliArgs, { args: [] }), ); const runtime = mockRuntimeInfo({ cwd: parent }); const credentials = mockLegacyCredentialsTracked(); diff --git a/apps/cli/src/legacy/commands/config/push/push.command.ts b/apps/cli/src/legacy/commands/config/push/push.command.ts index 744b143201..05854892e9 100644 --- a/apps/cli/src/legacy/commands/config/push/push.command.ts +++ b/apps/cli/src/legacy/commands/config/push/push.command.ts @@ -1,7 +1,9 @@ +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 { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyConfigPush } from "./push.handler.ts"; @@ -34,5 +36,5 @@ export const legacyConfigPushCommand = Command.make("push", config).pipe( withJsonErrorHandling, ), ), - Command.provide(legacyManagementApiRuntimeLayer(["config", "push"])), + Command.provide(Layer.mergeAll(legacyManagementApiRuntimeLayer(["config", "push"]), stdinLayer)), ); diff --git a/apps/cli/src/legacy/commands/config/push/push.handler.ts b/apps/cli/src/legacy/commands/config/push/push.handler.ts index 7624c2c23e..ebd07baabc 100644 --- a/apps/cli/src/legacy/commands/config/push/push.handler.ts +++ b/apps/cli/src/legacy/commands/config/push/push.handler.ts @@ -1,14 +1,16 @@ -import { loadProjectConfig } from "@supabase/config"; -import { Effect } from "effect"; +import { findProjectRoot, loadProjectConfig } from "@supabase/config"; +import { Effect, FileSystem, Path } from "effect"; import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts"; 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 { legacyResolveYes } from "../../../../shared/legacy/global-flags.ts"; +import { legacyResolveYesWithProjectEnv } from "../../../../shared/legacy/global-flags.ts"; import { Output } from "../../../../shared/output/output.service.ts"; import { RuntimeInfo } from "../../../../shared/runtime/runtime-info.service.ts"; +import { legacyLoadProjectEnv } from "../../../shared/legacy-db-config.toml-read.ts"; import { mapLegacyHttpError } from "../../../shared/legacy-http-errors.ts"; +import { legacyPromptYesNo } from "../../../shared/legacy-prompt-yes-no.ts"; import { apiSubsetFromConfig, apiToUpdateBody, diffApiWithRemote } from "./config-sync/api.sync.ts"; import { applyRemoteAuthConfig, @@ -87,8 +89,18 @@ export const legacyConfigPush = Effect.fn("legacy.config.push")(function* ( const linkedProjectCache = yield* LegacyLinkedProjectCache; const telemetryState = yield* LegacyTelemetryState; const runtimeInfo = yield* RuntimeInfo; - // `--yes` OR `SUPABASE_YES` (Go's viper AutomaticEnv, root.go:318-320). - const yes = yield* legacyResolveYes; + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + // `--yes` OR `SUPABASE_YES` (Go's viper AutomaticEnv, root.go:318-320). Go's + // `config push` runs `flags.LoadConfig`, which imports `supabase/.env` before + // `PromptYesNo` reads `viper.GetBool("YES")`, so a `SUPABASE_YES` set only in + // `supabase/.env` auto-confirms. Resolve against the project env, not just the + // flag + shell env. Load it from the resolved project root (walking up, same as + // `loadProjectConfig` below and Go's `ChangeWorkDir` before `LoadConfig`), so a + // push from a subdirectory still reads the project root's `supabase/.env`. + const projectRoot = (yield* findProjectRoot(runtimeInfo.cwd)) ?? runtimeInfo.cwd; + const projectEnv = yield* legacyLoadProjectEnv(fs, path, projectRoot); + const yes = yield* legacyResolveYesWithProjectEnv(projectEnv); const ref = yield* resolver.resolve(flags.projectRef); @@ -154,24 +166,17 @@ export const legacyConfigPush = Effect.fn("legacy.config.push")(function* ( yield* output.raw(`Pushing config to project: ${projectId}\n`, "stderr"); - // keep(name): Go push.go `keep` + console.PromptYesNo(title, true). - const keep = (name: string): Effect.Effect => + // keep(name): Go push.go `keep` + console.PromptYesNo(title, true). The shared + // helper mirrors Go's prompt across all modes, including scanning piped stdin on + // a non-TTY before falling back to the default (`console.go:64-82`). + const keep = (name: string) => Effect.gen(function* () { const item = cost.get(name); const title = item === undefined ? `Do you want to push ${name} config to remote?` : `Enabling ${item.name} will cost you ${item.price}. Keep it enabled?`; - if (output.format !== "text") { - return true; - } - if (yes) { - yield* output.raw(`${title} [Y/n] y\n`, "stderr"); - return true; - } - return yield* output - .promptConfirm(title, { defaultValue: true }) - .pipe(Effect.orElseSucceed(() => true)); + return yield* legacyPromptYesNo(output, yes, title, true); }); const services: Array = []; diff --git a/apps/cli/src/legacy/commands/config/push/push.integration.test.ts b/apps/cli/src/legacy/commands/config/push/push.integration.test.ts index 0cc0ffe678..d70a99bc20 100644 --- a/apps/cli/src/legacy/commands/config/push/push.integration.test.ts +++ b/apps/cli/src/legacy/commands/config/push/push.integration.test.ts @@ -17,7 +17,7 @@ import { mockLegacyTelemetryStateTracked, useLegacyTempWorkdir, } from "../../../../../tests/helpers/legacy-mocks.ts"; -import { mockRuntimeInfo } from "../../../../../tests/helpers/mocks.ts"; +import { mockRuntimeInfo, mockStdin, mockTty } from "../../../../../tests/helpers/mocks.ts"; import { LegacyYesFlag } from "../../../../shared/legacy/global-flags.ts"; import { legacyConfigPush } from "./push.handler.ts"; @@ -57,6 +57,12 @@ function setup(opts: { readonly yes?: boolean; readonly confirm?: ReadonlyArray; readonly promptFail?: boolean; + /** stdin interactivity; defaults to a TTY so prompt-driven tests reach the confirm. */ + readonly stdinIsTty?: boolean; + /** Piped (non-TTY) stdin answers, one consumed per confirmation prompt. */ + readonly pipedAnswers?: ReadonlyArray; + /** Working directory the handler runs from; defaults to the temp project root. */ + readonly runtimeCwd?: string; }) { writeConfig(opts.toml); const routes = opts.routes ?? {}; @@ -111,10 +117,15 @@ function setup(opts: { out, api, cliConfig: mockLegacyCliConfig({ workdir: tempRoot.current }), - runtimeInfo: mockRuntimeInfo({ cwd: tempRoot.current }), + runtimeInfo: mockRuntimeInfo({ cwd: opts.runtimeCwd ?? tempRoot.current }), telemetry: telemetry.layer, linkedProjectCache: linkedProjectCache.layer, + tty: mockTty({ stdinIsTty: opts.stdinIsTty ?? true, stdoutIsTty: false }), }), + mockStdin( + opts.stdinIsTty ?? true, + opts.pipedAnswers ? `${opts.pipedAnswers.join("\n")}\n` : undefined, + ), Layer.succeed(LegacyYesFlag, opts.yes ?? false), ); return { layer, out, api, telemetry, linkedProjectCache }; @@ -296,10 +307,13 @@ project_id = "abcdefghijklmnopqrst" }).pipe(Effect.provide(layer)); }); - it.live("defaults to yes in non-TTY text without --yes", () => { - const { layer, api } = setup({ + it.live("defaults to yes on empty non-TTY stdin, echoing the prompt", () => { + // Go's `PromptYesNo(..., true)` (`push.go:36`) prints the label and scans + // stdin even on a non-terminal (`console.go:96-102`); with no piped input the + // scan is empty and it falls back to the default (`true`), so the push proceeds. + const { layer, api, out } = setup({ toml: API_ONLY_TOML, - promptFail: true, + stdinIsTty: false, routes: { postgrestGet: { status: 200, body: POSTGREST_DISABLED }, postgresGet: { status: 200, body: {} }, @@ -310,9 +324,102 @@ project_id = "abcdefghijklmnopqrst" expect(api.requests.some((r) => r.method === "PATCH" && r.url.includes("/postgrest"))).toBe( true, ); + // Label printed + empty answer echoed (Go's non-TTY `PromptText`). + expect(out.stderrText).toContain("Do you want to push api config to remote? [Y/n] \n"); }).pipe(Effect.provide(layer)); }); + it.live("honors a piped 'n' decline on non-TTY stdin (no update)", () => { + // Regression: Go scans piped stdin before defaulting (`console.go:74-82`), so a + // piped `n` cancels the push even on a non-terminal — it must not silently apply. + const { layer, api, out } = setup({ + toml: API_ONLY_TOML, + stdinIsTty: false, + pipedAnswers: ["n"], + routes: { + postgrestGet: { status: 200, body: POSTGREST_DISABLED }, + postgresGet: { status: 200, body: {} }, + }, + }); + return Effect.gen(function* () { + yield* legacyConfigPush({ projectRef: Option.none() }); + expect(api.requests.some((r) => r.method === "PATCH" && r.url.includes("/postgrest"))).toBe( + false, + ); + // The consumed answer is echoed to stderr (Go's non-TTY `PromptText`). + expect(out.stderrText).toContain("Do you want to push api config to remote? [Y/n] n"); + }).pipe(Effect.provide(layer)); + }); + + it.live("honors SUPABASE_YES from supabase/.env even against a piped 'n'", () => { + // Go's config push runs `flags.LoadConfig`, importing `supabase/.env` before + // `PromptYesNo`, so a project-local `SUPABASE_YES=true` auto-confirms before + // stdin is read — the push proceeds despite the piped `n`. + const prev = process.env["SUPABASE_YES"]; + delete process.env["SUPABASE_YES"]; + const { layer, api } = setup({ + toml: API_ONLY_TOML, + stdinIsTty: false, + pipedAnswers: ["n"], + routes: { + postgrestGet: { status: 200, body: POSTGREST_DISABLED }, + postgresGet: { status: 200, body: {} }, + }, + }); + // Written after setup()'s writeConfig created supabase/. + writeFileSync(join(tempRoot.current, "supabase", ".env"), "SUPABASE_YES=true\n"); + return Effect.gen(function* () { + yield* legacyConfigPush({ projectRef: Option.none() }); + expect(api.requests.some((r) => r.method === "PATCH" && r.url.includes("/postgrest"))).toBe( + true, + ); + }).pipe( + Effect.ensuring( + Effect.sync(() => { + if (prev === undefined) delete process.env["SUPABASE_YES"]; + else process.env["SUPABASE_YES"] = prev; + }), + ), + Effect.provide(layer), + ); + }); + + it.live("loads config-push env from the project root when run from a subdirectory", () => { + // Go's ChangeWorkDir moves to the project root before flags.LoadConfig, so a + // SUPABASE_YES in /supabase/.env auto-confirms even when invoked from a + // subdir. The env load must walk up like loadProjectConfig, not use the raw cwd. + const prev = process.env["SUPABASE_YES"]; + delete process.env["SUPABASE_YES"]; + const sub = join(tempRoot.current, "nested", "dir"); + mkdirSync(sub, { recursive: true }); + const { layer, api } = setup({ + toml: API_ONLY_TOML, + stdinIsTty: false, + pipedAnswers: ["n"], + runtimeCwd: sub, + routes: { + postgrestGet: { status: 200, body: POSTGREST_DISABLED }, + postgresGet: { status: 200, body: {} }, + }, + }); + // `.env` lives at the project ROOT (setup's writeConfig wrote config.toml there). + writeFileSync(join(tempRoot.current, "supabase", ".env"), "SUPABASE_YES=true\n"); + return Effect.gen(function* () { + yield* legacyConfigPush({ projectRef: Option.none() }); + expect(api.requests.some((r) => r.method === "PATCH" && r.url.includes("/postgrest"))).toBe( + true, + ); + }).pipe( + Effect.ensuring( + Effect.sync(() => { + if (prev === undefined) delete process.env["SUPABASE_YES"]; + else process.env["SUPABASE_YES"] = prev; + }), + ), + Effect.provide(layer), + ); + }); + it.live("emits a structured summary in json mode without prompts", () => { const { layer, out } = setup({ toml: API_ONLY_TOML, @@ -394,6 +501,7 @@ file_size_limit = "50MiB" cliConfig: mockLegacyCliConfig({ workdir: tempRoot.current }), runtimeInfo: mockRuntimeInfo({ cwd: tempRoot.current }), }), + mockStdin(true), Layer.succeed(LegacyYesFlag, true), ); return Effect.gen(function* () { @@ -462,7 +570,10 @@ function setupService(opts: { runtimeInfo: mockRuntimeInfo({ cwd: opts.runtimeCwd ?? tempRoot.current }), telemetry: telemetry.layer, linkedProjectCache: linkedProjectCache.layer, + // Gated-service prompts model an interactive user answering via `confirm`. + tty: mockTty({ stdinIsTty: true, stdoutIsTty: false }), }), + mockStdin(true), Layer.succeed(LegacyYesFlag, opts.yes ?? false), ); return { layer, out, apiMock }; diff --git a/apps/cli/src/legacy/commands/db/dump/dump.handler.ts b/apps/cli/src/legacy/commands/db/dump/dump.handler.ts index 0a7ae2202b..0f72399b85 100644 --- a/apps/cli/src/legacy/commands/db/dump/dump.handler.ts +++ b/apps/cli/src/legacy/commands/db/dump/dump.handler.ts @@ -5,22 +5,20 @@ import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-proje import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; import { LegacyDbConfigResolver } from "../../../shared/legacy-db-config.service.ts"; import type { LegacyDbConnType } from "../../../shared/legacy-db-target-flags.ts"; -import { legacyReadDbToml } from "../../../shared/legacy-db-config.toml-read.ts"; +import { + legacyApplyProjectEnv, + legacyLoadProjectEnv, + legacyReadDbToml, +} from "../../../shared/legacy-db-config.toml-read.ts"; import { legacyReadProjectRefFile } from "../../../shared/legacy-temp-paths.ts"; import { legacyResolveDbImage } from "../../../shared/legacy-db-image.ts"; -import { LegacyDockerRun } from "../../../shared/legacy-docker-run.service.ts"; -import { legacyGetRegistryImageUrl } from "../../../shared/legacy-docker-registry.ts"; import { legacyIpv6Suggestion, legacyIsIPv6ConnectivityError, } from "../../../shared/legacy-connect-errors.ts"; -import { legacyBold, legacyYellow } from "../../../shared/legacy-colors.ts"; -import { - LegacyDnsResolverFlag, - LegacyNetworkIdFlag, -} from "../../../../shared/legacy/global-flags.ts"; +import { legacyBold } from "../../../shared/legacy-colors.ts"; +import { LegacyDnsResolverFlag } from "../../../../shared/legacy/global-flags.ts"; import { Output } from "../../../../shared/output/output.service.ts"; -import { RuntimeInfo } from "../../../../shared/runtime/runtime-info.service.ts"; import type { LegacyDbDumpFlags } from "./dump.command.ts"; import { LegacyDbDumpMutuallyExclusiveFlagsError, @@ -33,12 +31,14 @@ import { legacyBuildRoleDumpEnv, legacyBuildSchemaDumpEnv, legacyExpandScript, -} from "./dump.env.ts"; +} from "../shared/legacy-pg-dump.env.ts"; +import { legacyStreamPgDump } from "../shared/legacy-pg-dump.run.ts"; +import { legacyRunWithPoolerFallback } from "../shared/legacy-pooler-fallback.ts"; import { legacyDumpDataScript, legacyDumpRoleScript, legacyDumpSchemaScript, -} from "./dump.scripts.ts"; +} from "../shared/legacy-pg-dump.scripts.ts"; /** * Mutually-exclusive flag groups, in cobra's check order (it sorts the joined @@ -62,15 +62,12 @@ const toOpenFileError = (cause: { readonly message: string }) => export const legacyDbDump = Effect.fn("legacy.db.dump")(function* (flags: LegacyDbDumpFlags) { const output = yield* Output; const resolver = yield* LegacyDbConfigResolver; - const docker = yield* LegacyDockerRun; const cliConfig = yield* LegacyCliConfig; - const runtimeInfo = yield* RuntimeInfo; const telemetryState = yield* LegacyTelemetryState; const linkedProjectCache = yield* LegacyLinkedProjectCache; const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dnsResolver = yield* LegacyDnsResolverFlag; - const networkIdFlag = yield* LegacyNetworkIdFlag; // Resolved linked ref, captured so the post-run finalizer can cache the project // (GET /v1/projects/{ref}) AFTER the command's own API calls — matching Go's @@ -78,6 +75,13 @@ export const legacyDbDump = Effect.fn("legacy.db.dump")(function* (flags: Legacy let linkedRefForCache: string | undefined; yield* Effect.gen(function* () { + // Make an allowlisted `supabase/.env` registry override visible to the + // synchronous `process.env` reader in `legacyGetRegistryImageUrl` (the pg_dump + // image), reverted when this scope closes. Go's `loadNestedEnv` `os.Setenv`s the + // project `.env`; the pure `legacyLoadProjectEnv` no longer does that as a side + // effect of `resolveDbPassword`, so `db dump` opts in explicitly here. + yield* legacyApplyProjectEnv(yield* legacyLoadProjectEnv(fs, path, cliConfig.workdir)); + // The grouped boolean flags are modelled as `Option` (presence = pflag `Changed`) // for the mutex/target checks; resolve their effective values here for the places // that consume the value (Go's `BoolVar` default is false). @@ -138,7 +142,6 @@ export const legacyDbDump = Effect.fn("legacy.db.dump")(function* (flags: Legacy // linked, defaulting to linked when neither local nor db-url is set // (`internal/utils/flags/db_url.go:46-62`). const useLocal = Option.isNone(flags.dbUrl) && Option.isSome(flags.local); - const useLinked = Option.isNone(flags.dbUrl) && Option.isNone(flags.local); // `connType` selects the resolver branch (Go's Changed-first precedence): a // `--db-url` wins, then explicit `--local`; otherwise dump defaults to linked // (unlike the other db commands, whose unset default is local). @@ -190,7 +193,7 @@ export const legacyDbDump = Effect.fn("legacy.db.dump")(function* (flags: Legacy // remote `project_id`) fails rather than silently printing a script. const tomlValues = yield* legacyReadDbToml(fs, path, cliConfig.workdir, linkedRef); - // 4. Pick the mode-specific script + env (pure builders, `dump.env.ts`). + // 4. Pick the mode-specific script + env (pure builders, `legacy-pg-dump.env.ts`). // Go declares --schema/-s and --exclude/-x as cobra StringSlice // (`apps/cli-go/cmd/db.go:432,444`); both flags are CSV-parsed at the flag // level via `legacyParseSchemaFlags` (pflag `readAsCSV` semantics, quoted @@ -267,33 +270,14 @@ export const legacyDbDump = Effect.fn("legacy.db.dump")(function* (flags: Legacy // 6. Diagnostic to stderr (Go writes this for both real and dry-run paths). yield* output.raw(`Dumping ${mode.verb} from ${db} database...\n`, "stderr"); - // 7. Run the pg_dump container, capturing stdout. dump always uses host - // networking (`dockerExec` sets `NetworkMode: NetworkHost`), overridden only - // by `--network-id` (Go's `DockerStart`). No `SecurityOpt` is set. - const networkId = Option.getOrUndefined(networkIdFlag); - const network = - networkId !== undefined && networkId.length > 0 - ? { _tag: "named" as const, name: networkId } - : { _tag: "host" as const }; - const extraHosts = - runtimeInfo.platform === "linux" ? ["host.docker.internal:host-gateway"] : []; - - const dockerOpts = (env: Readonly>) => ({ - image: legacyGetRegistryImageUrl(image), - cmd: ["bash", "-c", mode.script, "--"], - env, - binds: [], - workingDir: Option.none(), - securityOpt: [], - extraHosts, - network, - }); - + // 7. Run the pg_dump container, streaming stdout. `legacyStreamPgDump` applies + // the registry mirror + host networking (overridden by `--network-id`) and + // tees stderr, mirroring Go's `dockerExec` (`internal/db/dump/dump.go`). + // // Go streams pg_dump stdout straight to the destination sink (the `--file` handle // or `os.Stdout`) via `stdcopy.StdCopy` with `Follow:true`, at constant memory // (`apps/cli-go/internal/utils/docker.go:374,394`). Mirror that: write each chunk - // to the destination as it arrives instead of buffering the whole dump. stderr is - // teed live (Go's `io.MultiWriter(os.Stderr, errBuf)`). + // to the destination as it arrives instead of buffering the whole dump. const runContainer = (env: Readonly>) => Option.isSome(resolvedFile) ? // `--file`: (re)truncate then append-stream. Truncating per attempt @@ -309,10 +293,12 @@ export const legacyDbDump = Effect.fn("legacy.db.dump")(function* (flags: Legacy const file = yield* fs .open(resolvedFile.value, { flag: "a" }) .pipe(Effect.mapError(toOpenFileError)); - return yield* docker.runStream(dockerOpts(env), { + return yield* legacyStreamPgDump({ + image, + script: mode.script, + env, onStdout: (chunk) => file.writeAll(chunk).pipe(Effect.mapError(toOpenFileError)), - teeStderr: true, }); }), ), @@ -321,53 +307,40 @@ export const legacyDbDump = Effect.fn("legacy.db.dump")(function* (flags: Legacy : // stdout: write each chunk straight to stdout (binary-safe, no decode). // On a pooler retry Go leaves the partial first-attempt bytes on stdout // (its `resetOutput` can't rewind a pipe); streaming matches that. - docker.runStream(dockerOpts(env), { + legacyStreamPgDump({ + image, + script: mode.script, + env, onStdout: (chunk) => output.rawBytes(chunk), - teeStderr: true, }); - let result = yield* runContainer(modeEnv); - - // 7b. Container-level pooler fallback (Go's `RunWithPoolerFallback`, - // `internal/db/dump/pooler_fallback.go`). A linked dump can reach the direct - // host from the CLI process (so the resolver returned the direct conn) yet - // fail from inside the pg_dump container on an IPv6-only Docker network. When - // the captured container stderr classifies as an IPv6 connectivity error, - // retry once through the project's IPv4 transaction pooler. Gated to the - // `--linked` path with a direct `db..` connection (Go's - // `PoolerFallbackEligible` + `ProjectRefFromDirectDbHost`). - if ( - result.exitCode !== 0 && - useLinked && - !isLocal && - conn.host.startsWith("db.") && - conn.host.endsWith(`.${cliConfig.projectHost}`) && - legacyIsIPv6ConnectivityError(result.stderr) - ) { - // Go's `PoolerFallbackConfig` returns `ok=false` on ANY fallback-resolution - // error (e.g. temp-role creation/wait fails) and then reports the ORIGINAL - // pg_dump failure with the IPv6 guidance — the optional retry must not replace - // the actionable dump error. So a resolution failure is treated as "no - // fallback" (the original `result` is surfaced at step 9). - const pooler = yield* resolver - .resolvePoolerFallback({ - dbUrl: flags.dbUrl, - connType: "linked", - dnsResolver, - password: flags.password, - }) - .pipe(Effect.orElseSucceed(() => Option.none())); - if (Option.isSome(pooler)) { - yield* output.raw( - `${legacyYellow( - `Warning: Direct connection to ${conn.host} is unavailable because this environment does not support IPv6.\nRetrying via the IPv4 connection pooler.`, - )}\n`, - "stderr", - ); - yield* output.raw(`Dumping ${mode.verb} from ${db} database...\n`, "stderr"); - result = yield* runContainer(mode.buildEnv(pooler.value, opt)); - } - } + // 7b. Container-level IPv6 → IPv4-pooler retry (Go's `RunWithPoolerFallback`, + // `internal/db/dump/pooler_fallback.go`), shared with `db pull`. A linked dump + // can reach the direct host from the CLI process (so the resolver returned the + // direct conn) yet fail from inside the pg_dump container on an IPv6-only Docker + // network. `resolvePoolerFallback` is neutralised to `None` on any resolution + // error so the original, actionable pg_dump failure is surfaced at step 9 rather + // than a fallback-setup error (Go's `PoolerFallbackConfig` ok=false path). + // `db dump` re-prints the "Dumping …" line on the retry (Go prints it inside the + // run closure, `dump.go:39-45`). + const result = yield* legacyRunWithPoolerFallback({ + result: yield* runContainer(modeEnv), + connType, + host: conn.host, + isLocal, + projectHost: cliConfig.projectHost, + resolvePooler: () => + resolver + .resolvePoolerFallback({ + dbUrl: flags.dbUrl, + connType: "linked", + dnsResolver, + password: flags.password, + }) + .pipe(Effect.orElseSucceed(() => Option.none())), + runWithConn: (c) => runContainer(mode.buildEnv(c, opt)), + reprintOnRetry: output.raw(`Dumping ${mode.verb} from ${db} database...\n`, "stderr"), + }); // 8. The dump has already been streamed to the destination by `runContainer` // (to `--file` or stdout) as pg_dump produced it. @@ -407,5 +380,8 @@ export const legacyDbDump = Effect.fn("legacy.db.dump")(function* (flags: Legacy ), ), Effect.ensuring(telemetryState.flush), + // Scope the `SUPABASE_INTERNAL_IMAGE_REGISTRY`-from-`.env` apply above to this + // command run: `legacyApplyProjectEnv` registers a finalizer that reverts it. + Effect.scoped, ); }); diff --git a/apps/cli/src/legacy/commands/db/dump/dump.live.test.ts b/apps/cli/src/legacy/commands/db/dump/dump.live.test.ts new file mode 100644 index 0000000000..6577c268b5 --- /dev/null +++ b/apps/cli/src/legacy/commands/db/dump/dump.live.test.ts @@ -0,0 +1,48 @@ +import { existsSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; + +import { + describeLiveDataPlane, + requireLiveProjectRef, + runSupabaseLive, +} from "../../../../../tests/helpers/live.ts"; + +const LIVE_TIMEOUT_MS = 300_000; + +// A fresh, isolated temp workdir so the CLI writes the dump there and never touches +// the repo tree. The provisioned project ref is supplied to `--linked` via the +// `SUPABASE_PROJECT_ID` env var — that is the `--linked` resolver chain in both Go +// and the legacy port (flag → `SUPABASE_PROJECT_ID` → `supabase/.temp/project-ref`); +// `config.toml`'s `project_id` is NOT consulted for `--linked`. +function tempWorkdir(): string { + return mkdtempSync(join(tmpdir(), "sb-db-dump-live-")); +} + +// Data-plane: needs a provisioned project whose database is routable (the +// cli-e2e-ci Linux runner). `describeLiveDataPlane` runs this only when the project +// instance is ACTIVE_HEALTHY, so a control-plane-only stack (ref set but the DB +// unreachable, e.g. local macOS or the current cli-e2e-ci control-plane case) is +// skipped rather than timing out on pg_dump. +describeLiveDataPlane("supabase db dump (live)", () => { + test("dumps the linked project's schema to a file", { timeout: LIVE_TIMEOUT_MS }, async () => { + const ref = requireLiveProjectRef(); + const dir = tempWorkdir(); + try { + const outFile = join(dir, "schema.sql"); + const { exitCode, stdout, stderr } = await runSupabaseLive( + ["db", "dump", "--linked", "-f", outFile], + { cwd: dir, env: { SUPABASE_PROJECT_ID: ref }, exitTimeoutMs: LIVE_TIMEOUT_MS - 20_000 }, + ); + expect(`${stdout}${stderr}`).not.toContain("Unauthorized"); + expect(exitCode).toBe(0); + // The native pg_dump container (shared `legacyStreamPgDump`) opened + wrote + // the dump file. A fresh project's public schema may be near-empty, so assert + // the file was created rather than its size. + expect(existsSync(outFile)).toBe(true); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/apps/cli/src/legacy/commands/db/pull/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/db/pull/SIDE_EFFECTS.md index c8ae8ac6e9..6ca74fd5f5 100644 --- a/apps/cli/src/legacy/commands/db/pull/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/db/pull/SIDE_EFFECTS.md @@ -2,9 +2,12 @@ Native Effect port. Pulls the remote schema into either a new timestamped migration (diffing a throwaway shadow against the remote, native pg-delta or -migra) or declarative files (`--declarative`, native pg-delta export). The rare -`--experimental` structured-dump and initial-pull `pg_dump` (migra) sub-branches -delegate to the bundled Go binary. +migra) or declarative files (`--declarative`, native pg-delta export). The +initial-migra pull (no local migrations) seeds the migration file with a native +`pg_dump` of the remote schema (a Docker `pg_dump` container, with IPv4 +transaction-pooler fallback) and then appends the migra diff. Only the rare +`--experimental` structured-dump sub-branch still delegates to the bundled Go +binary (it needs `format.WriteStructuredSchemas`, which has no TS port yet). ## Files Read @@ -17,18 +20,20 @@ delegate to the bundled Go binary. ## Files Written -| Path | Format | When | -| ----------------------------------------------------------- | ------ | ------------------------------------- | -| `/supabase/migrations/_.sql` | SQL | migration-style pull (non-empty diff) | -| `/supabase/database/**` | SQL | `--declarative` | -| `~/.supabase//linked-project.json` | JSON | linked (post-run cache) | -| `~/.supabase/telemetry.json` | JSON | every invocation (post-run) | +| Path | Format | When | +| ----------------------------------------------------------- | ------ | -------------------------------------------------------------------------- | +| `/supabase/migrations/_.sql` | SQL | migration-style pull (non-empty diff, or the initial-migra `pg_dump` seed) | +| `/supabase/database/**` | SQL | `--declarative` | +| `~/.supabase//linked-project.json` | JSON | linked (post-run cache) | +| `~/.supabase/telemetry.json` | JSON | every invocation (post-run) | ## Docker - Edge-runtime container (pg-delta export / pg-delta or migra diff). - Shadow Postgres container (provisioned + torn down via the Go `db __shadow` seam). - `supabase/migra` container — the migra OOM bash fallback only. +- `pg_dump` container — the initial-migra pull's native remote-schema dump + (`legacyStreamPgDump`, shared with `db dump`). ## API Routes / DB @@ -47,7 +52,7 @@ delegate to the bundled Go binary. | `SUPABASE_ACCESS_TOKEN` | auth for the linked target | no | | `SUPABASE_DB_PASSWORD` | remote DB password (overridden by `-p`) | no | | `SUPABASE_EXPERIMENTAL_PG_DELTA` | force pg-delta diff engine | no | -| `SUPABASE_EXPERIMENTAL` | structured-dump pull branch (delegates to Go) | no | +| `SUPABASE_EXPERIMENTAL` | structured-dump pull branch (still delegates) | no | | `PGDELTA_NPM_REGISTRY` | scoped npm registry for edge-runtime | no | ## Exit Codes @@ -82,7 +87,12 @@ Progress strings still go to stderr; stdout carries a single structured envelope - `--declarative` / deprecated `--use-pg-delta` are mutually exclusive with `--diff-engine`; `--db-url` / `--linked` (default) / `--local` are a target group. - `--use-pg-delta` is hidden and emits the cobra deprecation line to stderr. -- The `--experimental` structured-dump branch and the initial-pull `pg_dump` (migra, - no local migrations) rebuild the argv and exec the bundled Go binary (their side - effects are Go's); the Go child's telemetry is disabled so the single - `cli_command_executed` event comes from this TS command. +- The initial-migra pull (no local migrations) is native: it streams a `pg_dump` of + the remote schema into the migration file, then appends the migra diff. An empty + diff after a non-empty dump is swallowed (Go's `swallowInitialInSync`); an empty + dump + empty diff is "No schema changes found". +- The `--experimental` structured-dump branch still rebuilds the argv and execs the + bundled Go binary (its side effects are Go's), because Go's + `format.WriteStructuredSchemas` needs a PostgreSQL DDL AST parser that has no TS + port yet. The Go child's telemetry is disabled so the single `cli_command_executed` + event comes from this TS command. diff --git a/apps/cli/src/legacy/commands/db/pull/pull.errors.ts b/apps/cli/src/legacy/commands/db/pull/pull.errors.ts index eaf81d3da6..25c3c36300 100644 --- a/apps/cli/src/legacy/commands/db/pull/pull.errors.ts +++ b/apps/cli/src/legacy/commands/db/pull/pull.errors.ts @@ -49,3 +49,15 @@ export class LegacyDbPullInSyncError extends Data.TaggedError("LegacyDbPullInSyn export class LegacyDbPullWriteError extends Data.TaggedError("LegacyDbPullWriteError")<{ readonly message: string; }> {} + +/** + * The initial-pull pg_dump container exited non-zero. Go's `dumpRemoteSchema` + * (`internal/db/pull/pull.go:144-158`) propagates the `dump.RunWithPoolerFallback` + * error; byte-matches the dump's `"error running container: exit " + code`. Carries + * the same optional IPv6 transaction-pooler hint the dump path attaches, which + * `Output.fail` prints bare on stderr after the message. + */ +export class LegacyDbPullDumpError extends Data.TaggedError("LegacyDbPullDumpError")<{ + readonly message: string; + readonly suggestion?: string; +}> {} diff --git a/apps/cli/src/legacy/commands/db/pull/pull.handler.ts b/apps/cli/src/legacy/commands/db/pull/pull.handler.ts index aa7a44fc27..e95fc934f2 100644 --- a/apps/cli/src/legacy/commands/db/pull/pull.handler.ts +++ b/apps/cli/src/legacy/commands/db/pull/pull.handler.ts @@ -3,20 +3,27 @@ import { Clock, Effect, FileSystem, Option, Path } from "effect"; import { LegacyDnsResolverFlag, LegacyExperimentalFlag, - LegacyYesFlag, + legacyResolveYesWithProjectEnv, } from "../../../../shared/legacy/global-flags.ts"; import { CliArgs } from "../../../../shared/cli/cli-args.service.ts"; import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; import { Output } from "../../../../shared/output/output.service.ts"; import { LegacyCliConfig } from "../../../config/legacy-cli-config.service.ts"; -import { legacyAqua, legacyBold, legacyYellow } from "../../../shared/legacy-colors.ts"; -import { legacyIsIPv6ConnectivityError } from "../../../shared/legacy-connect-errors.ts"; +import { legacyAqua, legacyBold } from "../../../shared/legacy-colors.ts"; +import { legacyPromptYesNo } from "../../../shared/legacy-prompt-yes-no.ts"; +import { + legacyIpv6Suggestion, + legacyIsIPv6ConnectivityError, +} from "../../../shared/legacy-connect-errors.ts"; import { LegacyDbConfigResolver } from "../../../shared/legacy-db-config.service.ts"; +import { legacyResolveDbImage } from "../../../shared/legacy-db-image.ts"; import { LegacyDbConnection, type LegacyPgConnInput, } from "../../../shared/legacy-db-connection.service.ts"; import { + legacyApplyProjectEnv, + legacyLoadProjectEnv, legacyReadDbToml, legacyResolveDeclarativeDir, } from "../../../shared/legacy-db-config.toml-read.ts"; @@ -37,6 +44,14 @@ import { legacyShouldUsePgDelta, } from "../shared/legacy-diff-engine.ts"; import { legacyDiffMigra } from "../shared/legacy-migra.ts"; +import { type LegacyDumpOptions, legacyBuildSchemaDumpEnv } from "../shared/legacy-pg-dump.env.ts"; +import { legacyStreamPgDump } from "../shared/legacy-pg-dump.run.ts"; +import { + legacyEmitPoolerFallbackWarning, + legacyIsDirectLinkedHost, + legacyRunWithPoolerFallback, +} from "../shared/legacy-pooler-fallback.ts"; +import { legacyDumpSchemaScript } from "../shared/legacy-pg-dump.scripts.ts"; import { legacyFormatMigrationTimestamp, legacyGetMigrationPath, @@ -53,6 +68,7 @@ import { legacySaveEmptyPgDeltaPullDebug } from "./pull.debug.ts"; import { LegacyDeclarativeSeam } from "../shared/legacy-pgdelta.seam.service.ts"; import type { LegacyDbPullFlags } from "./pull.command.ts"; import { + LegacyDbPullDumpError, LegacyDbPullEngineConflictError, LegacyDbPullInSyncError, LegacyDbPullMigrationConflictError, @@ -71,6 +87,9 @@ import { legacyUpdateMigrationHistory } from "./pull.sync.ts"; const DEPRECATION_LINE = "Flag --use-pg-delta has been deprecated, use --declarative with [experimental.pgdelta] enabled = true in your config.toml instead."; +/** Migration-file mode for the initial pg_dump seed (Go's `OpenFile(..., 0644)`). */ +const MIGRATION_FILE_MODE = 0o644; + /** Builds a plain Postgres URL from a resolved connection (Go's `ToPostgresURL`). */ const connToUrl = (conn: LegacyPgConnInput): string => legacyToPostgresURL({ @@ -88,7 +107,7 @@ const connToUrl = (conn: LegacyPgConnInput): string => : {}), }); -/** Rebuilds the `db pull` argv for the Go-delegated branches (initial-migra / EXPERIMENTAL dump). */ +/** Rebuilds the `db pull` argv for the Go-delegated `--experimental` structured-dump branch. */ const rebuildDelegateArgs = (flags: LegacyDbPullFlags): Array => { const args = ["db", "pull"]; if (Option.isSome(flags.name)) args.push(flags.name.value); @@ -135,16 +154,27 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy const cliConfig = yield* LegacyCliConfig; const telemetryState = yield* LegacyTelemetryState; const linkedProjectCache = yield* LegacyLinkedProjectCache; - const yes = yield* LegacyYesFlag; const experimental = yield* LegacyExperimentalFlag; const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const dnsResolver = yield* LegacyDnsResolverFlag; const cliArgs = yield* CliArgs; + // `--yes` OR `SUPABASE_YES` (Go's `viper.GetBool("YES")`, root.go:318-320). Go + // loads the project `.env` via `loadNestedEnv` inside `ParseDatabaseConfig` + // (config.go:701) before `PromptYesNo`, so a `SUPABASE_YES` set only in + // `supabase/.env` auto-confirms the native initial-migra history repair too. + const projectEnv = yield* legacyLoadProjectEnv(fs, path, cliConfig.workdir); + const yes = yield* legacyResolveYesWithProjectEnv(projectEnv); + let linkedRefForCache: string | undefined; yield* Effect.gen(function* () { + // Make an allowlisted `supabase/.env` registry override visible to the + // synchronous `process.env` reader in `legacyGetRegistryImageUrl` (the pg_dump + // seed + migra/pg-delta diff images), reverted when this scope closes. Go's + // `loadNestedEnv` `os.Setenv`s the project `.env` before the dump runs. + yield* legacyApplyProjectEnv(projectEnv); const name = Option.getOrElse(flags.name, () => "remote_schema"); // `--declarative` and the deprecated `--use-pg-delta` both bind to the same // `useDeclarative` variable in Go (`cmd/db.go:534-535`), so when BOTH are @@ -242,10 +272,12 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy Effect.catch((error) => Effect.gen(function* () { if ( - connType === "linked" && - !resolved.isLocal && - resolved.conn.host.startsWith("db.") && - resolved.conn.host.endsWith(`.${cliConfig.projectHost}`) && + legacyIsDirectLinkedHost({ + connType, + host: resolved.conn.host, + isLocal: resolved.isLocal, + projectHost: cliConfig.projectHost, + }) && legacyIsIPv6ConnectivityError(error.message) ) { // Go's `PoolerFallbackConfig` returns `ok=false` on ANY resolution @@ -260,12 +292,7 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy }) .pipe(Effect.orElseSucceed(() => Option.none())); if (Option.isSome(pooler)) { - yield* output.raw( - `${legacyYellow( - `Warning: Direct connection to ${resolved.conn.host} is unavailable because this environment does not support IPv6.\nRetrying via the IPv4 connection pooler.`, - )}\n`, - "stderr", - ); + yield* legacyEmitPoolerFallbackWarning(resolved.conn.host); return yield* attempt(connToUrl(pooler.value)); } } @@ -284,19 +311,15 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy }), }); - // Runs a Go-delegated pull (initial-migra / EXPERIMENTAL structured dump). In - // machine-output mode the child's stdout is captured and a structured envelope - // is emitted instead, so scripted callers get valid JSON rather than the Go - // child's human output on stdout (CLI-1546: stdout is payload-only in machine - // mode). The child is run with a non-TTY stdin (`"ignore"`) so the migration - // path's "Update remote migration history table?" prompt (Go's `PromptYesNo`, - // `internal/db/pull/pull.go:73`) takes its `true` default without blocking the - // JSON caller. `remoteHistoryUpdated` is passed per call site because the two - // delegated Go paths differ: the initial-migra path prompts + calls - // `repair.UpdateMigrationTable` (so `true`), while the EXPERIMENTAL structured - // dump returns before writing a migration or touching `schema_migrations` - // (`pull.go:49-61`, so `false`). `schemaWritten` stays `null` — the child owns - // the write and doesn't surface the path on stdout. + // Runs the Go-delegated `--experimental` structured dump (still delegated; see the + // EXPERIMENTAL branch below for why). In machine-output mode the child's stdout is + // captured and a structured envelope is emitted instead, so scripted callers get + // valid JSON rather than the Go child's human output on stdout (CLI-1546: stdout is + // payload-only in machine mode). The child is run with a non-TTY stdin (`"ignore"`) + // so any prompt takes its default without blocking the JSON caller. The EXPERIMENTAL + // structured dump returns before writing a migration or touching `schema_migrations` + // (`pull.go:49-61`), so `remoteHistoryUpdated` is `false`; `schemaWritten` stays + // `null` — the child owns the write and doesn't surface the path on stdout. const delegatePull = ( engine: "migra" | "pg-delta", opts: { readonly remoteHistoryUpdated: boolean }, @@ -383,11 +406,18 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy return; } - // Go's `EXPERIMENTAL` structured-dump branch depends on unported `pg_dump` - // — delegate the whole pull to Go. viper resolves `EXPERIMENTAL` from - // *either* the global `--experimental` pflag or `SUPABASE_EXPERIMENTAL` - // (`cmd/root.go:318-320,327,334`), so honor both forms here; the legacy - // root only forwards `--experimental` to Go proxy argv, never into env. + // Go's `EXPERIMENTAL` structured-dump branch (`pull.go:49-61`) stays + // delegated to Go. pg_dump itself is now native (used by the initial-migra + // path below), but this branch also calls `format.WriteStructuredSchemas` + // (`cli-go/internal/migration/format/format.go:99`), which parses every + // dumped statement with a PostgreSQL DDL AST parser (`multigres`, ~50 node + // types) to route objects into structured files. No Postgres DDL parser + // exists in TS yet, so porting it is tracked separately; until then the + // experimental path delegates the whole pull to Go. viper resolves + // `EXPERIMENTAL` from *either* the global `--experimental` pflag or + // `SUPABASE_EXPERIMENTAL` (`cmd/root.go:318-320,327,334`), so honor both + // forms here; the legacy root only forwards `--experimental` to Go proxy + // argv, never into env. if (experimental || legacyParseBoolEnv(toml.envLookup("SUPABASE_EXPERIMENTAL"))) { // Go's structured-dump path returns before writing a migration or // touching schema_migrations (`pull.go:49-61`), so no history repair. @@ -417,12 +447,113 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy }), ); } - if (sync.kind === "missing" && !usePgDeltaDiff) { - // Initial pull with the migra engine needs `pg_dump` — delegate to Go. - // Go's migration path prompts + updates schema_migrations on the non-TTY - // default (`pull.go:73-76`), so the history is repaired. - yield* delegatePull("migra", { remoteHistoryUpdated: true }); - return; + // Initial pull, migra engine (Go's `run` → `assertRemoteInSync` returns + // `errMissing`): seed the migration file with a pg_dump of the remote schema + // (`dumpRemoteSchema`, `pull.go:144-158`), then run the migra diff below as a + // second pass appended to the same file (`diffRemoteSchema(ctx, nil, …)`), + // which captures default privileges / managed schemas pg_dump can't emit. + // pg-delta initial pulls skip the dump (`pull.go:126` `if !usePgDeltaDiff`): + // they diff against an empty shadow, which already yields the full schema. + const seededFromDump = sync.kind === "missing" && !usePgDeltaDiff; + // Tracks whether the pg_dump seed wrote any bytes, for Go's + // `ensureMigrationWritten` (`pull.go:68,263-268`): an empty dump + empty diff + // is "in sync", a non-empty dump is a valid initial migration on its own. + let seedWroteBytes = false; + if (seededFromDump) { + yield* legacyMakeDir(fs, path.dirname(migrationPath)).pipe( + Effect.mapError((cause) => new LegacyDbPullWriteError({ message: cause.message })), + ); + const image = yield* legacyResolveDbImage( + fs, + path, + cliConfig.workdir, + toml.majorVersion, + Option.getOrUndefined(toml.orioledbVersion), + ); + // Go's `migration.DumpSchema` default options: no schema filter (so the + // internal-schema exclude list applies) and comments stripped (`EXTRA_SED`). + const dumpEnvOpt: LegacyDumpOptions = { + schema: [], + keepComments: false, + excludeTable: [], + columnInsert: false, + }; + const toDumpOpenError = (cause: { readonly message: string }) => + new LegacyDbPullDumpError({ message: `failed to open dump file: ${cause.message}` }); + // Stream pg_dump → migration file, (re)truncating per attempt so a pooler + // retry leaves only the successful attempt's bytes (Go's `resetOutput`). + const runSchemaDump = (target: LegacyPgConnInput) => { + // Reset per attempt alongside the truncate, mirroring Go's `resetOutput` + // (`pooler_fallback.go:98-113`) which zeroes the file before the pooler + // retry. Go decides in-sync from the file on disk (`hasMigrationContent`, + // `pull.go:251-268`), so only the final successful attempt's bytes count: a + // partial direct write that then IPv6-fails must not leave this flag stuck + // true, or an empty pooler retry would be mis-reported as a schema write. + seedWroteBytes = false; + return fs + .writeFile(migrationPath, new Uint8Array(0), { mode: MIGRATION_FILE_MODE }) + .pipe(Effect.mapError(toDumpOpenError)) + .pipe( + Effect.andThen( + Effect.scoped( + Effect.gen(function* () { + const file = yield* fs + .open(migrationPath, { flag: "a" }) + .pipe(Effect.mapError(toDumpOpenError)); + return yield* legacyStreamPgDump({ + image, + script: legacyDumpSchemaScript, + env: legacyBuildSchemaDumpEnv(target, dumpEnvOpt), + onStdout: (chunk) => { + if (chunk.length > 0) seedWroteBytes = true; + return file.writeAll(chunk).pipe( + Effect.mapError( + (cause) => + new LegacyDbPullWriteError({ + message: `failed to write migration file: ${cause.message}`, + }), + ), + ); + }, + }); + }), + ), + ), + ); + }; + // Go's `dumpRemoteSchema` prints this once, before `RunWithPoolerFallback`. + yield* output.raw("Dumping schema from remote database...\n", "stderr"); + // Container-level IPv6 → IPv4-pooler retry (Go's `dump.RunWithPoolerFallback`), + // shared with `db dump`. `db pull` prints "Dumping…" once above, so it passes + // `Effect.void` for the retry re-print (Go prints it outside the retried closure). + const dumpResult = yield* legacyRunWithPoolerFallback({ + result: yield* runSchemaDump(resolved.conn), + connType, + host: resolved.conn.host, + isLocal: resolved.isLocal, + projectHost: cliConfig.projectHost, + resolvePooler: () => + resolver + .resolvePoolerFallback({ + dbUrl: flags.dbUrl, + connType: "linked", + dnsResolver, + password: flags.password ?? Option.none(), + }) + .pipe(Effect.orElseSucceed(() => Option.none())), + runWithConn: runSchemaDump, + reprintOnRetry: Effect.void, + }); + if (dumpResult.exitCode !== 0) { + return yield* Effect.fail( + new LegacyDbPullDumpError({ + message: `error running container: exit ${dumpResult.exitCode}`, + ...(legacyIsIPv6ConnectivityError(dumpResult.stderr) + ? { suggestion: legacyIpv6Suggestion() } + : {}), + }), + ); + } } // Native diff: shadow (baseline + local migrations) vs remote → migration SQL. @@ -504,7 +635,12 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy }).pipe(Effect.ensuring(seam.removeShadowContainer(shadow.container))); const out = diffOutcome.sql; - if (out.trim().length === 0) { + const diffEmpty = out.trim().length === 0; + // A non-initial pull with an empty diff is "in sync" and fails (Go's + // `diffRemoteSchema`). The initial-migra path seeded the file with a pg_dump + // above, so its empty second pass is swallowed (`swallowInitialInSync`, + // `pull.go:256-261`) and falls through to the shared tail below. + if (diffEmpty && !seededFromDump) { // Go saves a pg-delta debug bundle and embeds its path in the in-sync // error when PGDELTA_DEBUG is set (`internal/db/pull/pull.go:176-185`); a // bundle-save failure falls through to the plain in-sync error. @@ -541,17 +677,54 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy new LegacyDbPullInSyncError({ message: "No schema changes found" }), ); } - yield* legacyMakeDir(fs, path.dirname(migrationPath)).pipe( - Effect.mapError((cause) => new LegacyDbPullWriteError({ message: cause.message })), - ); - yield* fs.writeFileString(migrationPath, out).pipe( - Effect.mapError( - (cause) => - new LegacyDbPullWriteError({ - message: `failed to write migration file: ${cause.message}`, + + if (!diffEmpty) { + if (seededFromDump) { + // Append the migra diff to the dump-seeded file (Go's `diffRemoteSchema` + // opens the migration file `O_APPEND`, `pull.go:191`). + yield* Effect.scoped( + Effect.gen(function* () { + const file = yield* fs.open(migrationPath, { flag: "a" }).pipe( + Effect.mapError( + (cause) => + new LegacyDbPullWriteError({ + message: `failed to open migration file: ${cause.message}`, + }), + ), + ); + yield* file.writeAll(new TextEncoder().encode(out)).pipe( + Effect.mapError( + (cause) => + new LegacyDbPullWriteError({ + message: `failed to write migration file: ${cause.message}`, + }), + ), + ); }), - ), - ); + ); + } else { + yield* legacyMakeDir(fs, path.dirname(migrationPath)).pipe( + Effect.mapError((cause) => new LegacyDbPullWriteError({ message: cause.message })), + ); + yield* fs.writeFileString(migrationPath, out).pipe( + Effect.mapError( + (cause) => + new LegacyDbPullWriteError({ + message: `failed to write migration file: ${cause.message}`, + }), + ), + ); + } + } + + // Go's `ensureMigrationWritten` (`pull.go:68,263-268`): a dump that produced + // nothing followed by an empty diff leaves the file empty → in sync. + if (seededFromDump && !seedWroteBytes && diffEmpty) { + return yield* Effect.fail( + new LegacyDbPullInSyncError({ message: "No schema changes found" }), + ); + } + yield* output.raw(`Schema written to ${legacyBold(migrationPath)}\n`, "stderr"); // Prompt to update the remote migration history table. Go calls @@ -561,20 +734,10 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy // (`internal/utils/console.go:74-82`) — it never fails the command. let remoteHistoryUpdated = false; const updateHistoryTitle = "Update remote migration history table?"; - const shouldUpdate = yield* Effect.gen(function* () { - // Machine output (json/stream-json) never prompts — the non-text layers - // report non-interactive and fail every prompt — so take Go's default. - if (output.format !== "text") return true; - if (yes) { - yield* output.raw(`${updateHistoryTitle} [Y/n] y\n`, "stderr"); - return true; - } - // A non-interactive stdin or any prompt error falls back to the default, - // matching Go's `PromptYesNo` returning `def` on error/timeout. - return yield* output - .promptConfirm(updateHistoryTitle, { defaultValue: true }) - .pipe(Effect.orElseSucceed(() => true)); - }); + // Go's `PromptYesNo(ctx, title, true)` (`internal/db/pull/pull.go:73`): honors + // `--yes`, scans piped stdin on a non-TTY before falling back to the default + // (`console.go:64-82`), and otherwise prompts on a real TTY. + const shouldUpdate = yield* legacyPromptYesNo(output, yes, updateHistoryTitle, true); if (shouldUpdate) { yield* legacyUpdateMigrationHistory(session, fs, path, migrationPath, timestamp); remoteHistoryUpdated = true; @@ -599,5 +762,8 @@ export const legacyDbPull = Effect.fn("legacy.db.pull")(function* (flags: Legacy ), ), Effect.ensuring(telemetryState.flush), + // Scope the `SUPABASE_INTERNAL_IMAGE_REGISTRY`-from-`.env` apply below to this + // command run: `legacyApplyProjectEnv` registers a finalizer that reverts it. + Effect.scoped, ); }); diff --git a/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts b/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts index 794b61ab5e..745521c02f 100644 --- a/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts @@ -10,7 +10,12 @@ import { mockLegacyTelemetryStateTracked, useLegacyTempWorkdir, } from "../../../../../tests/helpers/legacy-mocks.ts"; -import { mockOutput, mockRuntimeInfo, mockTty } from "../../../../../tests/helpers/mocks.ts"; +import { + mockOutput, + mockRuntimeInfo, + mockStdin, + mockTty, +} from "../../../../../tests/helpers/mocks.ts"; import { LegacyDnsResolverFlag, LegacyExperimentalFlag, @@ -44,6 +49,8 @@ interface SetupOpts { readonly remoteVersions?: ReadonlyArray; readonly edgeStdout?: string; // diff SQL or declarative export JSON readonly stdinIsTty?: boolean; + // Piped (non-TTY) stdin answers, one consumed per confirmation prompt. + readonly pipedAnswers?: ReadonlyArray; readonly yes?: boolean; readonly experimental?: boolean; readonly shadowTargetOverride?: string; @@ -56,6 +63,17 @@ interface SetupOpts { readonly poolerAvailable?: boolean; readonly delegateStdout?: string; // stdout returned by a captured Go-delegate run readonly catalogStdout?: string; // stdout returned by pg-delta catalog-export runs + // Initial-migra pull: the bytes the native pg_dump container streams to its sink, + // its exit code / stderr, and (when set) an IPv6 stderr that fails the FIRST dump + // attempt so the pooler retry runs (the second attempt then streams `dumpStdout`). + readonly dumpStdout?: string; + readonly dumpExitCode?: number; + readonly dumpStderr?: string; + readonly dumpFailFirstWith?: string; + // Bytes the FIRST dump attempt streams to its sink before it fails with + // `dumpFailFirstWith`, reproducing a direct attempt that emits preamble then + // exits non-zero on an IPv6 drop. + readonly dumpFailFirstPartialBytes?: string; // Raw argv seen by the handler (CliArgs). Only consulted when both // `--declarative` and `--use-pg-delta` are present, to replay pflag's // last-occurrence-wins ordering; defaults to empty. @@ -112,10 +130,30 @@ function setup(workdir: string, opts: SetupOpts = {}) { }, }); + // The initial-migra pull seeds the migration file with a native pg_dump via + // `runStream`; deliver the configured bytes to `onStdout` (as Go's StdCopy would), + // then report the exit code + stderr. `dumpFailFirstWith` fails the first attempt + // so the pooler retry runs. + const dumpCalls: Array<{ env: Readonly>; image: string }> = []; + let dumpRunCount = 0; const docker = Layer.succeed(LegacyDockerRun, { run: () => Effect.die("run unused"), runCapture: () => Effect.die("runCapture unused"), - runStream: () => Effect.die("runStream unused"), + runStream: (runOpts, streamOpts) => + Effect.gen(function* () { + dumpRunCount += 1; + dumpCalls.push({ env: runOpts.env, image: runOpts.image }); + if (opts.dumpFailFirstWith !== undefined && dumpRunCount === 1) { + if (opts.dumpFailFirstPartialBytes !== undefined) { + const partial = new TextEncoder().encode(opts.dumpFailFirstPartialBytes); + if (partial.length > 0) yield* streamOpts.onStdout(partial); + } + return { exitCode: 1, stderr: opts.dumpFailFirstWith }; + } + const bytes = new TextEncoder().encode(opts.dumpStdout ?? ""); + if (bytes.length > 0) yield* streamOpts.onStdout(bytes); + return { exitCode: opts.dumpExitCode ?? 0, stderr: opts.dumpStderr ?? "" }; + }), }); const execLog: string[] = []; @@ -196,6 +234,10 @@ function setup(workdir: string, opts: SetupOpts = {}) { proxy, mockLegacyCliConfig({ workdir, projectId: Option.some("test") }), mockTty({ stdinIsTty: opts.stdinIsTty ?? false, stdoutIsTty: false }), + mockStdin( + opts.stdinIsTty ?? false, + opts.pipedAnswers ? `${opts.pipedAnswers.join("\n")}\n` : undefined, + ), Layer.succeed(LegacyYesFlag, opts.yes ?? false), Layer.succeed(LegacyExperimentalFlag, opts.experimental ?? false), Layer.succeed(LegacyDnsResolverFlag, "native"), @@ -216,6 +258,7 @@ function setup(workdir: string, opts: SetupOpts = {}) { historyUpserts, execLog, poolerFallbackCalls, + dumpCalls, get edgeRunCount() { return edgeRunCount; }, @@ -422,42 +465,177 @@ describe("legacy db pull", () => { }).pipe(Effect.provide(s.layer)); }); - it.effect("an initial pull with no local migrations delegates the dump to Go (migra)", () => { - const s = setup(tmp.current, { remoteVersions: [] }); - return Effect.gen(function* () { - yield* legacyDbPull(flags()); - expect(s.proxyCalls).toHaveLength(1); - expect(s.proxyCalls[0]?.args[0]).toBe("db"); - expect(s.proxyCalls[0]?.args[1]).toBe("pull"); - expect(s.proxyCalls[0]?.env).toEqual({ SUPABASE_TELEMETRY_DISABLED: "1" }); - }).pipe(Effect.provide(s.layer)); - }); + it.effect( + "an initial pull (no local migrations, migra) dumps the schema natively then appends the diff", + () => { + // Go's `run` → `dumpRemoteSchema` (pg_dump, now native) + `diffRemoteSchema(nil)` + // appended (`pull.go:117-141`). No Go delegation. + const s = setup(tmp.current, { + remoteVersions: [], + dumpStdout: "create table dumped ();\n", + edgeStdout: "create table diffed ();\n", // the migra second pass + yes: true, + }); + return Effect.gen(function* () { + yield* legacyDbPull(flags()); + expect(s.proxyCalls).toHaveLength(0); + expect(s.proxyCaptureCalls).toHaveLength(0); + // pg_dump ran with the schema-dump env (internal-schema exclude + comment strip). + expect(s.dumpCalls).toHaveLength(1); + expect(s.dumpCalls[0]?.env["EXTRA_SED"]).toBe("/^--/d"); + expect(s.dumpCalls[0]?.env["EXCLUDED_SCHEMAS"]).toContain("auth"); + // The diff ran against the shadow with the migra engine (no schema filter). + expect(s.provisionCalls[0]?.usePgDelta).toBe(false); + // The migration file holds the dump output followed by the appended diff. + const dir = join(tmp.current, "supabase", "migrations"); + const file = readdirSync(dir).find((f) => f.endsWith("_remote_schema.sql")); + expect(file).toBeDefined(); + const content = readFileSync(join(dir, file ?? ""), "utf8"); + expect(content).toContain("create table dumped ();"); + expect(content).toContain("create table diffed ();"); + expect(content.indexOf("dumped")).toBeLessThan(content.indexOf("diffed")); + // stderr order: dump → shadow → diff → written. + const err = streamText(s.out, "stderr"); + expect(err).toContain("Dumping schema from remote database..."); + expect(err).toContain("Creating shadow database..."); + expect(err).toContain("Schema written to"); + expect(err.indexOf("Dumping schema")).toBeLessThan(err.indexOf("Creating shadow")); + expect(s.historyUpserts.length).toBe(1); + }).pipe(Effect.provide(s.layer)); + }, + ); - it.effect("an initial pull in json mode emits a structured envelope (delegated output)", () => { - // Regression: the initial-migra delegate inherited stdout and returned without - // output.success, so machine-mode callers got the Go child's human output - // instead of a JSON envelope (CLI-1546). Now the child's stdout is captured and - // a structured payload is emitted instead. + it.effect("an initial pull in json mode emits a native structured envelope", () => { const s = setup(tmp.current, { format: "json", remoteVersions: [], - delegateStdout: "Schema written to supabase/migrations/x.sql\n", + dumpStdout: "create table dumped ();\n", + edgeStdout: "create table diffed ();\n", }); return Effect.gen(function* () { yield* legacyDbPull(flags()); expect(s.proxyCalls).toHaveLength(0); - expect(s.proxyCaptureCalls).toHaveLength(1); - // The delegated child runs with a non-TTY stdin so its history-update prompt - // takes Go's default (true) without blocking the JSON caller; the child then - // updates the history, so the envelope reports remoteHistoryUpdated: true. - expect(s.proxyCaptureCalls[0]?.stdin).toBe("ignore"); + expect(s.proxyCaptureCalls).toHaveLength(0); const success = s.out.messages.find((m) => m.type === "success"); + // Machine mode never prompts, so history is updated on Go's default (true); + // `schemaWritten` is the real native migration path (not null as when delegated). expect(success?.data).toMatchObject({ declarative: false, - schemaWritten: null, remoteHistoryUpdated: true, engine: "migra", }); + const data = success?.data as { schemaWritten?: string } | undefined; + expect(data?.schemaWritten).toMatch(/_remote_schema\.sql$/u); + }).pipe(Effect.provide(s.layer)); + }); + + it.effect("an initial pull swallows an empty migra diff once the dump wrote content", () => { + // Go's `swallowInitialInSync` (`pull.go:256-261`): after the pg_dump seed, an + // empty second pass is success, not "in sync". + const s = setup(tmp.current, { + remoteVersions: [], + dumpStdout: "create table dumped ();\n", + edgeStdout: "", // empty migra diff + yes: true, + }); + return Effect.gen(function* () { + const exit = yield* legacyDbPull(flags()).pipe(Effect.exit); + expect(Exit.isSuccess(exit)).toBe(true); + const dir = join(tmp.current, "supabase", "migrations"); + const file = readdirSync(dir).find((f) => f.endsWith("_remote_schema.sql")); + expect(file).toBeDefined(); + expect(readFileSync(join(dir, file ?? ""), "utf8")).toContain("create table dumped ();"); + expect(streamText(s.out, "stderr")).toContain("Schema written to"); + }).pipe(Effect.provide(s.layer)); + }); + + it.effect("an initial pull with an empty schema reports 'No schema changes found'", () => { + // Go's `ensureMigrationWritten` (`pull.go:68,263-268`): an empty dump + empty diff + // leaves the file empty → in sync. + const s = setup(tmp.current, { remoteVersions: [], dumpStdout: "", edgeStdout: "" }); + return Effect.gen(function* () { + const error = yield* legacyDbPull(flags()).pipe(Effect.flip); + expect(error.message).toBe("No schema changes found"); + }).pipe(Effect.provide(s.layer)); + }); + + it.effect( + "an initial-pull direct write that IPv6-fails then an empty pooler retry reports 'No schema changes found'", + () => { + // Regression: the direct attempt streams preamble bytes then drops over IPv6; + // the pooler retry succeeds empty. Go truncates the file before the retry + // (`resetOutput`, pooler_fallback.go:98-113) and decides in-sync from the file + // on disk (`hasMigrationContent`, pull.go:251-268), so an empty pooler retry + + // empty diff is in sync — not a schema write + migration-history upsert. The + // sticky `seedWroteBytes` flag must therefore reset per attempt. + const s = setup(tmp.current, { + remoteVersions: [], + dumpFailFirstWith: "could not translate host name: network is unreachable", + dumpFailFirstPartialBytes: "-- partial preamble\n", + dumpStdout: "", // pooler retry streams nothing + edgeStdout: "", // empty migra diff + poolerAvailable: true, + yes: true, + }); + return Effect.gen(function* () { + const error = yield* legacyDbPull(flags()).pipe(Effect.flip); + expect(error.message).toBe("No schema changes found"); + expect(s.dumpCalls).toHaveLength(2); // direct attempt + pooler retry + expect(s.historyUpserts).toHaveLength(0); // no migration-history row written + }).pipe(Effect.provide(s.layer)); + }, + ); + + it.effect("an initial pull fails when the pg_dump container exits non-zero", () => { + const s = setup(tmp.current, { + remoteVersions: [], + dumpExitCode: 1, + dumpStderr: "connection refused", + }); + return Effect.gen(function* () { + const error = yield* legacyDbPull(flags()).pipe(Effect.flip); + expect(error.message).toContain("error running container: exit 1"); + // The diff pass never ran — the dump failure aborts before provisioning a shadow. + expect(s.provisionCalls).toHaveLength(0); + }).pipe(Effect.provide(s.layer)); + }); + + it.effect("an initial-pull dump retries via the IPv4 pooler on an IPv6 failure", () => { + // Go's `dump.RunWithPoolerFallback`: a `--linked` direct-host dump that fails over + // IPv6 retries once through the transaction pooler (`pull.go:155`). + const s = setup(tmp.current, { + remoteVersions: [], + dumpFailFirstWith: "could not translate host name: network is unreachable", + dumpStdout: "create table dumped ();\n", + edgeStdout: "create table diffed ();\n", + poolerAvailable: true, + yes: true, + }); + return Effect.gen(function* () { + yield* legacyDbPull(flags()); + expect(s.dumpCalls).toHaveLength(2); // direct attempt + pooler retry + expect(s.poolerFallbackCalls).toHaveLength(1); + const err = streamText(s.out, "stderr"); + expect(err).toContain("does not support IPv6"); + expect(err).toContain("Retrying via the IPv4 connection pooler"); + // The "Dumping schema…" line is printed once (before the fallback), not re-printed + // on the pooler retry (Go's `PoolerFallbackConfig` only emits the warning). + expect(err.match(/Dumping schema from remote database/gu)).toHaveLength(1); + }).pipe(Effect.provide(s.layer)); + }); + + it.effect("an initial-pull IPv6 dump failure with no pooler surfaces the dump error", () => { + const s = setup(tmp.current, { + remoteVersions: [], + dumpExitCode: 1, + dumpStderr: "could not translate host name: network is unreachable", + poolerAvailable: false, + }); + return Effect.gen(function* () { + const error = yield* legacyDbPull(flags()).pipe(Effect.flip); + expect(error.message).toContain("error running container: exit 1"); + expect(s.poolerFallbackCalls).toHaveLength(1); // gate checked, no pooler resolved + expect(streamText(s.out, "stderr")).not.toContain("Retrying via the IPv4 connection pooler"); }).pipe(Effect.provide(s.layer)); }); @@ -556,6 +734,45 @@ describe("legacy db pull", () => { }).pipe(Effect.provide(s.layer)); }); + it.effect("updates history on an empty non-interactive stdin (Go default)", () => { + // Go's `PromptYesNo` scans stdin and only falls back to the default (`true`) when + // the scan is empty/exhausted (`console.go:64-82`). With no piped input a + // non-interactive `db pull` therefore proceeds to update the remote history. + // (The production clack prompt would hang on a non-TTY — that no-hang behavior is + // proven end-to-end in `pull.live.test.ts`; here the empty piped scan defaults.) + seedMigration(tmp.current, "20240101000000"); + const s = setup(tmp.current, { + remoteVersions: ["20240101000000"], + edgeStdout: "create table remote ();\n", + stdinIsTty: false, + }); + return Effect.gen(function* () { + yield* legacyDbPull(flags()); + expect(s.historyUpserts.length).toBe(1); + }).pipe(Effect.provide(s.layer)); + }); + + it.effect("declines the history update on a piped 'n' (non-tty)", () => { + // Regression: Go scans piped stdin before defaulting (`console.go:74-82`), so a + // piped `n` cancels the history update even on a non-terminal — `schema_migrations` + // must not be touched against the user's explicit decline. + seedMigration(tmp.current, "20240101000000"); + const s = setup(tmp.current, { + remoteVersions: ["20240101000000"], + edgeStdout: "create table remote ();\n", + stdinIsTty: false, + pipedAnswers: ["n"], + }); + return Effect.gen(function* () { + yield* legacyDbPull(flags()); + expect(s.historyUpserts.length).toBe(0); + // Go prints the label then echoes the consumed answer (`console.go:96-102`). + expect(streamText(s.out, "stderr")).toContain( + "Update remote migration history table? [Y/n] n", + ); + }).pipe(Effect.provide(s.layer)); + }); + it.effect("emits a json envelope and suppresses 'Finished' in machine mode", () => { seedMigration(tmp.current, "20240101000000"); const s = setup(tmp.current, { @@ -587,6 +804,134 @@ describe("legacy db pull", () => { }).pipe(Effect.provide(s.layer)); }); + it.effect("honors SUPABASE_YES for the initial-pull history update", () => { + // Go's `PromptYesNo` reads `viper.GetBool("YES")`, which includes the + // `SUPABASE_YES` env var (AutomaticEnv), so it auto-confirms even on a TTY with + // no piped answer. The native path resolves `yes` via `legacyResolveYesWithProjectEnv`, + // not the raw `--yes` flag, so the shell env var is honored here too. + const prev = process.env["SUPABASE_YES"]; + process.env["SUPABASE_YES"] = "1"; + seedMigration(tmp.current, "20240101000000"); + const s = setup(tmp.current, { + remoteVersions: ["20240101000000"], + edgeStdout: "create table remote ();\n", + // A TTY with no scripted prompt response: only SUPABASE_YES makes this pass. + stdinIsTty: true, + }); + return Effect.gen(function* () { + yield* legacyDbPull(flags()); + expect(s.historyUpserts.length).toBe(1); + expect(streamText(s.out, "stderr")).toContain( + "Update remote migration history table? [Y/n] y", + ); + }).pipe( + Effect.ensuring( + Effect.sync(() => { + if (prev === undefined) delete process.env["SUPABASE_YES"]; + else process.env["SUPABASE_YES"] = prev; + }), + ), + Effect.provide(s.layer), + ); + }); + + it.effect("honors SUPABASE_YES from supabase/.env for the initial-pull history update", () => { + // Go loads the project `.env` (loadNestedEnv) inside ParseDatabaseConfig before + // PromptYesNo (config.go:701), so `SUPABASE_YES` set only in `supabase/.env` + // auto-confirms — with no shell env or `--yes`. The native path resolves via + // `legacyResolveYesWithProjectEnv`, reading the loaded project env map. + const prev = process.env["SUPABASE_YES"]; + delete process.env["SUPABASE_YES"]; // only the project .env value must apply + seedMigration(tmp.current, "20240101000000"); + writeFileSync(join(tmp.current, "supabase", ".env"), "SUPABASE_YES=true\n"); + const s = setup(tmp.current, { + remoteVersions: ["20240101000000"], + edgeStdout: "create table remote ();\n", + // Pipe `n` on a non-TTY: only honoring the .env SUPABASE_YES (which is read + // before stdin, so it wins over the piped decline) still updates history. + stdinIsTty: false, + pipedAnswers: ["n"], + }); + return Effect.gen(function* () { + yield* legacyDbPull(flags()); + expect(s.historyUpserts.length).toBe(1); + }).pipe( + Effect.ensuring( + Effect.sync(() => { + if (prev === undefined) delete process.env["SUPABASE_YES"]; + else process.env["SUPABASE_YES"] = prev; + }), + ), + Effect.provide(s.layer), + ); + }); + + it.effect( + "resolves the pg_dump image via SUPABASE_INTERNAL_IMAGE_REGISTRY from supabase/.env", + () => { + // Go's LoadConfig applies the project `.env` (os.Setenv) before GetRegistryImageUrl, + // so a registry mirror set only in `supabase/.env` is used for the native pg_dump + // seed. The handler mirrors that with `legacyApplyProjectEnv` (scoped to the run, + // reverted on close); the loader itself stays pure. + const prev = process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"]; + delete process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"]; + mkdirSync(join(tmp.current, "supabase"), { recursive: true }); + writeFileSync( + join(tmp.current, "supabase", ".env"), + "SUPABASE_INTERNAL_IMAGE_REGISTRY=my-mirror.example.com\n", + ); + const s = setup(tmp.current, { + remoteVersions: [], // no remote history → initial-migra pg_dump path + dumpStdout: "create table dumped ();\n", + edgeStdout: "", + yes: true, + }); + return Effect.gen(function* () { + yield* legacyDbPull(flags()); + expect(s.dumpCalls.length).toBeGreaterThanOrEqual(1); + // The pg_dump container image is rewritten to the configured mirror. + expect(s.dumpCalls[0]?.image).toMatch(/^my-mirror\.example\.com\/supabase\//u); + }).pipe( + Effect.ensuring( + Effect.sync(() => { + if (prev === undefined) delete process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"]; + else process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"] = prev; + }), + ), + Effect.provide(s.layer), + ); + }, + ); + + it.effect("an explicit --yes=false overrides SUPABASE_YES and honors the piped answer", () => { + // Go binds `--yes` to viper, so an explicit `--yes=false` wins over the + // SUPABASE_YES env (AutomaticEnv). `printf 'n\n' | SUPABASE_YES=1 supabase + // --yes=false db pull` must let the piped `n` decline the history update rather + // than auto-confirming — schema_migrations stays untouched. + const prev = process.env["SUPABASE_YES"]; + process.env["SUPABASE_YES"] = "1"; + seedMigration(tmp.current, "20240101000000"); + const s = setup(tmp.current, { + remoteVersions: ["20240101000000"], + edgeStdout: "create table remote ();\n", + stdinIsTty: false, + pipedAnswers: ["n"], + args: ["db", "pull", "--yes=false"], + }); + return Effect.gen(function* () { + yield* legacyDbPull(flags()); + expect(s.historyUpserts.length).toBe(0); + }).pipe( + Effect.ensuring( + Effect.sync(() => { + if (prev === undefined) delete process.env["SUPABASE_YES"]; + else process.env["SUPABASE_YES"] = prev; + }), + ), + Effect.provide(s.layer), + ); + }); + it.effect("SUPABASE_EXPERIMENTAL delegates the structured-dump pull to Go", () => { const s = setup(tmp.current); return Effect.gen(function* () { diff --git a/apps/cli/src/legacy/commands/db/pull/pull.layers.ts b/apps/cli/src/legacy/commands/db/pull/pull.layers.ts index 0e298b8bc0..821fd07acd 100644 --- a/apps/cli/src/legacy/commands/db/pull/pull.layers.ts +++ b/apps/cli/src/legacy/commands/db/pull/pull.layers.ts @@ -11,6 +11,7 @@ import { legacyIdentityStitchLayer } from "../../../shared/legacy-identity-stitc import { legacyLinkedDbResolverRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; import { legacyPgDeltaSslProbeLayer } from "../../../shared/legacy-pgdelta-ssl-probe.layer.ts"; import { legacyTelemetryStateLayer } from "../../../telemetry/legacy-telemetry-state.layer.ts"; +import { stdinLayer } from "../../../../shared/runtime/stdin.layer.ts"; import { legacyDeclarativeSeamLayer } from "../shared/legacy-pgdelta.seam.layer.ts"; /** @@ -47,4 +48,5 @@ export const legacyDbPullRuntimeLayer = Layer.mergeAll( legacyTelemetryStateLayer, legacyLinkedDbResolverRuntimeLayer(["db", "pull"]).pipe(Layer.provide(legacyIdentityStitchLayer)), commandRuntimeLayer(["db", "pull"]), + stdinLayer, ); diff --git a/apps/cli/src/legacy/commands/db/pull/pull.live.test.ts b/apps/cli/src/legacy/commands/db/pull/pull.live.test.ts new file mode 100644 index 0000000000..320d274d60 --- /dev/null +++ b/apps/cli/src/legacy/commands/db/pull/pull.live.test.ts @@ -0,0 +1,69 @@ +import { existsSync, mkdtempSync, readdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; + +import { + describeLiveDataPlane, + requireLiveProjectRef, + runSupabaseLive, +} from "../../../../../tests/helpers/live.ts"; + +const LIVE_TIMEOUT_MS = 300_000; + +// A fresh, isolated temp workdir so the CLI writes migrations there and never +// touches the repo tree. The provisioned project ref is supplied to `--linked` via +// the `SUPABASE_PROJECT_ID` env var — that is the `--linked` resolver chain in both +// Go and the legacy port (flag → `SUPABASE_PROJECT_ID` → `supabase/.temp/project-ref`); +// `config.toml`'s `project_id` is NOT consulted for `--linked`. +function tempWorkdir(): string { + return mkdtempSync(join(tmpdir(), "sb-db-pull-live-")); +} + +// Data-plane: needs a provisioned project whose database is routable (the +// cli-e2e-ci Linux runner). `describeLiveDataPlane` runs this only when the project +// instance is ACTIVE_HEALTHY, so a control-plane-only stack (ref set but the DB +// unreachable, e.g. local macOS or the current cli-e2e-ci control-plane case) is +// skipped rather than timing out on the pg_dump seed. +describeLiveDataPlane("supabase db pull (live)", () => { + test( + "initial pull from the linked project (native pg_dump seed + migra diff)", + { timeout: LIVE_TIMEOUT_MS }, + async () => { + const ref = requireLiveProjectRef(); + const dir = tempWorkdir(); + try { + const { stdout, stderr, exitCode } = await runSupabaseLive(["db", "pull", "--linked"], { + cwd: dir, + env: { SUPABASE_PROJECT_ID: ref }, + exitTimeoutMs: LIVE_TIMEOUT_MS - 20_000, + // Decline the "Update remote migration history table?" prompt with a piped + // `n`: this project ref is shared across live runs, and writing a + // `schema_migrations` row here would make a later run see it as an extra + // remote migration and fail with a history conflict before pulling. The + // piped answer also exercises the native prompt's stdin scanning end to end. + stdin: "n\n", + }); + const combined = `${stdout}${stderr}`; + expect(combined).not.toContain("Unauthorized"); + // No local migrations → the native initial-migra path runs: pg_dump the remote + // schema, then append the migra diff. Assert on the durable side effect: a + // provisioned project with schema writes a `_remote_schema.sql` + // migration; a fresh empty schema reports "No schema changes found". Either + // proves the path ran end to end against the real database without hanging. + const migDir = join(dir, "supabase", "migrations"); + const wroteMigration = + existsSync(migDir) && readdirSync(migDir).some((f) => f.endsWith("_remote_schema.sql")); + expect(wroteMigration || combined.includes("No schema changes found")).toBe(true); + // The native path creates the migration file BEFORE pg_dump runs, so a failed + // dump/diff could leave a stray file behind — a written migration is only + // meaningful if the command actually succeeded. + if (wroteMigration) { + expect(exitCode).toBe(0); + } + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }, + ); +}); diff --git a/apps/cli/src/legacy/commands/db/dump/dump.env.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.env.ts similarity index 98% rename from apps/cli/src/legacy/commands/db/dump/dump.env.ts rename to apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.env.ts index 8c83a06102..770aca703a 100644 --- a/apps/cli/src/legacy/commands/db/dump/dump.env.ts +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.env.ts @@ -3,8 +3,8 @@ import type { LegacyPgConnInput } from "../../../shared/legacy-db-connection.ser /** * Pure pg_dump environment builders, ported 1:1 from Go's `pkg/migration/dump.go`. * No Effect or service dependencies, so the schema/role/config lists and the - * `os.Expand` dry-run expansion stay unit-testable in isolation. Promote to - * `legacy/shared/` if `db diff` / `db pull` ever need the same env builders. + * `os.Expand` dry-run expansion stay unit-testable in isolation. Shared by the + * `db` command family (`db dump`, and `db pull`'s initial-migra schema dump). */ /** `migration.InternalSchemas` (`pkg/migration/dump.go:18-49`). Used by schema dumps. */ diff --git a/apps/cli/src/legacy/commands/db/dump/dump.env.unit.test.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.env.unit.test.ts similarity index 98% rename from apps/cli/src/legacy/commands/db/dump/dump.env.unit.test.ts rename to apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.env.unit.test.ts index 4ff33a2d9f..1488772664 100644 --- a/apps/cli/src/legacy/commands/db/dump/dump.env.unit.test.ts +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.env.unit.test.ts @@ -15,12 +15,12 @@ import { legacyQuoteUpperCase, legacyToDumpEnv, type LegacyDumpOptions, -} from "./dump.env.ts"; +} from "./legacy-pg-dump.env.ts"; import { legacyDumpDataScript, legacyDumpRoleScript, legacyDumpSchemaScript, -} from "./dump.scripts.ts"; +} from "./legacy-pg-dump.scripts.ts"; const CONN: LegacyPgConnInput = { host: "db.example.supabase.co", diff --git a/apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.run.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.run.ts new file mode 100644 index 0000000000..5d6918bddd --- /dev/null +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.run.ts @@ -0,0 +1,54 @@ +import { Effect, Option } from "effect"; + +import { LegacyNetworkIdFlag } from "../../../../shared/legacy/global-flags.ts"; +import { RuntimeInfo } from "../../../../shared/runtime/runtime-info.service.ts"; +import { legacyGetRegistryImageUrl } from "../../../shared/legacy-docker-registry.ts"; +import { LegacyDockerRun } from "../../../shared/legacy-docker-run.service.ts"; + +/** + * Runs a pg_dump / pg_dumpall bash script in a one-shot container, streaming its + * stdout chunk-by-chunk to `onStdout` and teeing stderr live, returning the exit + * code + captured stderr for failure classification. Mirrors Go's `dockerExec` + * (`apps/cli-go/internal/db/dump/dump.go`): host networking by default (overridden + * by the global `--network-id`), no security-opt, and the Linux-only + * `host.docker.internal:host-gateway` extra host. + * + * Shared by `db dump` (streams to `--file`/stdout) and `db pull`'s initial-migra + * schema dump (streams to the migration file). The pooler-fallback *decision* + * stays with the caller — this helper runs a single attempt and surfaces its + * exit/stderr so the caller can classify with `legacyIsIPv6ConnectivityError`. + */ +export const legacyStreamPgDump = Effect.fnUntraced(function* (params: { + /** Resolved Postgres image tag (pre-registry-URL); the helper applies the registry mirror. */ + readonly image: string; + /** The bash pg_dump/pg_dumpall script (`legacyDump{Schema,Data,Role}Script`). */ + readonly script: string; + readonly env: Readonly>; + /** Receives each stdout chunk in arrival order; its failure aborts the run as `E`. */ + readonly onStdout: (chunk: Uint8Array) => Effect.Effect; +}) { + const docker = yield* LegacyDockerRun; + const runtimeInfo = yield* RuntimeInfo; + const networkIdFlag = yield* LegacyNetworkIdFlag; + + const networkId = Option.getOrUndefined(networkIdFlag); + const network = + networkId !== undefined && networkId.length > 0 + ? { _tag: "named" as const, name: networkId } + : { _tag: "host" as const }; + const extraHosts = runtimeInfo.platform === "linux" ? ["host.docker.internal:host-gateway"] : []; + + return yield* docker.runStream( + { + image: legacyGetRegistryImageUrl(params.image), + cmd: ["bash", "-c", params.script, "--"], + env: params.env, + binds: [], + workingDir: Option.none(), + securityOpt: [], + extraHosts, + network, + }, + { onStdout: params.onStdout, teeStderr: true }, + ); +}); diff --git a/apps/cli/src/legacy/commands/db/dump/dump.scripts.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.scripts.ts similarity index 96% rename from apps/cli/src/legacy/commands/db/dump/dump.scripts.ts rename to apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.scripts.ts index cf9659adcf..48dd02fbf7 100644 --- a/apps/cli/src/legacy/commands/db/dump/dump.scripts.ts +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pg-dump.scripts.ts @@ -1,6 +1,6 @@ // Verbatim copies of the Go pg_dump scripts (`apps/cli-go/pkg/migration/scripts/`). -// These embed the dump pipelines byte-for-byte; `dump.scripts.unit.test.ts` asserts -// equality against the Go `.sh` sources. Do not hand-edit — regenerate from Go. +// These embed the dump pipelines byte-for-byte; `legacy-pg-dump.env.unit.test.ts` +// asserts equality against the Go `.sh` sources. Do not hand-edit — regenerate from Go. export const legacyDumpSchemaScript = '#!/usr/bin/env bash\nset -euo pipefail\n\nexport PGHOST="$PGHOST"\nexport PGPORT="$PGPORT"\nexport PGUSER="$PGUSER"\nexport PGPASSWORD="$PGPASSWORD"\nexport PGDATABASE="$PGDATABASE"\n\n# Explanation of pg_dump flags:\n#\n# --schema-only omit data like migration history, pgsodium key, etc.\n# --exclude-schema omit internal schemas as they are maintained by platform\n#\n# Explanation of sed substitutions:\n#\n# - do not emit psql meta commands\n# - do not alter superuser role "supabase_admin"\n# - do not alter foreign data wrappers owner\n# - do not include ACL changes on internal schemas\n# - do not include RLS policies on cron extension schema\n# - do not include event triggers\n# - do not create pgtle schema and extension comments\n# - do not create publication "supabase_realtime"\n# - do not set transaction_timeout which requires pg17\npg_dump \\\n --schema-only \\\n --quote-all-identifier \\\n --role "postgres" \\\n --exclude-schema "${EXCLUDED_SCHEMAS:-}" \\\n ${EXTRA_FLAGS:-} \\\n| sed -E \'s/^\\\\(un)?restrict .*$/-- &/\' \\\n| sed -E \'s/^CREATE SCHEMA "/CREATE SCHEMA IF NOT EXISTS "/\' \\\n| sed -E \'s/^CREATE TABLE "/CREATE TABLE IF NOT EXISTS "/\' \\\n| sed -E \'s/^CREATE SEQUENCE "/CREATE SEQUENCE IF NOT EXISTS "/\' \\\n| sed -E \'s/^CREATE VIEW "/CREATE OR REPLACE VIEW "/\' \\\n| sed -E \'s/^CREATE FUNCTION "/CREATE OR REPLACE FUNCTION "/\' \\\n| sed -E \'s/^CREATE TRIGGER "/CREATE OR REPLACE TRIGGER "/\' \\\n| sed -E \'s/^CREATE PUBLICATION "supabase_realtime/-- &/\' \\\n| sed -E \'s/^CREATE EVENT TRIGGER /-- &/\' \\\n| sed -E \'s/^ WHEN TAG IN /-- &/\' \\\n| sed -E \'s/^ EXECUTE FUNCTION /-- &/\' \\\n| sed -E \'s/^ALTER EVENT TRIGGER /-- &/\' \\\n| sed -E \'s/^ALTER PUBLICATION "supabase_realtime_/-- &/\' \\\n| sed -E \'s/^ALTER FOREIGN DATA WRAPPER (.+) OWNER TO /-- &/\' \\\n| sed -E \'s/^ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_admin"/-- &/\' \\\n| sed -E \'s/^GRANT ALL ON FOREIGN DATA WRAPPER (.+) TO "postgres" WITH GRANT OPTION/-- &/\' \\\n| sed -E "s/^GRANT (.+) ON (.+) \\"(${EXCLUDED_SCHEMAS:-})\\"/-- &/" \\\n| sed -E "s/^REVOKE (.+) ON (.+) \\"(${EXCLUDED_SCHEMAS:-})\\"/-- &/" \\\n| sed -E \'s/^(CREATE EXTENSION IF NOT EXISTS "pg_tle").+/\\1;/\' \\\n| sed -E \'s/^(CREATE EXTENSION IF NOT EXISTS "pgsodium").+/\\1;/\' \\\n| sed -E \'s/^(CREATE EXTENSION IF NOT EXISTS "pgmq").+/\\1;/\' \\\n| sed -E \'s/^COMMENT ON EXTENSION (.+)/-- &/\' \\\n| sed -E \'s/^CREATE POLICY "cron_job_/-- &/\' \\\n| sed -E \'s/^ALTER TABLE "cron"/-- &/\' \\\n| sed -E \'s/^SET transaction_timeout = 0;/-- &/\' \\\n| sed -E "${EXTRA_SED:-}"\n'; diff --git a/apps/cli/src/legacy/commands/db/shared/legacy-pooler-fallback.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pooler-fallback.ts new file mode 100644 index 0000000000..18b7d819ad --- /dev/null +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pooler-fallback.ts @@ -0,0 +1,118 @@ +import { Effect, Option } from "effect"; + +import { Output } from "../../../../shared/output/output.service.ts"; +import { legacyYellow } from "../../../shared/legacy-colors.ts"; +import { legacyIsIPv6ConnectivityError } from "../../../shared/legacy-connect-errors.ts"; +import type { LegacyPgConnInput } from "../../../shared/legacy-db-connection.service.ts"; +import type { LegacyDbConnType } from "../../../shared/legacy-db-target-flags.ts"; + +/** The exit/stderr pair a dump attempt surfaces for pooler-fallback classification. */ +interface LegacyPoolerFallbackResult { + readonly exitCode: number; + readonly stderr: string; +} + +/** + * Go's `PoolerFallbackConfig` host gate (`internal/db/dump/pooler_fallback.go:82-96`): + * a dump/diff is only rerouted through the pooler when it was a `--linked` run against + * a *direct* Supabase DB host (`db..`, never local/pooler). + * `ProjectRefFromDirectDbHost` already excludes local, and `PoolerFallbackEligible == + * linked` makes local impossible — the `!isLocal` check is belt-and-braces. Shared by + * the result-based dump/pull retry ({@link legacyRunWithPoolerFallback}) and the + * error-based diff retry in `db pull`; each ANDs in its own IPv6 classification of the + * relevant stderr/error. + */ +export const legacyIsDirectLinkedHost = (params: { + readonly connType: LegacyDbConnType; + readonly host: string; + readonly isLocal: boolean; + readonly projectHost: string; +}): boolean => + params.connType === "linked" && + !params.isLocal && + params.host.startsWith("db.") && + params.host.endsWith(`.${params.projectHost}`); + +/** + * Go's IPv6 pooler-fallback warning (`internal/utils/connect.go:283-289`), to stderr, + * `Yellow`-wrapped, byte-for-byte. Emitted just before the IPv4 pooler retry by every + * pooler-fallback path. + */ +export const legacyEmitPoolerFallbackWarning = (host: string): Effect.Effect => + Effect.gen(function* () { + const output = yield* Output; + yield* output.raw( + `${legacyYellow( + `Warning: Direct connection to ${host} is unavailable because this environment does not support IPv6.\nRetrying via the IPv4 connection pooler.`, + )}\n`, + "stderr", + ); + }); + +/** + * Container-level IPv6 → IPv4-pooler retry shared by `db dump` and `db pull`'s initial + * remote-schema dump — the single port of Go's `RunWithPoolerFallback` + * (`internal/db/dump/pooler_fallback.go:31-66`). Runs the first attempt's `result` + * through the host gate + IPv6 classification; when eligible and a pooler connection + * resolves, emits Go's warning and retries once via `runWithConn`, returning the + * retry's result. Otherwise returns the original `result` unchanged, so the caller's + * failure classification reads the correct stderr in both cases (Go returns the retry + * error on a failed retry, the original error on no fallback). + * + * The one load-bearing per-command difference is `reprintOnRetry`: Go prints the + * "Dumping ..." line *inside* `db dump`'s run closure, so it re-prints on the retry + * (`dump.go:39-45`); `db pull` prints it once *before* `RunWithPoolerFallback` + * (`pull.go:146`), so it does not. Callers pass the re-print effect (`db dump`) or + * `Effect.void` (`db pull`). + * + * Two Go behaviours are intentionally *not* reproduced here (pre-existing TS gaps, + * unchanged by this hoist): the `resetOutput` truncation between attempts (each caller + * (re)truncates its own file per attempt / streams to stdout) and the + * `SuggestIPv6Pooler` URL enrichment on the no-fallback hint (callers fall back to the + * generic `legacyIpv6Suggestion`). + */ +export const legacyRunWithPoolerFallback = Effect.fnUntraced(function* (params: { + /** The first attempt's result; returned unchanged when no fallback fires. */ + readonly result: LegacyPoolerFallbackResult; + readonly connType: LegacyDbConnType; + /** The direct connection host that failed (`resolved.conn.host`). */ + readonly host: string; + readonly isLocal: boolean; + /** `cliConfig.projectHost` — the direct-DB-host suffix (`supabase.co`/`.red`). */ + readonly projectHost: string; + /** + * Resolves the IPv4 pooler connection, already error-neutralised to `None` (the + * caller pipes `resolver.resolvePoolerFallback(...)` through `orElseSucceed(None)`) — + * Go treats any fallback-resolution error as "no fallback" and surfaces the original + * dump failure. A **thunk**: Go only resolves the pooler (creating a temp role) once + * the error is eligible, so this is invoked only after the gate passes, never on the + * happy path. + */ + readonly resolvePooler: () => Effect.Effect>; + /** Re-runs the dump against a connection (`db dump`/`db pull` each adapt their runner). */ + readonly runWithConn: ( + conn: LegacyPgConnInput, + ) => Effect.Effect; + /** `db dump` re-prints "Dumping ..." on retry; `db pull` passes `Effect.void`. */ + readonly reprintOnRetry: Effect.Effect; +}) { + const eligible = + params.result.exitCode !== 0 && + legacyIsDirectLinkedHost({ + connType: params.connType, + host: params.host, + isLocal: params.isLocal, + projectHost: params.projectHost, + }) && + legacyIsIPv6ConnectivityError(params.result.stderr); + if (!eligible) { + return params.result; + } + const pooler = yield* params.resolvePooler(); + if (Option.isNone(pooler)) { + return params.result; + } + yield* legacyEmitPoolerFallbackWarning(params.host); + yield* params.reprintOnRetry; + return yield* params.runWithConn(pooler.value); +}); diff --git a/apps/cli/src/legacy/commands/logout/logout.handler.ts b/apps/cli/src/legacy/commands/logout/logout.handler.ts index 4539c9fb14..8b4a606249 100644 --- a/apps/cli/src/legacy/commands/logout/logout.handler.ts +++ b/apps/cli/src/legacy/commands/logout/logout.handler.ts @@ -2,8 +2,9 @@ import { Effect } from "effect"; import { LegacyCredentials } from "../../auth/legacy-credentials.service.ts"; import { LegacyTelemetryState } from "../../telemetry/legacy-telemetry-state.service.ts"; -import { LegacyYesFlag } from "../../../shared/legacy/global-flags.ts"; +import { legacyResolveYes } from "../../../shared/legacy/global-flags.ts"; import { Output } from "../../../shared/output/output.service.ts"; +import { legacyPromptYesNo } from "../../shared/legacy-prompt-yes-no.ts"; import { LegacyLogoutCancelledError, LEGACY_LOGOUT_CANCELLED_MESSAGE } from "./logout.errors.ts"; const LOGGED_OUT_MSG = "Access token deleted successfully. You are now logged out."; @@ -12,16 +13,28 @@ export const legacyLogout = Effect.fn("legacy.logout")(function* () { const output = yield* Output; const credentials = yield* LegacyCredentials; const telemetryState = yield* LegacyTelemetryState; - const yes = yield* LegacyYesFlag; + // `--yes` OR `SUPABASE_YES` (Go's `viper.GetBool("YES")`, root.go:318-320): Go + // reads it before scanning stdin, so the env var auto-confirms logout too. + const yes = yield* legacyResolveYes; + + const confirmLabel = + "Do you want to log out? This will remove the access token from your system."; const body = Effect.gen(function* () { - // Confirm prompt, honoring the global `--yes` (`logout.go:15`). - const confirmed = yes - ? true - : yield* output.promptConfirm( - "Do you want to log out? This will remove the access token from your system.", - { defaultValue: false }, - ); + // Confirm prompt, honoring the global `--yes`/`SUPABASE_YES` (`logout.go:15-16`). + const confirmed = yield* Effect.gen(function* () { + if (yes) return true; + // Machine (json/stream-json) mode has no Go equivalent — fail loudly on a + // non-interactive prompt rather than silently defaulting, preserving the + // existing contract. + if (output.format !== "text") { + return yield* output.promptConfirm(confirmLabel, { defaultValue: false }); + } + // Text mode mirrors Go's `PromptYesNo(..., false)` (`logout.go:16`): it scans + // piped stdin before falling back to the default (`console.go:64-82`), so + // `printf 'y\n' | supabase logout` deletes the token. + return yield* legacyPromptYesNo(output, yes, confirmLabel, false); + }); if (!confirmed) { return yield* Effect.fail( new LegacyLogoutCancelledError({ message: LEGACY_LOGOUT_CANCELLED_MESSAGE }), diff --git a/apps/cli/src/legacy/commands/logout/logout.integration.test.ts b/apps/cli/src/legacy/commands/logout/logout.integration.test.ts index 39b3701f36..a1dbbb0b84 100644 --- a/apps/cli/src/legacy/commands/logout/logout.integration.test.ts +++ b/apps/cli/src/legacy/commands/logout/logout.integration.test.ts @@ -1,7 +1,8 @@ import { describe, expect, it } from "@effect/vitest"; import { Effect, Exit, Layer } from "effect"; -import { mockOutput } from "../../../../tests/helpers/mocks.ts"; +import { mockOutput, mockStdin, mockTty } from "../../../../tests/helpers/mocks.ts"; +import { CliArgs } from "../../../shared/cli/cli-args.service.ts"; import { mockLegacyCredentialsTracked, mockLegacyTelemetryStateTracked, @@ -15,6 +16,10 @@ interface SetupOpts { readonly yes?: boolean; readonly deleteOutcome?: "ok" | "notLoggedIn" | "deleteError"; readonly promptConfirmFail?: boolean; + /** stdin interactivity; defaults to a TTY so prompt-driven tests reach the confirm. */ + readonly stdinIsTty?: boolean; + /** Piped (non-TTY) stdin answers, one consumed per confirmation prompt. */ + readonly pipedAnswers?: ReadonlyArray; } function setupLegacyLogout(opts: SetupOpts = {}) { @@ -30,6 +35,12 @@ function setupLegacyLogout(opts: SetupOpts = {}) { credentials.layer, telemetry.layer, Layer.succeed(LegacyYesFlag, opts.yes ?? false), + mockTty({ stdinIsTty: opts.stdinIsTty ?? true, stdoutIsTty: false }), + mockStdin( + opts.stdinIsTty ?? true, + opts.pipedAnswers ? `${opts.pipedAnswers.join("\n")}\n` : undefined, + ), + Layer.succeed(CliArgs, { args: [] }), ); return { layer, out, telemetry, credentials }; } @@ -69,6 +80,53 @@ describe("legacy logout integration", () => { }).pipe(Effect.provide(layer)); }); + it.live("empty non-interactive stdin takes Go's default (false) and cancels", () => { + // Go's `PromptYesNo(..., false)` scans stdin and falls back to the default when + // the scan is empty (`logout.go:16`, `console.go:64-82`). With no piped input + // logout cancels — without hanging on the clack confirm. + const { layer, credentials } = setupLegacyLogout({ stdinIsTty: false }); + return Effect.gen(function* () { + const exit = yield* Effect.exit(legacyLogout()); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + expect(JSON.stringify(exit.cause)).toContain("LegacyLogoutCancelledError"); + } + expect(credentials.deletedAll).toBe(false); + }).pipe(Effect.provide(layer)); + }); + + it.live("honors a piped 'y' on non-interactive stdin and logs out", () => { + // Regression: Go scans piped stdin before defaulting (`console.go:74-82`), so + // `printf 'y\n' | supabase logout` deletes the token even on a non-terminal. + const { layer, credentials } = setupLegacyLogout({ stdinIsTty: false, pipedAnswers: ["y"] }); + return Effect.gen(function* () { + yield* legacyLogout(); + expect(credentials.deletedAll).toBe(true); + }).pipe(Effect.provide(layer)); + }); + + it.live("honors SUPABASE_YES and logs out even when a piped 'n' is present", () => { + // Go reads `viper.GetBool("YES")` (incl. the SUPABASE_YES env var) BEFORE + // scanning stdin (`console.go:71`), so `SUPABASE_YES=1 printf 'n\n' | supabase + // logout` auto-confirms and deletes rather than consuming the piped `n`. The + // handler resolves `yes` via legacyResolveYes, not the raw --yes flag. + const prev = process.env["SUPABASE_YES"]; + process.env["SUPABASE_YES"] = "1"; + const { layer, credentials } = setupLegacyLogout({ stdinIsTty: false, pipedAnswers: ["n"] }); + return Effect.gen(function* () { + yield* legacyLogout(); + expect(credentials.deletedAll).toBe(true); + }).pipe( + Effect.ensuring( + Effect.sync(() => { + if (prev === undefined) delete process.env["SUPABASE_YES"]; + else process.env["SUPABASE_YES"] = prev; + }), + ), + Effect.provide(layer), + ); + }); + it.live("not logged in: prints to stderr, exits 0, and does not sweep credentials", () => { const { layer, out, credentials } = setupLegacyLogout({ yes: true, diff --git a/apps/cli/src/legacy/commands/logout/logout.layers.ts b/apps/cli/src/legacy/commands/logout/logout.layers.ts index a2b44c218d..6498228c50 100644 --- a/apps/cli/src/legacy/commands/logout/logout.layers.ts +++ b/apps/cli/src/legacy/commands/logout/logout.layers.ts @@ -5,6 +5,7 @@ import { legacyCliConfigLayer } from "../../config/legacy-cli-config.layer.ts"; import { legacyDebugLoggerLayer } from "../../shared/legacy-debug-logger.layer.ts"; import { legacyTelemetryStateLayer } from "../../telemetry/legacy-telemetry-state.layer.ts"; import { commandRuntimeLayer } from "../../../shared/runtime/command-runtime.layer.ts"; +import { stdinLayer } from "../../../shared/runtime/stdin.layer.ts"; /** * Lean runtime for `logout`. Like `unlink`, it must NOT use @@ -14,8 +15,10 @@ import { commandRuntimeLayer } from "../../../shared/runtime/command-runtime.lay * * `legacyCliConfigLayer` is provided to `legacyCredentialsLayer` and also exposed * at the top level (`Layer.provide` does not share to siblings inside a merge — - * legacy CLAUDE.md item 5). `Analytics`, `Output`, `Stdio`, `FileSystem`, - * `Path`, `TelemetryRuntime`, and `LegacyYesFlag` come from the root layer. + * legacy CLAUDE.md item 5). `Analytics`, `Output`, `Stdio`, `Tty`, `FileSystem`, + * `Path`, `TelemetryRuntime`, and `LegacyYesFlag` come from the root layer; + * `stdinLayer` (the shared piped-stdin reader for the logout confirm) builds its + * `Stdin` from the root `Stdio`/`Tty`, like the migration runtimes. */ const cliConfig = legacyCliConfigLayer.pipe(Layer.provide(legacyDebugLoggerLayer)); const credentials = legacyCredentialsLayer.pipe( @@ -28,4 +31,5 @@ export const legacyLogoutRuntimeLayer = Layer.mergeAll( cliConfig, legacyTelemetryStateLayer, commandRuntimeLayer(["logout"]), + stdinLayer, ); diff --git a/apps/cli/src/legacy/commands/seed/buckets/buckets.command.ts b/apps/cli/src/legacy/commands/seed/buckets/buckets.command.ts index 20c9b269fe..8fe2122f71 100644 --- a/apps/cli/src/legacy/commands/seed/buckets/buckets.command.ts +++ b/apps/cli/src/legacy/commands/seed/buckets/buckets.command.ts @@ -1,4 +1,4 @@ -import { Effect } from "effect"; +import { Effect, Layer } from "effect"; import { Command } from "effect/unstable/cli"; import { CliArgs } from "../../../../shared/cli/cli-args.service.ts"; @@ -6,6 +6,7 @@ import { withJsonErrorHandling } from "../../../../shared/output/json-error-hand import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; import { LegacySeedLinkedFlag, LegacySeedLocalFlag } from "../seed.flags.ts"; import { legacyAssertSeedTargetsExclusive } from "./buckets.flags.ts"; +import { stdinLayer } from "../../../../shared/runtime/stdin.layer.ts"; import { legacyStorageGatewayRuntimeLayer } from "../../../shared/legacy-storage-runtime.layer.ts"; import { legacySeedBuckets } from "./buckets.handler.ts"; @@ -36,5 +37,7 @@ export const legacyBucketsCommand = Command.make("buckets").pipe( return yield* legacySeedBuckets(flags).pipe(withLegacyCommandInstrumentation({ flags })); }).pipe(withJsonErrorHandling), ), - Command.provide(legacyStorageGatewayRuntimeLayer(["seed", "buckets"])), + Command.provide( + Layer.mergeAll(legacyStorageGatewayRuntimeLayer(["seed", "buckets"]), stdinLayer), + ), ); diff --git a/apps/cli/src/legacy/commands/seed/buckets/buckets.integration.test.ts b/apps/cli/src/legacy/commands/seed/buckets/buckets.integration.test.ts index 674cf9f82e..986fcc4af6 100644 --- a/apps/cli/src/legacy/commands/seed/buckets/buckets.integration.test.ts +++ b/apps/cli/src/legacy/commands/seed/buckets/buckets.integration.test.ts @@ -8,7 +8,7 @@ import { Effect, Exit, Layer, Option } from "effect"; import * as HttpClient from "effect/unstable/http/HttpClient"; import type * as HttpClientError from "effect/unstable/http/HttpClientError"; -import { mockOutput } from "../../../../../tests/helpers/mocks.ts"; +import { mockOutput, mockStdin, mockTty } from "../../../../../tests/helpers/mocks.ts"; import { LEGACY_VALID_REF, legacyJsonResponse, @@ -53,6 +53,8 @@ function setupLegacySeedBuckets( readonly format?: OutputFormat; readonly confirm?: ReadonlyArray; readonly promptConfirmFail?: boolean; + /** Piped (non-TTY) stdin answers, one consumed per confirmation prompt. */ + readonly pipedAnswers?: ReadonlyArray; readonly args?: ReadonlyArray; readonly yes?: boolean; /** Project ref returned by loadProjectRef for --linked tests. */ @@ -181,6 +183,9 @@ function setupLegacySeedBuckets( telemetry.layer, mockLegacyCliConfig({ workdir }), BunServices.layer, + // Seed-bucket prompts model an interactive user answering via `confirm`. + mockTty({ stdinIsTty: true, stdoutIsTty: false }), + mockStdin(true, opts.pipedAnswers ? `${opts.pipedAnswers.join("\n")}\n` : undefined), Layer.succeed(CliArgs, { args: opts.args ?? ["seed", "buckets"] }), Layer.succeed(LegacyYesFlag, opts.yes ?? false), projectRefLayer, diff --git a/apps/cli/src/legacy/commands/storage/rm/rm.command.ts b/apps/cli/src/legacy/commands/storage/rm/rm.command.ts index ebd45bbf79..af97d7f907 100644 --- a/apps/cli/src/legacy/commands/storage/rm/rm.command.ts +++ b/apps/cli/src/legacy/commands/storage/rm/rm.command.ts @@ -1,9 +1,10 @@ -import { Effect } from "effect"; +import { Effect, Layer } from "effect"; import { Argument, Command, Flag } from "effect/unstable/cli"; import { CliArgs } from "../../../../shared/cli/cli-args.service.ts"; import { withJsonErrorHandling } from "../../../../shared/output/json-error-handling.ts"; import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; +import { stdinLayer } from "../../../../shared/runtime/stdin.layer.ts"; import { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts"; import { legacyStorageGatewayRuntimeLayer } from "../../../shared/legacy-storage-runtime.layer.ts"; import { @@ -59,5 +60,5 @@ export const legacyStorageRmCommand = Command.make("rm", config).pipe( }).pipe(withLegacyCommandInstrumentation({ flags: telemetryFlags })); }).pipe(withJsonErrorHandling), ), - Command.provide(legacyStorageGatewayRuntimeLayer(["storage", "rm"])), + Command.provide(Layer.mergeAll(legacyStorageGatewayRuntimeLayer(["storage", "rm"]), stdinLayer)), ); diff --git a/apps/cli/src/legacy/commands/storage/rm/rm.integration.test.ts b/apps/cli/src/legacy/commands/storage/rm/rm.integration.test.ts index cc35dfd5e5..c279b49460 100644 --- a/apps/cli/src/legacy/commands/storage/rm/rm.integration.test.ts +++ b/apps/cli/src/legacy/commands/storage/rm/rm.integration.test.ts @@ -119,6 +119,52 @@ describe("legacy storage rm", () => { }); }); + it.live("honors a piped 'y' on non-TTY stdin and deletes", () => { + // Go scans piped stdin before defaulting (`console.go:74-82`); a piped `y` + // overrides the `n` default and deletes, even on a non-terminal. + const { layer, requests, out } = setupLegacyStorage(tmp.current, { + toml: 'project_id = "test"\n', + local: true, + stdinIsTty: false, + pipedAnswers: ["y"], + routes: [{ method: "DELETE", match: DELETE_OBJECT("private"), body: [{ name: "a.pdf" }] }], + }); + return Effect.gen(function* () { + const exit = yield* legacyStorageRm({ + files: ["ss:///private/a.pdf"], + recursive: false, + linked: true, + local: true, + }).pipe(Effect.provide(layer), Effect.exit); + expect(Exit.isSuccess(exit)).toBe(true); + expect(requests.some((r) => r.method === "DELETE")).toBe(true); + // The consumed answer is echoed after the label (Go's non-TTY `PromptText`). + expect(out.stderrText).toContain("[y/N] y"); + }); + }); + + it.live("falls back to the default (no) on an unparseable piped answer", () => { + // Go's `parseYesNo` returns nil for unrecognized input (`console.go:84-93`), so + // `PromptYesNo` keeps the `n` default and the deletion is skipped. + const { layer, requests } = setupLegacyStorage(tmp.current, { + toml: 'project_id = "test"\n', + local: true, + stdinIsTty: false, + pipedAnswers: ["maybe"], + routes: [{ method: "DELETE", match: DELETE_OBJECT("private"), body: [] }], + }); + return Effect.gen(function* () { + const exit = yield* legacyStorageRm({ + files: ["ss:///private/a.pdf"], + recursive: false, + linked: true, + local: true, + }).pipe(Effect.provide(layer), Effect.exit); + expect(Exit.isSuccess(exit)).toBe(true); + expect(requests.some((r) => r.method === "DELETE")).toBe(false); + }); + }); + it.live("uses the default (no) when non-interactive and skips deletion", () => { const { layer, requests } = setupLegacyStorage(tmp.current, { toml: 'project_id = "test"\n', diff --git a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts index b9a89e6577..0e4bc28028 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.ts @@ -480,17 +480,37 @@ function legacyJoinSupabaseSeedPath(pattern: string): string { const DEFAULT_SUPABASE_ENV = "development"; /** - * Load the project's nested `.env` files into a lookup map, mirroring Go's - * `loadNestedEnv` + `loadDefaultEnv` (`pkg/config/config.go:1047-1085`). Go walks - * from the `supabase/` directory up to the repo root and, in each directory, - * loads `.env..local`, `.env.local` (skipped when `SUPABASE_ENV=test`), - * `.env.`, then `.env` via `godotenv.Load`, which never overrides a value - * already set. So the shell environment wins over the files, the `supabase/` - * directory wins over the repo root, and earlier filenames win within a - * directory. A malformed `.env` — or one that exists but cannot be read — - * aborts: Go's `loadEnvIfExists` swallows only `os.ErrNotExist` and returns - * every other error. The path is named without leaking file contents - * (CWE-209-safe). + * Keys {@link legacyApplyProjectEnv} copies from the project `.env` into + * `process.env`. Kept to an allowlist of values that are read *only* via + * `process.env` (no project-env map path) and must reflect `supabase/.env` — + * currently just `SUPABASE_INTERNAL_IMAGE_REGISTRY` (`legacyGetRegistryImageUrl`). + * Everything else is read from {@link legacyLoadProjectEnv}'s returned map + * (`envLookup`, `legacyResolveYesWithProjectEnv`, `resolveDbPassword`) or resolved + * eagerly from the shell before any `.env` load — Go's root globals (workdir / + * profile / `SUPABASE_ENV` / project-ref) are frozen before `loadNestedEnv`, so + * writing them here would let our lazily-built resolvers diverge from Go (retarget + * the project, switch the env-file set, or leak into the Go `--experimental` proxy). + */ +const LEGACY_PROCESS_ENV_APPLY_KEYS = ["SUPABASE_INTERNAL_IMAGE_REGISTRY"] as const; + +/** + * Load the project's nested `.env` files into a lookup map. **Pure**: it reads the + * files and returns the merged map, with no `process.env` side effect — so the + * `SUPABASE_YES` / `SUPABASE_DB_PASSWORD` readers that call it + * (`legacyResolveYesWithProjectEnv`, `resolveDbPassword`) never mutate the global + * environment. Commands that need an allowlisted key visible to a synchronous + * `process.env` reader (`db dump` / `db pull` → `legacyGetRegistryImageUrl`) opt + * into {@link legacyApplyProjectEnv} around the container work instead. + * + * Partially mirrors Go's `loadNestedEnv` + `loadDefaultEnv` + * (`pkg/config/config.go:1047-1085`). Go walks from the `supabase/` directory up to + * the repo root and, in each directory, loads `.env..local`, `.env.local` + * (skipped when `SUPABASE_ENV=test`), `.env.`, then `.env` via `godotenv.Load`, + * which never overrides a value already set. So the shell environment wins over the + * files, the `supabase/` directory wins over the repo root, and earlier filenames + * win within a directory. A malformed `.env` — or one that exists but cannot be + * read — aborts: Go's `loadEnvIfExists` swallows only `os.ErrNotExist` and returns + * every other error. The path is named without leaking file contents (CWE-209-safe). */ export const legacyLoadProjectEnv = Effect.fnUntraced(function* ( fs: FileSystem.FileSystem, @@ -539,6 +559,42 @@ export const legacyLoadProjectEnv = Effect.fnUntraced(function* ( return loaded; }); +/** + * Apply the allowlisted project-`.env` keys (see {@link LEGACY_PROCESS_ENV_APPLY_KEYS}) + * to `process.env` **for the duration of the current scope**, then revert. This is + * the opt-in counterpart to the pure {@link legacyLoadProjectEnv}: `db dump` / + * `db pull` run it around their pg_dump / diff container work so a + * `SUPABASE_INTERNAL_IMAGE_REGISTRY` set in `supabase/.env` reaches + * `legacyGetRegistryImageUrl` (which reads `process.env` synchronously) — mirroring + * the `os.Setenv` half of Go's `loadNestedEnv`. Kept out of the shared loader so + * SUPABASE_YES / db-password reads stay side-effect-free. + * + * Never overrides an existing `process.env` value (Go's `godotenv.Load` never + * overrides; `loaded` already excludes keys present in `process.env`, and this + * re-checks). The `acquireRelease` finalizer deletes only the keys it set when the + * scope closes, so in-process test workers don't leak env between cases. + */ +export const legacyApplyProjectEnv = (loaded: Record) => + Effect.forEach( + LEGACY_PROCESS_ENV_APPLY_KEYS, + (key) => { + const value = loaded[key]; + if (value === undefined || process.env[key] !== undefined) { + return Effect.void; + } + return Effect.acquireRelease( + Effect.sync(() => { + process.env[key] = value; + }), + () => + Effect.sync(() => { + delete process.env[key]; + }), + ); + }, + { discard: true }, + ); + function nonEmptyString(value: unknown): Option.Option { return typeof value === "string" && value.length > 0 ? Option.some(value) : Option.none(); } diff --git a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.unit.test.ts b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.unit.test.ts index ade3dc5918..986c8a6075 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.toml-read.unit.test.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.toml-read.unit.test.ts @@ -6,6 +6,7 @@ import { describe, expect, it } from "@effect/vitest"; import { Effect, Exit, FileSystem, Option, Path } from "effect"; import { + legacyApplyProjectEnv, legacyLoadProjectEnv, legacyReadDbToml, legacyResolveDeclarativeDir, @@ -1669,6 +1670,93 @@ describe("legacyReadDbToml", () => { }, ); + it.effect("legacyLoadProjectEnv is pure: returns every key and never touches process.env", () => { + // The loader is a pure read: it returns all project-.env keys in the map (config + // env() resolution + the SUPABASE_YES / db-password readers use the map) and does + // NOT mutate process.env. Applying to process.env is the separate, opt-in + // legacyApplyProjectEnv (below), so a mere `load` for SUPABASE_YES has no global + // side effect. + const saved: Record = {}; + for (const k of ["SUPABASE_INTERNAL_IMAGE_REGISTRY", "SUPABASE_PROJECT_ID", "SUPABASE_ENV"]) { + saved[k] = process.env[k]; + delete process.env[k]; + } + const dir = mkdtempSync(join(tmpdir(), "legacy-db-toml-")); + mkdirSync(join(dir, "supabase"), { recursive: true }); + writeFileSync( + join(dir, "supabase", ".env"), + "SUPABASE_INTERNAL_IMAGE_REGISTRY=my-mirror.example.com\nSUPABASE_PROJECT_ID=envonlyref\nSUPABASE_ENV=staging\n", + ); + return loadEnv(dir).pipe( + Effect.tap((env) => + Effect.sync(() => { + // The returned map carries all keys. + expect(env["SUPABASE_INTERNAL_IMAGE_REGISTRY"]).toBe("my-mirror.example.com"); + expect(env["SUPABASE_PROJECT_ID"]).toBe("envonlyref"); + expect(env["SUPABASE_ENV"]).toBe("staging"); + // ...but process.env is untouched, including the allowlisted registry key. + expect(process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"]).toBeUndefined(); + expect(process.env["SUPABASE_PROJECT_ID"]).toBeUndefined(); + expect(process.env["SUPABASE_ENV"]).toBeUndefined(); + for (const [k, v] of Object.entries(saved)) { + if (v === undefined) delete process.env[k]; + else process.env[k] = v; + } + rmSync(dir, { recursive: true, force: true }); + }), + ), + ); + }); + + it.effect( + "legacyApplyProjectEnv sets only the allowlisted key in-scope, never overrides, reverts on close", + () => { + // Go's loadNestedEnv os.Setenv's the project .env, but its root globals + // (project-ref, SUPABASE_ENV, workdir/profile) are resolved from the shell + // BEFORE loadNestedEnv. Our resolvers read process.env lazily, so we apply only + // the allowlisted `SUPABASE_INTERNAL_IMAGE_REGISTRY` (the one process.env-only + // reader): a .env project-ref must not retarget the lazy ref/pooler resolvers, + // and a .env SUPABASE_ENV must not switch the env-file set. + const saved: Record = {}; + for (const k of ["SUPABASE_INTERNAL_IMAGE_REGISTRY", "SUPABASE_PROJECT_ID", "SUPABASE_ENV"]) { + saved[k] = process.env[k]; + delete process.env[k]; + } + const loaded = { + SUPABASE_INTERNAL_IMAGE_REGISTRY: "my-mirror.example.com", + SUPABASE_PROJECT_ID: "envonlyref", + SUPABASE_ENV: "staging", + }; + return Effect.gen(function* () { + // Inside the scope: only the registry key is applied; the ref/env selector are not. + yield* Effect.scoped( + Effect.gen(function* () { + yield* legacyApplyProjectEnv(loaded); + expect(process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"]).toBe("my-mirror.example.com"); + expect(process.env["SUPABASE_PROJECT_ID"]).toBeUndefined(); + expect(process.env["SUPABASE_ENV"]).toBeUndefined(); + }), + ); + // After the scope closes the applied key is reverted (no test-worker leak). + expect(process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"]).toBeUndefined(); + + // An existing process.env value is never overridden, and is NOT deleted on close. + process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"] = "shell-wins.example.com"; + yield* Effect.scoped(legacyApplyProjectEnv(loaded)); + expect(process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"]).toBe("shell-wins.example.com"); + }).pipe( + Effect.ensuring( + Effect.sync(() => { + for (const [k, v] of Object.entries(saved)) { + if (v === undefined) delete process.env[k]; + else process.env[k] = v; + } + }), + ), + ); + }, + ); + it.effect("ignores a [db.pooler] connection_string in config.toml (Go reads .temp only)", () => { // The Go config field is tagged `toml:"-"`, so a connection_string in config.toml // is never honored; only supabase/.temp/pooler-url counts. diff --git a/apps/cli/src/legacy/shared/legacy-prompt-yes-no.ts b/apps/cli/src/legacy/shared/legacy-prompt-yes-no.ts index 13ad746906..64ba6368a6 100644 --- a/apps/cli/src/legacy/shared/legacy-prompt-yes-no.ts +++ b/apps/cli/src/legacy/shared/legacy-prompt-yes-no.ts @@ -1,16 +1,40 @@ -import { Effect } from "effect"; +import { Effect, Option } from "effect"; import { Output } from "../../shared/output/output.service.ts"; +import { Stdin } from "../../shared/runtime/stdin.service.ts"; +import { Tty } from "../../shared/runtime/tty.service.ts"; + +/** Go's non-TTY `Console.ReadLine` timeout (`internal/utils/console.go:36`). */ +const LEGACY_NON_TTY_TIMEOUT_MILLIS = 100; + +/** + * Port of Go's `parseYesNo` (`apps/cli-go/internal/utils/console.go:84-93`): + * case-insensitive and trimmed. `y`/`yes` → `true`, `n`/`no` → `false`, + * anything else → `undefined` (caller falls back to the default). + */ +export const legacyParseYesNo = (input: string): boolean | undefined => { + const s = input.trim().toLowerCase(); + if (s === "y" || s === "yes") { + return true; + } + if (s === "n" || s === "no") { + return false; + } + return undefined; +}; /** * Confirm-or-default prompt mirroring Go's `console.PromptYesNo` - * (`apps/cli-go/internal/utils/console.go`), shared by `seed buckets` and - * `storage rm`: + * (`apps/cli-go/internal/utils/console.go:64-82`), shared by `config push`, + * `db pull`, `seed buckets`, and `storage rm`: * - when `yes` is set, echoes `
Release notes

Sourced from @​anthropic-ai/claude-agent-sdk's releases.

v0.3.193

What's changed

  • Added promptSuggestions option to Browser SDK query() to opt the remote CLI into emitting follow-up suggestions
  • Fixed brief console window flashes on Windows when spawning CLI subprocesses

Update

npm install @anthropic-ai/claude-agent-sdk@0.3.193
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.193
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.193
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.193
Changelog

Sourced from @​anthropic-ai/claude-agent-sdk's changelog.

0.3.193

  • Added promptSuggestions option to Browser SDK query() to opt the remote CLI into emitting follow-up suggestions
  • Fixed brief console window flashes on Windows when spawning CLI subprocesses

0.3.192

  • Updated to parity with Claude Code v2.1.192
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@anthropic-ai/claude-agent-sdk&package-manager=npm_and_yarn&previous-version=0.3.191&new-version=0.3.193)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) 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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli/package.json | 2 +- pnpm-lock.yaml | 82 +++++++++++++++++++++++-------------------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 0fc1982f26..fea2505b46 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -43,7 +43,7 @@ "jose": "^6.2.3" }, "devDependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.3.191", + "@anthropic-ai/claude-agent-sdk": "^0.3.193", "@anthropic-ai/sdk": "^0.106.0", "@clack/prompts": "^1.6.0", "@effect/atom-react": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c943e1176..9160ae34ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -97,8 +97,8 @@ importers: version: 6.2.3 devDependencies: '@anthropic-ai/claude-agent-sdk': - specifier: ^0.3.191 - version: 0.3.191(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) + specifier: ^0.3.193 + version: 0.3.193(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) '@anthropic-ai/sdk': specifier: ^0.106.0 version: 0.106.0(zod@4.4.3) @@ -570,52 +570,52 @@ packages: resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} engines: {node: '>=18'} - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.191': - resolution: {integrity: sha512-rmpi5ONfuuw7oXdzyr9QJB5sK9Rr1MmdOUsiGutVFpcAqqSXpVYLmMvfaarmXvItgfwgGprzrGDfcAeAQHSUGw==} + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.193': + resolution: {integrity: sha512-1hT7b+KIm/3E1OSJofr7PF21Xq2zT1ccnjzuVcWQ5LYXJ09lgCMvA/ZfcDBKuoyCZ4lSnuicKXZ/h5vRbWfqrA==} cpu: [arm64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.191': - resolution: {integrity: sha512-b/4pfSFTC2gkc0RoGSJ5B3DWo77qZFMi2hRlx8kwaRDiefeIOnbJu42gDBrs4F8nSJJug77HM9AsG6XHXvt5Fg==} + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.193': + resolution: {integrity: sha512-9x5Y/L6iwETwEJFmPaYXfsE8q0cVgx75V7nL60HvSzi8K1XQNcG5u53jOzRvqDNah8mvGYOb+9AWrMCSJvmFyA==} cpu: [x64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.191': - resolution: {integrity: sha512-+Gg7lKwYDr8uiA37EBMYI63if+YGdbrFt2tcRFBB4IhdVf5r1IkKwQ8rEYV6Y0nqGTKoXLh3bRc5QWOe5JwgbQ==} + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.193': + resolution: {integrity: sha512-a3qsVTBe4G6ndsVavfIXEVAXLVXM8uvBUNYpm4jKqk2+ovWZQe/96CXOwmerg9U+/bllg4QJ9lSyYCsdtVIr6w==} cpu: [arm64] os: [linux] libc: [musl] - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.191': - resolution: {integrity: sha512-vm526e3InpItmh+Ua8a49eGTpFJvZTYLURX9ppKrtFb+Wo7rOgoXmK/Hzf/crEU7h72xtaD7PIOXm4P+psxO3w==} + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.193': + resolution: {integrity: sha512-gvfD9pKHXWxCkkIX6bC4/FOALTxqHYjAu83iT1bzn5mCv6QWSZRl6WLRRtvKpWtWuY1jpML1lL3YfKADsAX/rA==} cpu: [arm64] os: [linux] libc: [glibc] - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.191': - resolution: {integrity: sha512-tUwwIXpPBrzJge3E4SQJQX8PuC+lxPb5xQBmyLsWxyjttlgpFpTG/jWPVQO5qH0HgYaEJS5JALHFiYcXwVrEkg==} + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.193': + resolution: {integrity: sha512-DLXlO4tlcWygz0Ft4nu6ai5KssByYt2tOeWdc4dFXKt6uBKXpbZVziUUq3ePO5zuAFyU6w7EjYLv8MMPURbAiQ==} cpu: [x64] os: [linux] libc: [musl] - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.191': - resolution: {integrity: sha512-wj43/l0Behb4UWYt6Lomq2W7jhmuU7ARsEP0el6gLGCR348PDeqLGcB4dMOcYMGRZv8TYKfnhDwJnX4pJBT1vQ==} + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.193': + resolution: {integrity: sha512-1sz+7cn0iuh0ThInuAYF1jpFvLyOaZ0PZYQIF7eb9JDZbBohUf6INeTCwjAwUL0ASCq2xS7Odu/qDVuTtLTeDA==} cpu: [x64] os: [linux] libc: [glibc] - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.191': - resolution: {integrity: sha512-J8aTOi7n6AM7tHFlKt6hlJ4rue0TbY2glqcI2r6K3iK7Fmzvt9zfg00+l8fWKOais4K+8jqJCC4WSCUlz6+tKQ==} + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.193': + resolution: {integrity: sha512-36LJKiGuKusgaPTVeh9QanL00UcaE0RcC4pgK800/0SenApbh979ndxI0XKUVfLHzlGkqlhkhT3foMdqS+zx1w==} cpu: [arm64] os: [win32] - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.191': - resolution: {integrity: sha512-1rYlPuM9PS37IPCE/sAG7A7oCrWnIb0J7EY6eQBdImdXHEjwBngZ3E8KbtEM79fUTA3C810LqrnFpjOmNExLMA==} + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.193': + resolution: {integrity: sha512-VyyKZlWQpbD6nkUTeNvgmLvpqt1QaPQQBOC30tbEYwYsV5MC1I35Li0H7nWwngGqPSTtMvHpjpz6Eb2FSTn7/A==} cpu: [x64] os: [win32] - '@anthropic-ai/claude-agent-sdk@0.3.191': - resolution: {integrity: sha512-DM9oYbt+WAb/CiD8l/CO6akqPKfyZ4sL1e+lP2O2eS9IR0f58sf90xWeji7V+/zfnIp+jyH3hJ3V6r7odCdB8g==} + '@anthropic-ai/claude-agent-sdk@0.3.193': + resolution: {integrity: sha512-WzL03VJE1sT0Nz3rEpsYMYR+9n6iyQtLVt7ghMWnYC9pvDsiy6kwcMplZniWSjH8Dm6CfkUBN5t6KB4i/JfouA==} engines: {node: '>=18.0.0'} peerDependencies: '@anthropic-ai/sdk': '>=0.93.0' @@ -5583,6 +5583,10 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} @@ -6849,44 +6853,44 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.191': + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.193': optional: true - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.191': + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.193': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.191': + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.193': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.191': + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.193': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.191': + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.193': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.191': + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.193': optional: true - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.191': + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.193': optional: true - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.191': + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.193': optional: true - '@anthropic-ai/claude-agent-sdk@0.3.191(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': + '@anthropic-ai/claude-agent-sdk@0.3.193(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': dependencies: '@anthropic-ai/sdk': 0.106.0(zod@4.4.3) '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) zod: 4.4.3 optionalDependencies: - '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.191 - '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.191 - '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.191 - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.191 - '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.191 - '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.191 - '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.191 - '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.191 + '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.193 + '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.193 + '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.193 + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.193 + '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.193 + '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.193 + '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.193 + '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.193 '@anthropic-ai/sdk@0.106.0(zod@4.4.3)': dependencies: @@ -11988,6 +11992,8 @@ snapshots: picomatch@4.0.4: {} + picomatch@4.0.5: {} + pify@3.0.0: {} pino-abstract-transport@1.2.0: @@ -13186,7 +13192,7 @@ snapshots: vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 - picomatch: 4.0.4 + picomatch: 4.0.5 postcss: 8.5.16 rolldown: 1.0.2 tinyglobby: 0.2.17 From 5e9e1e934e3aa71da7431a91852cdbf4b8046ab1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jul 2026 00:12:50 +0000 Subject: [PATCH 24/48] chore(ci): bump the actions-major group with 2 updates (#5774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the actions-major group with 2 updates: [useblacksmith/checkout](https://github.com/useblacksmith/checkout) and [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials). Updates `useblacksmith/checkout` from 41cdeedae8edb2e684ba22896a5fd2a3cb85db6b to c9796daa2a4bdebdab5bd16be2c09a70cd4e1121
Changelog

Sourced from useblacksmith/checkout's changelog.

Changelog

V6.0.0

V5.0.1

V5.0.0

V4.3.1

V4.3.0

v4.2.2

v4.2.1

v4.2.0

v4.1.7

v4.1.6

v4.1.5

... (truncated)

Commits
  • c9796da feat(blacksmith-cache): add BLACKSMITH_BYPASS_CHECKOUT control-plane kill swi...
  • 626e346 clarify(blacksmith-cache): make hydration-in-progress fallback log explicit (...
  • e24db84 Update checkout from private beta to beta (#24)
  • See full diff in compare view

Updates `aws-actions/configure-aws-credentials` from 6.2.0 to 6.2.1
Release notes

Sourced from aws-actions/configure-aws-credentials's releases.

v6.2.1

6.2.1 (2026-06-26)

Bug Fixes

  • enforce allowed-account-ids on all auth paths (#1847) (4d281fb)
Changelog

Sourced from aws-actions/configure-aws-credentials's changelog.

Changelog

All notable changes to this project will be documented in this file. See standard-version for commit guidelines.

6.2.1 (2026-06-26)

Bug Fixes

  • enforce allowed-account-ids on all auth paths (#1847) (4d281fb)

6.2.0 (2026-06-01)

Features

Bug Fixes

  • skip credential check on output-env-credentials: false (#1778) (58e7c47)
  • assumeRole failing from session tag size too large (#1808) (d6f5dc3)

6.1.3 (2026-05-28)

Bug Fixes

  • fix: allow kubelet token symlink in #1805

6.1.2 (2026-05-26)

Bug Fixes

6.1.1 (2026-05-05)

Miscellaneous Chores

  • various dependency updates

6.1.0 (2026-04-06)

Features

... (truncated)

Commits
  • 254c19b chore(main): release 6.2.1 (#1849)
  • a20cf82 chore: Update dist
  • 4d281fb fix: enforce allowed-account-ids on all auth paths (#1847)
  • e004cdc chore(deps-dev): bump @​smithy/property-provider from 4.4.0 to 4.4.2 (#1845)
  • 88aa369 chore: Update dist
  • 687331b chore(deps): bump @​aws-sdk/client-sts from 3.1069.0 to 3.1075.0 (#1841)
  • ea607be chore: Update dist
  • 6d13606 chore(deps): bump @​smithy/node-http-handler from 4.8.0 to 4.8.2 (#1842)
  • 71a32ae chore: Update dist
  • b290f2c chore(deps-dev): bump @​aws-sdk/credential-provider-env (#1844)
  • 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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/apply-release-notes.yml | 2 +- .github/workflows/backfill-release-notes.yml | 2 +- .github/workflows/cli-go-mirror-image.yml | 2 +- .github/workflows/propose-release-notes.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/sync-main-to-develop.yml | 2 +- .github/workflows/test.yml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/apply-release-notes.yml b/.github/workflows/apply-release-notes.yml index 55ef8f94a5..2b46a7d7d1 100644 --- a/.github/workflows/apply-release-notes.yml +++ b/.github/workflows/apply-release-notes.yml @@ -82,7 +82,7 @@ jobs: # Checkout the PR head so any reviewer edits made in the GitHub UI before # approval are captured. apply-release-notes.ts reads from the working # tree. - - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 + - uses: useblacksmith/checkout@c9796daa2a4bdebdab5bd16be2c09a70cd4e1121 # v1 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 1 diff --git a/.github/workflows/backfill-release-notes.yml b/.github/workflows/backfill-release-notes.yml index 87e0bdc358..36dc30584b 100644 --- a/.github/workflows/backfill-release-notes.yml +++ b/.github/workflows/backfill-release-notes.yml @@ -46,7 +46,7 @@ jobs: TAG: ${{ inputs.tag }} GH_TOKEN: ${{ github.token }} steps: - - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 + - uses: useblacksmith/checkout@c9796daa2a4bdebdab5bd16be2c09a70cd4e1121 # v1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/cli-go-mirror-image.yml b/.github/workflows/cli-go-mirror-image.yml index d5c864e337..3c9484e266 100644 --- a/.github/workflows/cli-go-mirror-image.yml +++ b/.github/workflows/cli-go-mirror-image.yml @@ -34,7 +34,7 @@ jobs: run: | echo "image=${TAG##*/}" >> $GITHUB_OUTPUT - name: configure aws credentials - uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0 + uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1 with: role-to-assume: ${{ secrets.PROD_AWS_ROLE }} aws-region: us-east-1 diff --git a/.github/workflows/propose-release-notes.yml b/.github/workflows/propose-release-notes.yml index 164dfee30c..49de50a825 100644 --- a/.github/workflows/propose-release-notes.yml +++ b/.github/workflows/propose-release-notes.yml @@ -62,7 +62,7 @@ jobs: client-id: ${{ vars.GH_APP_CLIENT_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 + - uses: useblacksmith/checkout@c9796daa2a4bdebdab5bd16be2c09a70cd4e1121 # v1 with: # Full history + tags so backfill-release-notes.ts can reach the # commit graph it needs (semantic-release walks notes back to the diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ccfd511d6a..50de42423f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,7 +64,7 @@ jobs: client-id: ${{ vars.GH_APP_CLIENT_ID }} private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} permission-contents: write - - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 + - uses: useblacksmith/checkout@c9796daa2a4bdebdab5bd16be2c09a70cd4e1121 # v1 with: persist-credentials: true fetch-depth: 0 @@ -109,7 +109,7 @@ jobs: # Authorization header overrides the App token semantic-release puts in # the push URL — making the dry-push identify as `github-actions[bot]` # and get rejected by branch protection. - - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 + - uses: useblacksmith/checkout@c9796daa2a4bdebdab5bd16be2c09a70cd4e1121 # v1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/sync-main-to-develop.yml b/.github/workflows/sync-main-to-develop.yml index c93f2135ed..b46397c33b 100644 --- a/.github/workflows/sync-main-to-develop.yml +++ b/.github/workflows/sync-main-to-develop.yml @@ -35,7 +35,7 @@ jobs: private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} permission-contents: write - - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 + - uses: useblacksmith/checkout@c9796daa2a4bdebdab5bd16be2c09a70cd4e1121 # v1 with: ref: develop fetch-depth: 0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5da5adca0..273971448e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -101,7 +101,7 @@ jobs: shard: [ 1, 2, 3 ] steps: - name: Checkout - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1.0.0-beta + uses: useblacksmith/checkout@c9796daa2a4bdebdab5bd16be2c09a70cd4e1121 # v1.0.0-beta with: fetch-depth: 0 From 4c4f6b646e52ec4a32de44db818c7d6d8c6a55af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jul 2026 09:39:23 +0200 Subject: [PATCH 25/48] fix(docker): bump supabase/postgres from 17.6.1.141 to 17.6.1.142 in /apps/cli-go/pkg/config/templates (#5759) Bumps supabase/postgres from 17.6.1.141 to 17.6.1.142. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=supabase/postgres&package-manager=docker&previous-version=17.6.1.141&new-version=17.6.1.142)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) 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 this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- apps/cli-go/pkg/config/templates/Dockerfile | 2 +- packages/stack/src/versions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index 4d6d4ac5d9..9cff67eda6 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -1,5 +1,5 @@ # Exposed for updates by .github/dependabot.yml -FROM supabase/postgres:17.6.1.141 AS pg +FROM supabase/postgres:17.6.1.142 AS pg # Append to ServiceImages when adding new dependencies below FROM library/kong:2.8.1 AS kong FROM axllent/mailpit:v1.30.2 AS mailpit diff --git a/packages/stack/src/versions.ts b/packages/stack/src/versions.ts index 2141ee733a..9d5c28f550 100644 --- a/packages/stack/src/versions.ts +++ b/packages/stack/src/versions.ts @@ -46,7 +46,7 @@ export interface VersionManifest { } export const DEFAULT_VERSIONS: VersionManifest = { - postgres: "17.6.1.141", + postgres: "17.6.1.142", postgrest: "14.14", auth: "2.192.0", "edge-runtime": "1.74.2", From 09d13d97ca2b3f1061181b14cb162d154077ea30 Mon Sep 17 00:00:00 2001 From: "supabase-cli-releaser[bot]" <246109035+supabase-cli-releaser[bot]@users.noreply.github.com> Date: Fri, 3 Jul 2026 09:45:42 +0200 Subject: [PATCH 26/48] chore(api): sync Management API OpenAPI spec (#5752) This PR was automatically created to sync the generated `@supabase/api` package with the latest Management API OpenAPI document. Changes were detected in the upstream OpenAPI document exposed by `https://api.supabase.com/api/v1-json`. --------- Co-authored-by: jgoux <1443499+jgoux@users.noreply.github.com> Co-authored-by: Julien Goux --- .../2.response.json | 1 + .../3.response.json | 1 + .../default.response.json | 1 + .../interactions.json | 1 + .../interactions.json | 1 + .../interactions.json | 1 + .../config/push/push.integration.test.ts | 2 ++ packages/api/src/generated/contracts.ts | 26 ++++++++++++++- packages/api/src/generated/openapi.json | 32 +++++++++++++++++-- packages/cli-test-helpers/src/normalize.ts | 6 ++-- .../src/normalize.unit.test.ts | 23 +++++++++++++ 11 files changed, 89 insertions(+), 6 deletions(-) diff --git a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/2.response.json b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/2.response.json index be253aaf01..ec12771c6e 100644 --- a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/2.response.json +++ b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/2.response.json @@ -12,6 +12,7 @@ "max_rows": 1000, "db_extra_search_path": "public, extensions", "db_pool": null, + "db_pool_acquisition_timeout": null, "jwt_secret": "EH9SYrAOLy4wUwRSts5kU5oZfiqFLy3+v+a1EAwFpKRbqxzPldJ5wMoiySHJiDmyWrMZXL5yK/9V7XkCtRdU0Q==" } } diff --git a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/3.response.json b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/3.response.json index 7d6f03351f..71b0aeac92 100644 --- a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/3.response.json +++ b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/3.response.json @@ -12,6 +12,7 @@ "max_rows": 1000, "db_extra_search_path": "public, extensions", "db_pool": null, + "db_pool_acquisition_timeout": null, "jwt_secret": "r0/2ujDmPfvUIbzLHvYV1Uz58kitPHk55HZt/wJ8ULYVsNKQ7huFluWj7LL+yi8nTv+yUVPEHFtsqCiHlR1lRw==" } } diff --git a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/default.response.json b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/default.response.json index 563ffe0590..abacd48f2b 100644 --- a/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/default.response.json +++ b/apps/cli-e2e/fixtures/recorded/GET_v1_projects___PROJECT_REF___postgrest/default.response.json @@ -12,6 +12,7 @@ "max_rows": 1000, "db_extra_search_path": "public, extensions", "db_pool": null, + "db_pool_acquisition_timeout": null, "jwt_secret": "wYu1Y5zcq5XMI/BHlkIl6p9LkFCuWMLyiAVVoKKcIKJkp2un+UF9KkNth/4YXd2nLagAXtJ0wq/DaixwsuUfww==" } } diff --git a/apps/cli-e2e/fixtures/scenarios/config-push-emits-http-trace-with-debug/interactions.json b/apps/cli-e2e/fixtures/scenarios/config-push-emits-http-trace-with-debug/interactions.json index 4341b8a12a..72a45c2466 100644 --- a/apps/cli-e2e/fixtures/scenarios/config-push-emits-http-trace-with-debug/interactions.json +++ b/apps/cli-e2e/fixtures/scenarios/config-push-emits-http-trace-with-debug/interactions.json @@ -691,6 +691,7 @@ "max_rows": 1000, "db_extra_search_path": "public, extensions", "db_pool": null, + "db_pool_acquisition_timeout": null, "jwt_secret": "EH9SYrAOLy4wUwRSts5kU5oZfiqFLy3+v+a1EAwFpKRbqxzPldJ5wMoiySHJiDmyWrMZXL5yK/9V7XkCtRdU0Q==" } } diff --git a/apps/cli-e2e/fixtures/scenarios/config-push-reconciles-every-section-against-the-remote/interactions.json b/apps/cli-e2e/fixtures/scenarios/config-push-reconciles-every-section-against-the-remote/interactions.json index b19751a226..71257560ea 100644 --- a/apps/cli-e2e/fixtures/scenarios/config-push-reconciles-every-section-against-the-remote/interactions.json +++ b/apps/cli-e2e/fixtures/scenarios/config-push-reconciles-every-section-against-the-remote/interactions.json @@ -691,6 +691,7 @@ "max_rows": 1000, "db_extra_search_path": "public, extensions", "db_pool": null, + "db_pool_acquisition_timeout": null, "jwt_secret": "EH9SYrAOLy4wUwRSts5kU5oZfiqFLy3+v+a1EAwFpKRbqxzPldJ5wMoiySHJiDmyWrMZXL5yK/9V7XkCtRdU0Q==" } } diff --git a/apps/cli-e2e/fixtures/scenarios/link-links-when-only-supabase-project-id-is-set-in-non-tty/interactions.json b/apps/cli-e2e/fixtures/scenarios/link-links-when-only-supabase-project-id-is-set-in-non-tty/interactions.json index 263250637c..a0d179bafa 100644 --- a/apps/cli-e2e/fixtures/scenarios/link-links-when-only-supabase-project-id-is-set-in-non-tty/interactions.json +++ b/apps/cli-e2e/fixtures/scenarios/link-links-when-only-supabase-project-id-is-set-in-non-tty/interactions.json @@ -236,6 +236,7 @@ "max_rows": 1000, "db_extra_search_path": "public, extensions", "db_pool": null, + "db_pool_acquisition_timeout": null, "jwt_secret": "wYu1Y5zcq5XMI/BHlkIl6p9LkFCuWMLyiAVVoKKcIKJkp2un+UF9KkNth/4YXd2nLagAXtJ0wq/DaixwsuUfww==" } } diff --git a/apps/cli/src/legacy/commands/config/push/push.integration.test.ts b/apps/cli/src/legacy/commands/config/push/push.integration.test.ts index d70a99bc20..02ccd9d77c 100644 --- a/apps/cli/src/legacy/commands/config/push/push.integration.test.ts +++ b/apps/cli/src/legacy/commands/config/push/push.integration.test.ts @@ -37,6 +37,7 @@ const POSTGREST_DISABLED = { db_extra_search_path: "", max_rows: 0, db_pool: null, + db_pool_acquisition_timeout: null, }; /** Routes mock HTTP responses by URL path so a single handler serves every endpoint. */ @@ -259,6 +260,7 @@ project_id = "abcdefghijklmnopqrst" db_extra_search_path: "public,extensions", max_rows: 1000, db_pool: null, + db_pool_acquisition_timeout: null, jwt_secret: "x", }, }, diff --git a/packages/api/src/generated/contracts.ts b/packages/api/src/generated/contracts.ts index 48a0fa2a9c..ce1f153aa9 100644 --- a/packages/api/src/generated/contracts.ts +++ b/packages/api/src/generated/contracts.ts @@ -2539,6 +2539,7 @@ export const V1GetOrganizationEntitlementsOutput = Schema.Struct({ "integrations.github_connections", "dedicated_pooler", "observability.dashboard_advanced_metrics", + "api.members.invitations", "api.members.roles", ]), type: Schema.Literals(["boolean", "numeric", "set"]), @@ -2906,6 +2907,12 @@ export const V1GetPostgrestServiceConfigOutput = Schema.Struct({ }).check(Schema.isInt()), Schema.Null, ]), + db_pool_acquisition_timeout: Schema.Union([ + Schema.Number.annotate({ + description: "If `null`, the value is automatically configured to 10.", + }).check(Schema.isInt()), + Schema.Null, + ]), jwt_secret: Schema.optionalKey(Schema.String), }); export const V1GetProfileInput = Schema.Struct({}); @@ -5789,6 +5796,11 @@ export const V1UpdatePostgrestServiceConfigInput = Schema.Struct({ .check(Schema.isGreaterThanOrEqualTo(0)) .check(Schema.isLessThanOrEqualTo(1000)), ), + db_pool_acquisition_timeout: Schema.optionalKey( + Schema.Number.check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)) + .check(Schema.isLessThanOrEqualTo(60)), + ), }); export const V1UpdatePostgrestServiceConfigOutput = Schema.Struct({ db_schema: Schema.String, @@ -5800,6 +5812,12 @@ export const V1UpdatePostgrestServiceConfigOutput = Schema.Struct({ }).check(Schema.isInt()), Schema.Null, ]), + db_pool_acquisition_timeout: Schema.Union([ + Schema.Number.annotate({ + description: "If `null`, the value is automatically configured to 10.", + }).check(Schema.isInt()), + Schema.Null, + ]), }); export const V1UpdateProjectApiKeyInput = Schema.Struct({ ref: Schema.String.check(Schema.isMinLength(20)) @@ -8792,7 +8810,13 @@ export const operationDefinitions = { requestBody: { kind: "json", contentType: "application/json", - fields: ["db_extra_search_path", "db_schema", "max_rows", "db_pool"], + fields: [ + "db_extra_search_path", + "db_schema", + "max_rows", + "db_pool", + "db_pool_acquisition_timeout", + ], }, response: { kind: "json" }, inputSchema: V1UpdatePostgrestServiceConfigInput, diff --git a/packages/api/src/generated/openapi.json b/packages/api/src/generated/openapi.json index 758084cf9b..0cc0c762a7 100644 --- a/packages/api/src/generated/openapi.json +++ b/packages/api/src/generated/openapi.json @@ -13333,11 +13333,22 @@ "nullable": true, "description": "If `null`, the value is automatically configured based on compute size." }, + "db_pool_acquisition_timeout": { + "type": "integer", + "nullable": true, + "description": "If `null`, the value is automatically configured to 10." + }, "jwt_secret": { "type": "string" } }, - "required": ["db_schema", "max_rows", "db_extra_search_path", "db_pool"] + "required": [ + "db_schema", + "max_rows", + "db_extra_search_path", + "db_pool", + "db_pool_acquisition_timeout" + ] }, "V1UpdatePostgrestConfigBody": { "type": "object", @@ -13357,6 +13368,11 @@ "type": "integer", "minimum": 0, "maximum": 1000 + }, + "db_pool_acquisition_timeout": { + "type": "integer", + "minimum": 0, + "maximum": 60 } }, "example": { @@ -13381,9 +13397,20 @@ "type": "integer", "nullable": true, "description": "If `null`, the value is automatically configured based on compute size." + }, + "db_pool_acquisition_timeout": { + "type": "integer", + "nullable": true, + "description": "If `null`, the value is automatically configured to 10." } }, - "required": ["db_schema", "max_rows", "db_extra_search_path", "db_pool"] + "required": [ + "db_schema", + "max_rows", + "db_extra_search_path", + "db_pool", + "db_pool_acquisition_timeout" + ] }, "V1ProjectRefResponse": { "type": "object", @@ -20296,6 +20323,7 @@ "integrations.github_connections", "dedicated_pooler", "observability.dashboard_advanced_metrics", + "api.members.invitations", "api.members.roles" ] }, diff --git a/packages/cli-test-helpers/src/normalize.ts b/packages/cli-test-helpers/src/normalize.ts index e10e491ccd..fb9ac28687 100644 --- a/packages/cli-test-helpers/src/normalize.ts +++ b/packages/cli-test-helpers/src/normalize.ts @@ -83,8 +83,8 @@ export function normalize(output: string, options: NormalizeOptions = {}): strin .replace(/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/g, "") // 7. Durations (e.g. 1.23s, 123ms, 42s) .replace(/\b\d+(?:\.\d+)?(?:ms|s)\b/g, "") - // 8. Unix absolute paths (/home/…, /Users/…, /tmp/…, /var/…, /opt/…, /usr/…) - .replace(/\/(?:home|Users|tmp|var|opt|usr)\/\S+/g, "") + // 8. Unix absolute paths (/home/…, /Users/…, /tmp/…, /private/tmp/…, /var/…, /private/var/…, /opt/…, /usr/…) + .replace(/\/(?:home|Users|tmp|private\/tmp|var|private\/var|opt|usr)\/\S+/g, "") // 9. Windows absolute paths (C:\…) .replace(/[A-Z]:\\\S+/g, "") // 10. Go stack frame addresses (0x1a2b3c4d) — vary per run due to ASLR @@ -102,7 +102,7 @@ export function normalize(output: string, options: NormalizeOptions = {}): strin // \t: // 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, "") + .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 diff --git a/packages/cli-test-helpers/src/normalize.unit.test.ts b/packages/cli-test-helpers/src/normalize.unit.test.ts index 2bfef356cd..c80c4bb641 100644 --- a/packages/cli-test-helpers/src/normalize.unit.test.ts +++ b/packages/cli-test-helpers/src/normalize.unit.test.ts @@ -57,7 +57,9 @@ describe("normalize", () => { expect(normalize("/Users/colum/supabase/cli/config.toml")).toBe(""); expect(normalize("/home/runner/work/config")).toBe(""); expect(normalize("/tmp/cli-e2e-profile-abc.yaml")).toBe(""); + expect(normalize("/private/tmp/cli-e2e-profile-abc.yaml")).toBe(""); expect(normalize("/var/log/app.log")).toBe(""); + expect(normalize("/private/var/folders/app.log")).toBe(""); }); it("normalizes Windows absolute paths", () => { @@ -74,6 +76,27 @@ describe("normalize", () => { expect(normalize(trace)).toBe(""); }); + it("strips github.com/go-errors stack frames from macOS temp paths", () => { + const trace = [ + "/private/var/folders/xy/cli/cmd.go (0x101234)", + "\trunCommand: return err", + "/private/tmp/supabase-cli/cmd/root.go (0x105678)", + "\tExecute: return err", + "", + "Cannot find project ref.", + ].join("\n"); + expect(normalize(trace)).toBe("Cannot find project ref."); + }); + + it("strips standalone github.com/go-errors stack frame lines", () => { + const trace = [ + "/private/var/folders/xy/cli/cmd.go (0x101234)", + "/private/tmp/supabase-cli/cmd/root.go (0x105678)", + "Cannot find project ref.", + ].join("\n"); + expect(normalize(trace)).toBe("Cannot find project ref."); + }); + it("normalizes Node/Bun stack trace lines", () => { const trace = [ "Error: something failed", From 7f2002ee85b8511f02f5740ba3a800a186df4e86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:03:03 +0200 Subject: [PATCH 27/48] fix(docker): bump the docker-minor group in /apps/cli-go/pkg/config/templates with 3 updates (#5758) Bumps the docker-minor group in /apps/cli-go/pkg/config/templates with 3 updates: supabase/realtime, supabase/storage-api and supabase/logflare. Updates `supabase/realtime` from v2.112.2 to v2.112.3 Updates `supabase/storage-api` from v1.61.9 to v1.61.11 Updates `supabase/logflare` from 1.45.6 to 1.46.0 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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Julien Goux --- apps/cli-go/pkg/config/templates/Dockerfile | 6 +++--- packages/stack/src/versions.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index 9cff67eda6..7b376de5eb 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -11,9 +11,9 @@ FROM supabase/edge-runtime:v1.74.2 AS edgeruntime FROM timberio/vector:0.53.0-alpine AS vector FROM supabase/supavisor:2.9.7 AS supavisor FROM supabase/gotrue:v2.192.0 AS gotrue -FROM supabase/realtime:v2.112.2 AS realtime -FROM supabase/storage-api:v1.61.9 AS storage -FROM supabase/logflare:1.45.6 AS logflare +FROM supabase/realtime:v2.112.3 AS realtime +FROM supabase/storage-api:v1.61.11 AS storage +FROM supabase/logflare:1.46.0 AS logflare # Append to JobImages when adding new dependencies below FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ FROM supabase/migra:3.0.1663481299 AS migra diff --git a/packages/stack/src/versions.ts b/packages/stack/src/versions.ts index 9d5c28f550..e13a2d4c71 100644 --- a/packages/stack/src/versions.ts +++ b/packages/stack/src/versions.ts @@ -50,13 +50,13 @@ export const DEFAULT_VERSIONS: VersionManifest = { postgrest: "14.14", auth: "2.192.0", "edge-runtime": "1.74.2", - realtime: "2.112.2", - storage: "1.61.9", + realtime: "2.112.3", + storage: "1.61.11", imgproxy: "v3.8.0", mailpit: "v1.30.2", pgmeta: "0.96.6", studio: "2026.06.29-sha-20290c7", - analytics: "1.45.6", + analytics: "1.46.0", vector: "0.53.0-alpine", pooler: "2.9.7", } as const; From 9bb97be9b6245de45b49ca0eaac207a2eaf02429 Mon Sep 17 00:00:00 2001 From: Luc Peng Date: Fri, 3 Jul 2026 16:26:39 +0800 Subject: [PATCH 28/48] fix(db): respect schema_paths order in diff (#5706) ## Summary - Preserve explicit `[db.migrations].schema_paths` order when preparing declared schemas for `db diff`. - Expand directory schema path entries deterministically without overriding explicit file/glob order. - Use pg-delta declarative apply only when schema_paths is unset or points directly at the declarative directory. Fixes #3854 ## Testing - `go test ./internal/db/diff ./internal/db/declarative ./cmd` --------- Co-authored-by: Julien Goux --- apps/cli-go/internal/db/diff/diff.go | 32 +++- apps/cli-go/internal/db/diff/diff_test.go | 160 ++++++++++++++++++ apps/cli-go/internal/db/diff/shadow.go | 2 +- apps/cli-go/internal/migration/apply/apply.go | 2 +- apps/cli-go/pkg/config/config.go | 53 +++++- apps/cli-go/pkg/config/config_test.go | 44 +++++ apps/cli-go/pkg/config/templates/config.toml | 4 +- apps/cli-go/pkg/migration/seed.go | 2 +- .../src/shared/init/project-init.templates.ts | 4 +- apps/docs/public/cli/config.schema.json | 8 +- packages/config/src/db.ts | 5 +- 11 files changed, 295 insertions(+), 21 deletions(-) diff --git a/apps/cli-go/internal/db/diff/diff.go b/apps/cli-go/internal/db/diff/diff.go index 5d2790e5b9..85c9c09c9c 100644 --- a/apps/cli-go/internal/db/diff/diff.go +++ b/apps/cli-go/internal/db/diff/diff.go @@ -50,6 +50,13 @@ func Run(ctx context.Context, schema []string, file string, config pgconn.Config } func loadDeclaredSchemas(fsys afero.Fs) ([]string, error) { + if schemas := utils.Config.Db.Migrations.SchemaPaths; len(schemas) > 0 { + return schemas.SQLFiles( + afero.NewIOFS(fsys), + configpkg.WithSkipEmptyGlobs(), + configpkg.WithErrorOnAllSkippedGlobs(), + ) + } // When pg-delta is enabled, declarative path is the source of truth (config or default). if utils.IsPgDeltaEnabled() { declDir := utils.GetDeclarativeDir() @@ -70,13 +77,6 @@ func loadDeclaredSchemas(fsys afero.Fs) ([]string, error) { return declared, nil } } - if schemas := utils.Config.Db.Migrations.SchemaPaths; len(schemas) > 0 { - return schemas.Files( - afero.NewIOFS(fsys), - configpkg.WithSkipEmptyGlobs(), - configpkg.WithErrorOnAllSkippedGlobs(), - ) - } if exists, err := afero.DirExists(fsys, utils.SchemasDir); err != nil { return nil, errors.Errorf("failed to check schemas: %w", err) } else if !exists { @@ -100,6 +100,24 @@ func loadDeclaredSchemas(fsys afero.Fs) ([]string, error) { return declared, nil } +func shouldApplyDeclarativeWithPgDelta(usePgDelta bool) bool { + if !usePgDelta { + return false + } + schemas := utils.Config.Db.Migrations.SchemaPaths + if len(schemas) == 0 { + return true + } + if len(schemas) != 1 { + return false + } + return cleanSchemaPath(schemas[0]) == cleanSchemaPath(utils.GetDeclarativeDir()) +} + +func cleanSchemaPath(path string) string { + return filepath.ToSlash(filepath.Clean(path)) +} + // https://github.com/djrobstep/migra/blob/master/migra/statements.py#L6 var dropStatementPattern = regexp.MustCompile(`(?i)drop\s+`) diff --git a/apps/cli-go/internal/db/diff/diff_test.go b/apps/cli-go/internal/db/diff/diff_test.go index ca52d9e09b..92fd01164b 100644 --- a/apps/cli-go/internal/db/diff/diff_test.go +++ b/apps/cli-go/internal/db/diff/diff_test.go @@ -24,6 +24,7 @@ import ( "github.com/supabase/cli/internal/testing/helper" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" + pkgconfig "github.com/supabase/cli/pkg/config" "github.com/supabase/cli/pkg/migration" "github.com/supabase/cli/pkg/pgtest" ) @@ -36,6 +37,80 @@ var dbConfig = pgconn.Config{ Database: "postgres", } +func TestLoadDeclaredSchemas(t *testing.T) { + t.Run("respects schema_paths order when pg-delta declarative dir exists", func(t *testing.T) { + originalConfig := utils.Config + t.Cleanup(func() { utils.Config = originalConfig }) + utils.Config.Db.Migrations.SchemaPaths = pkgconfig.Glob{ + "supabase/schemas/z_function.sql", + "supabase/schemas/a_table.sql", + } + utils.Config.Experimental.PgDelta = &pkgconfig.PgDeltaConfig{ + Enabled: true, + DeclarativeSchemaPath: utils.SchemasDir, + } + fsys := afero.NewMemMapFs() + require.NoError(t, fsys.MkdirAll(utils.SchemasDir, 0755)) + require.NoError(t, afero.WriteFile(fsys, "supabase/schemas/a_table.sql", []byte("create table a();"), 0644)) + require.NoError(t, afero.WriteFile(fsys, "supabase/schemas/z_function.sql", []byte("create function z() returns void language sql as $$ select 1 $$;"), 0644)) + + declared, err := loadDeclaredSchemas(fsys) + + require.NoError(t, err) + assert.Equal(t, []string{ + "supabase/schemas/z_function.sql", + "supabase/schemas/a_table.sql", + }, declared) + }) + + t.Run("expands schema_paths directory entries deterministically", func(t *testing.T) { + originalConfig := utils.Config + t.Cleanup(func() { utils.Config = originalConfig }) + utils.Config.Db.Migrations.SchemaPaths = pkgconfig.Glob{utils.DeclarativeDir} + fsys := afero.NewMemMapFs() + require.NoError(t, fsys.MkdirAll(filepath.Join(utils.DeclarativeDir, "nested"), 0755)) + require.NoError(t, afero.WriteFile(fsys, filepath.Join(utils.DeclarativeDir, "nested", "b.sql"), []byte("select 2;"), 0644)) + require.NoError(t, afero.WriteFile(fsys, filepath.Join(utils.DeclarativeDir, "a.sql"), []byte("select 1;"), 0644)) + + declared, err := loadDeclaredSchemas(fsys) + + require.NoError(t, err) + assert.Equal(t, []string{ + filepath.Join(utils.DeclarativeDir, "a.sql"), + filepath.Join(utils.DeclarativeDir, "nested", "b.sql"), + }, declared) + }) +} + +func TestShouldApplyDeclarativeWithPgDelta(t *testing.T) { + t.Run("uses pg-delta declarative apply when no schema_paths override is configured", func(t *testing.T) { + originalConfig := utils.Config + t.Cleanup(func() { utils.Config = originalConfig }) + utils.Config.Db.Migrations.SchemaPaths = nil + + assert.True(t, shouldApplyDeclarativeWithPgDelta(true)) + }) + + t.Run("uses pg-delta declarative apply when schema_paths points at the declarative dir", func(t *testing.T) { + originalConfig := utils.Config + t.Cleanup(func() { utils.Config = originalConfig }) + utils.Config.Db.Migrations.SchemaPaths = pkgconfig.Glob{utils.DeclarativeDir + "/"} + + assert.True(t, shouldApplyDeclarativeWithPgDelta(true)) + }) + + t.Run("uses ordered migration apply for explicit schema_paths files", func(t *testing.T) { + originalConfig := utils.Config + t.Cleanup(func() { utils.Config = originalConfig }) + utils.Config.Db.Migrations.SchemaPaths = pkgconfig.Glob{ + "supabase/schemas/z_function.sql", + "supabase/schemas/a_table.sql", + } + + assert.False(t, shouldApplyDeclarativeWithPgDelta(true)) + }) +} + func TestRun(t *testing.T) { t.Run("runs migra diff", func(t *testing.T) { // Setup in-memory fs @@ -98,6 +173,91 @@ func TestRun(t *testing.T) { assert.Equal(t, []byte(diff), contents) }) + t.Run("applies schema_paths in order before saving generated diff", func(t *testing.T) { + originalConfig := utils.Config + t.Cleanup(func() { utils.Config = originalConfig }) + utils.Config.Db.MajorVersion = 14 + utils.Config.Db.ShadowPort = 54320 + utils.Config.Db.Migrations.SchemaPaths = pkgconfig.Glob{ + "supabase/schemas/z_function.sql", + "supabase/schemas/a_table.sql", + } + utils.Config.Experimental.PgDelta = &pkgconfig.PgDeltaConfig{ + Enabled: true, + DeclarativeSchemaPath: utils.SchemasDir, + } + utils.GlobalsSql = "create schema public" + utils.InitialSchemaPg14Sql = "create schema private" + functionSQL := "create function public.z_function() returns integer language sql as $$ select 1 $$" + tableSQL := "create table public.a_table (id integer default public.z_function())" + generated := functionSQL + ";\n" + tableSQL + ";\n" + fsys := afero.NewMemMapFs() + require.NoError(t, afero.WriteFile(fsys, "supabase/schemas/a_table.sql", []byte(tableSQL), 0644)) + require.NoError(t, afero.WriteFile(fsys, "supabase/schemas/z_function.sql", []byte(functionSQL), 0644)) + require.NoError(t, apitest.MockDocker(utils.Docker)) + defer gock.OffAll() + apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Db.Image), "test-shadow-db") + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/containers/test-shadow-db/json"). + Reply(http.StatusOK). + JSON(container.InspectResponse{ContainerJSONBase: &container.ContainerJSONBase{ + State: &container.State{ + Running: true, + Health: &container.Health{Status: types.Healthy}, + }, + }}) + gock.New(utils.Docker.DaemonHost()). + Delete("/v" + utils.Docker.ClientVersion() + "/containers/test-shadow-db"). + Reply(http.StatusOK) + shadowConn := pgtest.NewConn() + defer shadowConn.Close(t) + shadowConn.Query(utils.GlobalsSql). + Reply("CREATE SCHEMA"). + Query(utils.InitialSchemaPg14Sql). + Reply("CREATE SCHEMA") + helper.MockApiPrivilegesRevoke(shadowConn). + Query(CREATE_TEMPLATE). + Reply("CREATE DATABASE") + declaredConn := pgtest.NewConn() + defer declaredConn.Close(t) + declaredConn.Query(functionSQL). + Reply("CREATE FUNCTION"). + Query(tableSQL). + Reply("CREATE TABLE") + diffCalled := false + differ := func(_ context.Context, _, target pgconn.Config, schema []string, _ ...func(*pgx.ConnConfig)) (string, error) { + diffCalled = true + assert.Equal(t, "contrib_regression", target.Database) + assert.Equal(t, []string{"public"}, schema) + return generated, nil + } + localConfig := pgconn.Config{ + Host: utils.Config.Hostname, + Port: utils.Config.Db.Port, + User: "postgres", + Password: utils.Config.Db.Password, + Database: "postgres", + } + + err := Run(context.Background(), []string{"public"}, "ordered_schema", localConfig, differ, true, fsys, func(cc *pgx.ConnConfig) { + if cc.Database == "contrib_regression" { + declaredConn.Intercept(cc) + } else { + shadowConn.Intercept(cc) + } + }) + + require.NoError(t, err) + assert.True(t, diffCalled) + assert.Empty(t, apitest.ListUnmatchedRequests()) + files, err := afero.ReadDir(fsys, utils.MigrationsDir) + require.NoError(t, err) + require.Len(t, files, 1) + contents, err := afero.ReadFile(fsys, filepath.Join(utils.MigrationsDir, files[0].Name())) + require.NoError(t, err) + assert.Equal(t, []byte(generated), contents) + }) + t.Run("throws error on failure to diff target", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() diff --git a/apps/cli-go/internal/db/diff/shadow.go b/apps/cli-go/internal/db/diff/shadow.go index 8012c565e8..2ebd13591f 100644 --- a/apps/cli-go/internal/db/diff/shadow.go +++ b/apps/cli-go/internal/db/diff/shadow.go @@ -67,7 +67,7 @@ func PrepareShadowSource(ctx context.Context, schema []string, targetLocal bool, if len(declared) > 0 { override := shadowConfig override.Database = "contrib_regression" - if usePgDelta { + if shouldApplyDeclarativeWithPgDelta(usePgDelta) { declDir := utils.GetDeclarativeDir() if exists, _ := afero.DirExists(fsys, declDir); exists { if err := pgdelta.ApplyDeclarative(ctx, override, fsys); err != nil { diff --git a/apps/cli-go/internal/migration/apply/apply.go b/apps/cli-go/internal/migration/apply/apply.go index 16fdd63611..18bd330ea6 100644 --- a/apps/cli-go/internal/migration/apply/apply.go +++ b/apps/cli-go/internal/migration/apply/apply.go @@ -49,7 +49,7 @@ func applySeedFiles(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error { } func applySchemaFiles(ctx context.Context, conn *pgx.Conn, fsys fs.FS) error { - declared, err := utils.Config.Db.Migrations.SchemaPaths.Files(fsys) + declared, err := utils.Config.Db.Migrations.SchemaPaths.SQLFiles(fsys) if len(declared) == 0 { return err } diff --git a/apps/cli-go/pkg/config/config.go b/apps/cli-go/pkg/config/config.go index b9f253230a..b55a52b27e 100644 --- a/apps/cli-go/pkg/config/config.go +++ b/apps/cli-go/pkg/config/config.go @@ -116,9 +116,22 @@ func WithErrorOnAllSkippedGlobs() GlobOption { } } +func (g Glob) Files(fsys fs.FS, options ...GlobOption) ([]string, error) { + return g.files(fsys, nil, options...) +} + +// SQLFiles matches glob patterns and expands directory matches recursively to +// SQL files. Pattern order is preserved, and directory contents are sorted for +// deterministic application. +func (g Glob) SQLFiles(fsys fs.FS, options ...GlobOption) ([]string, error) { + return g.files(fsys, func(path string, entry fs.DirEntry) bool { + return entry.Type().IsRegular() && filepath.Ext(path) == ".sql" + }, options...) +} + // Match the glob patterns in the given FS to get a deduplicated // array of all migrations files to apply in the declared order. -func (g Glob) Files(fsys fs.FS, options ...GlobOption) ([]string, error) { +func (g Glob) files(fsys fs.FS, expandDir func(string, fs.DirEntry) bool, options ...GlobOption) ([]string, error) { opts := globOptions{} for _, apply := range options { apply(&opts) @@ -143,6 +156,27 @@ func (g Glob) Files(fsys fs.FS, options ...GlobOption) ([]string, error) { // Remove duplicates for _, item := range matches { fp := filepath.ToSlash(item) + if expandDir != nil { + info, err := fs.Stat(fsys, fp) + if err != nil { + allErrors = append(allErrors, errors.Errorf("failed to stat matched file: %w", err)) + continue + } + if info.IsDir() { + files, err := walkMatchedDir(fsys, fp, expandDir) + if err != nil { + allErrors = append(allErrors, err) + continue + } + for _, file := range files { + if _, exists := set[file]; !exists { + set[file] = struct{}{} + result = append(result, file) + } + } + continue + } + } if _, exists := set[fp]; !exists { set[fp] = struct{}{} result = append(result, fp) @@ -157,6 +191,23 @@ func (g Glob) Files(fsys fs.FS, options ...GlobOption) ([]string, error) { return result, errors.Join(allErrors...) } +func walkMatchedDir(fsys fs.FS, dir string, include func(string, fs.DirEntry) bool) ([]string, error) { + var files []string + if err := fs.WalkDir(fsys, dir, func(path string, entry fs.DirEntry, err error) error { + if err != nil { + return err + } + if include(path, entry) { + files = append(files, filepath.ToSlash(path)) + } + return nil + }); err != nil { + return nil, errors.Errorf("failed to walk matched directory: %w", err) + } + sort.Strings(files) + return files, nil +} + func hasGlobMeta(pattern string) bool { return strings.ContainsAny(pattern, `*?[`) } diff --git a/apps/cli-go/pkg/config/config_test.go b/apps/cli-go/pkg/config/config_test.go index 6c2697ca0e..aa0570ec34 100644 --- a/apps/cli-go/pkg/config/config_test.go +++ b/apps/cli-go/pkg/config/config_test.go @@ -706,6 +706,50 @@ func TestGlobFiles(t *testing.T) { }) } +func TestGlobSQLFiles(t *testing.T) { + t.Run("expands directory entries in declared order", func(t *testing.T) { + fsys := fs.MapFS{ + "supabase/schemas/z_function.sql": &fs.MapFile{Data: []byte("select 1;")}, + "supabase/schemas/tables/a_table.sql": &fs.MapFile{Data: []byte("select 2;")}, + "supabase/schemas/tables/nested/b_table.sql": &fs.MapFile{Data: []byte("select 3;")}, + "supabase/schemas/tables/readme.md": &fs.MapFile{Data: []byte("ignored")}, + } + g := Glob{ + "supabase/schemas/z_function.sql", + "supabase/schemas/tables", + } + + files, err := g.SQLFiles(fsys) + + assert.NoError(t, err) + assert.Equal(t, []string{ + "supabase/schemas/z_function.sql", + "supabase/schemas/tables/a_table.sql", + "supabase/schemas/tables/nested/b_table.sql", + }, files) + }) + + t.Run("deduplicates explicit files and directory matches", func(t *testing.T) { + fsys := fs.MapFS{ + "supabase/database/a.sql": &fs.MapFile{Data: []byte("select 1;")}, + "supabase/database/b.sql": &fs.MapFile{Data: []byte("select 2;")}, + } + g := Glob{ + "supabase/database/a.sql", + "supabase/database", + "supabase/database/*.sql", + } + + files, err := g.SQLFiles(fsys) + + assert.NoError(t, err) + assert.Equal(t, []string{ + "supabase/database/a.sql", + "supabase/database/b.sql", + }, files) + }) +} + func TestLoadFunctionImportMap(t *testing.T) { t.Run("uses deno.json as import map when present", func(t *testing.T) { config := NewConfig() diff --git a/apps/cli-go/pkg/config/templates/config.toml b/apps/cli-go/pkg/config/templates/config.toml index 56cc27beac..98e034f8d8 100644 --- a/apps/cli-go/pkg/config/templates/config.toml +++ b/apps/cli-go/pkg/config/templates/config.toml @@ -59,8 +59,8 @@ max_client_conn = 100 [db.migrations] # If disabled, migrations will be skipped during a db push or reset. enabled = true -# Specifies an ordered list of schema files that describe your database. -# Supports glob patterns relative to supabase directory: "./schemas/*.sql" +# Specifies an ordered list of schema files, directories, or glob patterns that describe your database. +# Supports paths relative to supabase directory: "./schemas/*.sql", "./database". schema_paths = [] [db.seed] diff --git a/apps/cli-go/pkg/migration/seed.go b/apps/cli-go/pkg/migration/seed.go index 89a049878a..2e8fff71c8 100644 --- a/apps/cli-go/pkg/migration/seed.go +++ b/apps/cli-go/pkg/migration/seed.go @@ -32,7 +32,7 @@ func getRemoteSeeds(ctx context.Context, conn *pgx.Conn) (map[string]string, err } func GetPendingSeeds(ctx context.Context, locals config.Glob, conn *pgx.Conn, fsys fs.FS) ([]SeedFile, error) { - locals, err := locals.Files(fsys) + locals, err := locals.SQLFiles(fsys) if err != nil { fmt.Fprintln(os.Stderr, "WARN:", err) } diff --git a/apps/cli/src/shared/init/project-init.templates.ts b/apps/cli/src/shared/init/project-init.templates.ts index 6415e44a22..f3a41b6db8 100644 --- a/apps/cli/src/shared/init/project-init.templates.ts +++ b/apps/cli/src/shared/init/project-init.templates.ts @@ -59,8 +59,8 @@ max_client_conn = 100 [db.migrations] # If disabled, migrations will be skipped during a db push or reset. enabled = true -# Specifies an ordered list of schema files that describe your database. -# Supports glob patterns relative to supabase directory: "./schemas/*.sql" +# Specifies an ordered list of schema files, directories, or glob patterns that describe your database. +# Supports paths relative to supabase directory: "./schemas/*.sql", "./database". schema_paths = [] [db.seed] diff --git a/apps/docs/public/cli/config.schema.json b/apps/docs/public/cli/config.schema.json index 28062d0891..0503269e77 100644 --- a/apps/docs/public/cli/config.schema.json +++ b/apps/docs/public/cli/config.schema.json @@ -2420,9 +2420,9 @@ "type": "array", "items": { "type": "string", - "description": "Schema file path or glob relative to the supabase directory." + "description": "Schema file path, directory, or glob relative to the supabase directory." }, - "description": "Ordered list of schema files that describe your database.", + "description": "Ordered list of schema files, directories, or glob patterns that describe your database.", "default": [] } }, @@ -5965,9 +5965,9 @@ "type": "array", "items": { "type": "string", - "description": "Schema file path or glob relative to the supabase directory." + "description": "Schema file path, directory, or glob relative to the supabase directory." }, - "description": "Ordered list of schema files that describe your database.", + "description": "Ordered list of schema files, directories, or glob patterns that describe your database.", "default": [] } }, diff --git a/packages/config/src/db.ts b/packages/config/src/db.ts index 1bc6c57c9f..28e1fdabe6 100644 --- a/packages/config/src/db.ts +++ b/packages/config/src/db.ts @@ -132,13 +132,14 @@ export const db = Schema.Struct({ }).pipe(Schema.withDecodingDefaultKey(Effect.succeed(defaultMigrationsEnabled))), schema_paths: Schema.Array( Schema.String.annotate({ - description: "Schema file path or glob relative to the supabase directory.", + description: "Schema file path, directory, or glob relative to the supabase directory.", tags, }), ) .annotate({ default: defaultSchemaPaths, - description: "Ordered list of schema files that describe your database.", + description: + "Ordered list of schema files, directories, or glob patterns that describe your database.", tags, }) .pipe(Schema.withDecodingDefaultKey(Effect.succeed([...defaultSchemaPaths]))), From f6aff1c8da0193928f8b2baec22df50deedf4b25 Mon Sep 17 00:00:00 2001 From: kanad Date: Fri, 3 Jul 2026 03:43:03 -0700 Subject: [PATCH 29/48] chore(dev/onboarding): set up `mise`-based workflow for initial codebase setup (#5761) ## What kind of change does this PR introduce? Adds a `mise.toml` along with enhancements to our `CONTRIBUTING.md` so first-time contributors have a quick and easy way to install all the correct versions of all of the required software (Node, Bun, Go, `golangci-lint`, pnpm) to get tests running. I tried to minimize the amount of additional configuration, utilizing existing files (e.g., `.nvmrc`, `go.mod`, `package.json`) as the respective versioning sources of truth where applicable. I also chose not to commit [a `mise.lock` lockfile](https://mise.en.dev/dev-tools/mise-lock.html) for simplicity's sake but I'm happy to add it if folks want to lean into `mise` further in this repo. ## What is the current behavior? If someone clones the repo for the first time, they'll need to manually download all of the executables (Node, Bun, Go, golangci-lint) and look in a few different places to determine which versions to use. ## What is the new behavior? Now, users can install `mise` and run `mise install` and it'll install the correct versions of all the required software: https://github.com/user-attachments/assets/4cad1a5c-417c-403a-9ffb-57c88979902b --------- Co-authored-by: Andrew Valleteau Co-authored-by: Julien Goux --- .bun-version | 1 + .github/actions/setup/action.yml | 82 ++++++++++++++--------- .github/workflows/build-cli-artifacts.yml | 7 -- .github/workflows/cli-go-ci.yml | 12 ++-- .github/workflows/live-e2e.yml | 6 -- .github/workflows/test.yml | 24 ------- .nvmrc | 1 - CONTRIBUTING.md | 54 +++++++++++++++ apps/cli-go/go.mod | 2 +- mise.toml | 6 ++ package.json | 6 ++ 11 files changed, 127 insertions(+), 74 deletions(-) create mode 100644 .bun-version delete mode 100644 .nvmrc create mode 100644 mise.toml diff --git a/.bun-version b/.bun-version new file mode 100644 index 0000000000..7962dcfdb6 --- /dev/null +++ b/.bun-version @@ -0,0 +1 @@ +1.3.13 diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index cdd585bae4..12f3aab054 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -9,8 +9,8 @@ inputs: default: "" dependency-cache: description: >- - Whether to enable the pnpm dependency cache. Disable this when the job - deletes the pnpm store before exiting, otherwise the post-job cache save + Whether to enable dependency caches. Disable this when the job deletes a + cached dependency path before exiting, otherwise the post-job cache save fails with a path validation error. required: false default: "true" @@ -18,47 +18,69 @@ inputs: runs: using: "composite" steps: - - name: Resolve Bun version + - name: Resolve mise cache key + id: mise-cache-key shell: bash - run: echo "BUN_VERSION=1.3.13" >> "$GITHUB_ENV" + run: | + node <<'JS' >> "$GITHUB_OUTPUT" + const { createHash } = require("node:crypto"); + const { readFileSync } = require("node:fs"); - - name: Install Bun - id: install-bun - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - continue-on-error: true - with: - bun-version: ${{ env.BUN_VERSION }} + const packageJson = JSON.parse(readFileSync("package.json", "utf8")); + const payload = { + bun: readFileSync(".bun-version", "utf8").trim(), + mise: readFileSync("mise.toml", "utf8").trim(), + node: packageJson.devEngines?.runtime, + pnpm: packageJson.packageManager, + }; + const digest = createHash("sha256") + .update(JSON.stringify(payload)) + .digest("hex"); + console.log(`hash=${digest}`); + JS - - name: Install Bun (fallback with retries) - if: steps.install-bun.outcome == 'failure' - uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2 + - name: Install toolchains + uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4 with: - timeout_minutes: 3 - max_attempts: 5 - retry_wait_seconds: 30 - command: | - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" - echo "$HOME/.bun/bin" >> "$GITHUB_PATH" + version: 2026.7.0 + install: true + cache_key: >- + {{cache_key_prefix}}-{{platform}}-{{version}}-${{ + steps.mise-cache-key.outputs.hash }} - - name: Verify Bun + - name: Resolve pnpm store path + if: inputs.dependency-cache == 'true' + id: pnpm-store shell: bash - run: bun --version + run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" - - name: Install Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + - name: Configure pnpm dependency cache + if: inputs.dependency-cache == 'true' + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - node-version-file: .nvmrc - package-manager-cache: false + path: ${{ steps.pnpm-store.outputs.path }} + key: pnpm-store-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + pnpm-store-${{ runner.os }}-${{ runner.arch }}- - - name: Enable Corepack + - name: Resolve Go cache paths + if: inputs.dependency-cache == 'true' + id: go-cache shell: bash - run: npm install --global --force corepack && corepack enable + run: | + echo "mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" + echo "build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" - - name: Configure dependency cache + - name: Configure Go dependency cache if: inputs.dependency-cache == 'true' - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - cache: pnpm + path: | + ${{ steps.go-cache.outputs.mod }} + ${{ steps.go-cache.outputs.build }} + key: go-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('apps/cli-go/go.mod', 'apps/cli-go/go.sum') }} + restore-keys: | + go-cache-${{ runner.os }}-${{ runner.arch }}- - name: Install dependencies shell: bash diff --git a/.github/workflows/build-cli-artifacts.yml b/.github/workflows/build-cli-artifacts.yml index 336c125cbf..8faf9d0377 100644 --- a/.github/workflows/build-cli-artifacts.yml +++ b/.github/workflows/build-cli-artifacts.yml @@ -76,13 +76,6 @@ jobs: with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 - with: - go-version-file: apps/cli-go/go.mod - cache: true - cache-dependency-path: apps/cli-go/go.sum - - name: Pre-download Go modules working-directory: apps/cli-go run: go mod download -x diff --git a/.github/workflows/cli-go-ci.yml b/.github/workflows/cli-go-ci.yml index 0fc58f8c37..fdf2dcb52f 100644 --- a/.github/workflows/cli-go-ci.yml +++ b/.github/workflows/cli-go-ci.yml @@ -78,16 +78,18 @@ jobs: with: persist-credentials: false - - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4 with: - go-version-file: apps/cli-go/go.mod - # Linter requires no cache - cache: false + version: 2026.7.0 + install: true + install_args: >- + go + go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint - uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1 with: args: --timeout 5m --verbose - version: latest + install-mode: none only-new-issues: true working-directory: apps/cli-go diff --git a/.github/workflows/live-e2e.yml b/.github/workflows/live-e2e.yml index a2954fcbde..efdb4a8374 100644 --- a/.github/workflows/live-e2e.yml +++ b/.github/workflows/live-e2e.yml @@ -113,12 +113,6 @@ jobs: - name: Setup uses: ./.github/actions/setup - - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 - with: - go-version-file: apps/cli-go/go.mod - cache-dependency-path: apps/cli-go/go.sum - # Build the Go binary for every target: `go` runs it directly and # `ts-legacy` shells out to it for most commands. - name: Build Go CLI diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 273971448e..571fb5fc62 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,14 +43,6 @@ jobs: uses: ./.github/actions/setup with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 - with: - go-version-file: apps/cli-go/go.mod - cache-dependency-path: apps/cli-go/go.sum - - name: Install golangci-lint - run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest && - echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - name: Unlock keyring (for cli-go keyring tests) uses: t1m0thyj/unlock-keyring@cbcf205c879ebd86add70bab3a6abfcce59a5cae # v1.2.0 @@ -74,14 +66,6 @@ jobs: uses: ./.github/actions/setup with: dependency-firewall-token: ${{ secrets.DF_FIREWALL_TOKEN }} - - name: Setup Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 - with: - go-version-file: apps/cli-go/go.mod - cache-dependency-path: apps/cli-go/go.sum - - name: Install golangci-lint - run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest && - echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - name: Unlock keyring (for cli-go keyring tests) uses: t1m0thyj/unlock-keyring@cbcf205c879ebd86add70bab3a6abfcce59a5cae # v1.2.0 @@ -144,14 +128,6 @@ jobs: key: go-cli-${{ runner.os }}-${{ hashFiles('apps/cli-go/**/*.go', 'apps/cli-go/go.mod', 'apps/cli-go/go.sum') }} - - name: Setup Go - if: steps.detect.outputs.cli_e2e == 'true' && - steps.cache-go-binary.outputs.cache-hit != 'true' - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 - with: - go-version-file: apps/cli-go/go.mod - cache-dependency-path: apps/cli-go/go.sum - - name: Build Go CLI if: steps.detect.outputs.cli_e2e == 'true' && steps.cache-go-binary.outputs.cache-hit != 'true' diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index cabf43b5dd..0000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cfa452dd09..eff0cec734 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,60 @@ Bun monorepo for exploring the next generation of the Supabase CLI and local dev ## Setup +### Tool versions + +This repo pins the versions of Node, Bun, Go, pnpm, and golangci-lint that contributors are expected to build against, and uses [`mise`](https://mise.jdx.dev/) — a polyglot version manager — to install and activate Node, Bun, Go, pnpm, and golangci-lint automatically. If you don't already have these tools installed, `mise` is a great way to get up and running quickly. + +#### Installing mise + +```sh +# macOS / Linux +curl https://mise.run | sh + +# macOS via Homebrew +brew install mise +``` + +See the [`mise` installation docs](https://mise.jdx.dev/getting-started.html) for other package managers (apt, dnf, cargo, npm, Windows, …). + +`mise` needs to hook into your shell so it can inject the right tool versions into your `PATH` as you move between directories. Follow the `mise activate` instructions [in this section](https://mise.jdx.dev/getting-started.html#activate-mise) to add the activation line for your shell to its startup file. + +This repo relies on `mise` support for reading Node and pnpm versions from `package.json`, so use mise `2026.7.0` or newer. + +#### Installing the pinned tool versions + +Trust this repo's `mise.toml` once from the repo root so `mise` can read the project setting that enables idiomatic version files: + +```sh +mise trust +``` + +Then install the pinned tool versions: + +```sh +mise install +``` + +After `mise trust`, `mise` resolves the versions this repo expects from a handful of files, rather than hardcoding them all in one place: + +| Tool | Version source | +| --- | --- | +| Bun | `.bun-version` | +| Node.js | `devEngines.runtime` field in `package.json` | +| pnpm | `packageManager` field in `package.json` | +| Go | `mise.toml` | +| golangci-lint | `mise.toml` | + +The Go and golangci-lint entries in `mise.toml` are intentionally temporary while the Go CLI remains in the repo. The canonical Go module metadata still lives in `apps/cli-go/go.mod`; keep the `mise.toml` entries aligned only until the Go code is removed. + +Once installed, `mise` activates these versions automatically whenever your shell is inside this repo — no manual `nvm use`, `gvm use`, or similar switching required. + +#### Without mise + +`mise` is not required. If you already have Bun, Node, pnpm, and Go installed and managed some other way, just make sure your versions match the ones pinned in `.bun-version`, `mise.toml`, `package.json`, and `apps/cli-go/go.mod`. + +### Install dependencies + Install workspace dependencies: ```sh diff --git a/apps/cli-go/go.mod b/apps/cli-go/go.mod index e11a2d06fc..c9961ac356 100644 --- a/apps/cli-go/go.mod +++ b/apps/cli-go/go.mod @@ -1,6 +1,6 @@ module github.com/supabase/cli -go 1.25.5 +go 1.25.11 require ( github.com/BurntSushi/toml v1.6.0 diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000000..78f7662769 --- /dev/null +++ b/mise.toml @@ -0,0 +1,6 @@ +[tools] +go = "1.25.11" +"go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint" = "2.1.6" + +[settings] +idiomatic_version_file_enable_tools = ["node", "bun", "pnpm"] diff --git a/package.json b/package.json index 0afc996a05..d835f5b2c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,12 @@ { "name": "@supabase/root", "private": true, + "devEngines": { + "runtime": { + "name": "node", + "version": "24" + } + }, "scripts": { "test:core": "nx run-many -t test:unit test:integration --coverage.enabled", "test:e2e": "nx run-many -t test:e2e", From c57097155103e4338d4689323ca16ed0afeaa4ab Mon Sep 17 00:00:00 2001 From: kanad Date: Fri, 3 Jul 2026 10:29:37 -0700 Subject: [PATCH 30/48] chore(ci/mise): add lockfile, simplify caching behavior (#5778) ## What kind of change does this PR introduce? Follow-up enhancement to https://github.com/supabase/cli/pull/5761. Adds [a `mise.lock` lockfile](https://mise.jdx.dev/dev-tools/mise-lock.html) and uses that as our cache key (as opposed to building our own). ## What is the current behavior? `mise` cache in CI is built by manually grabbing all of the source files and creating our own hash. ## What is the new behavior? Uses the `mise.lock` file to create the file hash. Hopefully same caching behavior as before. --- .github/actions/setup/action.yml | 25 ----- CONTRIBUTING.md | 4 +- mise.lock | 151 +++++++++++++++++++++++++++++++ mise.toml | 3 + 4 files changed, 156 insertions(+), 27 deletions(-) create mode 100644 mise.lock diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 12f3aab054..d95b35caf4 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -18,35 +18,10 @@ inputs: runs: using: "composite" steps: - - name: Resolve mise cache key - id: mise-cache-key - shell: bash - run: | - node <<'JS' >> "$GITHUB_OUTPUT" - const { createHash } = require("node:crypto"); - const { readFileSync } = require("node:fs"); - - const packageJson = JSON.parse(readFileSync("package.json", "utf8")); - const payload = { - bun: readFileSync(".bun-version", "utf8").trim(), - mise: readFileSync("mise.toml", "utf8").trim(), - node: packageJson.devEngines?.runtime, - pnpm: packageJson.packageManager, - }; - const digest = createHash("sha256") - .update(JSON.stringify(payload)) - .digest("hex"); - console.log(`hash=${digest}`); - JS - - name: Install toolchains uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4 with: version: 2026.7.0 - install: true - cache_key: >- - {{cache_key_prefix}}-{{platform}}-{{version}}-${{ - steps.mise-cache-key.outputs.hash }} - name: Resolve pnpm store path if: inputs.dependency-cache == 'true' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eff0cec734..4312076c56 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Bun monorepo for exploring the next generation of the Supabase CLI and local dev ### Tool versions -This repo pins the versions of Node, Bun, Go, pnpm, and golangci-lint that contributors are expected to build against, and uses [`mise`](https://mise.jdx.dev/) — a polyglot version manager — to install and activate Node, Bun, Go, pnpm, and golangci-lint automatically. If you don't already have these tools installed, `mise` is a great way to get up and running quickly. +This repo pins the versions of Node, Bun, Go, pnpm, and golangci-lint that contributors are expected to build against, and uses [`mise`](https://mise.jdx.dev/) — a polyglot version manager — to install and activate them automatically. If you don't already have these tools installed, `mise` is a great way to get up and running quickly. #### Installing mise @@ -38,7 +38,7 @@ Then install the pinned tool versions: mise install ``` -After `mise trust`, `mise` resolves the versions this repo expects from a handful of files, rather than hardcoding them all in one place: +`mise install` resolves the versions this repo expects from a handful of files, rather than hardcoding them all in one place: | Tool | Version source | | --- | --- | diff --git a/mise.lock b/mise.lock new file mode 100644 index 0000000000..df835f53f2 --- /dev/null +++ b/mise.lock @@ -0,0 +1,151 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html + +[[tools.bun]] +version = "1.3.13" +backend = "core:bun" + +[tools.bun."platforms.linux-arm64"] +checksum = "sha256:70bae41b3908b0a120e1e58c5c8af30e74afae3b8d11b0d3fdd8e787ddfb4b22" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-linux-aarch64.zip" + +[tools.bun."platforms.linux-arm64-musl"] +checksum = "sha256:5385e978107ce4934298d8d6afe9bfbb898683f6cc23e6753a0da60bc60c5b81" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-linux-aarch64-musl.zip" + +[tools.bun."platforms.linux-x64"] +checksum = "sha256:79c0771fa8b92c33aae41e15a0e0d307ea99d0e2f00317c71c6c53237a78e25a" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-linux-x64.zip" + +[tools.bun."platforms.linux-x64-baseline"] +checksum = "sha256:9d8a24292a7068090205daac0a5a223f5f69736f5287e37bf88d3b4031edc750" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-linux-x64-baseline.zip" + +[tools.bun."platforms.linux-x64-musl"] +checksum = "sha256:5b91a48f0b00df9fd2da8bff1a795d2659d842da966432969203f25da19d1c74" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-linux-x64-musl.zip" + +[tools.bun."platforms.linux-x64-musl-baseline"] +checksum = "sha256:88ca7c7ad235b498f549eea2f770f434e9f0f5e9ba95168a2d3a1f235184c394" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-linux-x64-musl-baseline.zip" + +[tools.bun."platforms.macos-arm64"] +checksum = "sha256:5467e3f65dba526b9fea98f0cce04efafc0c63e169733ec27b876a3ad32da190" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-darwin-aarch64.zip" + +[tools.bun."platforms.macos-x64"] +checksum = "sha256:e5a6c8b64f419925232d111ecb13e25f0abf55e54f792341f987623fd0778009" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-darwin-x64.zip" + +[tools.bun."platforms.macos-x64-baseline"] +checksum = "sha256:a98ba6a480f22fda9b343626b906a4e26aa53618bf85d2bc5928ecf2ba45f0ed" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-darwin-x64-baseline.zip" + +[tools.bun."platforms.windows-x64"] +checksum = "sha256:85b14f3e0584218e9b63407b3aa6b90c4835ec5c32435c1f12cb6fc13667c7c9" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-windows-x64.zip" + +[tools.bun."platforms.windows-x64-baseline"] +checksum = "sha256:c68c7903c1190101590cc1b2129835f47211b3b37ae87759f2b97d6534aa3ad1" +url = "https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-windows-x64-baseline.zip" + +[[tools.go]] +version = "1.25.11" +backend = "core:go" + +[tools.go."platforms.linux-arm64"] +checksum = "sha256:c30bf9e156a54ea4e31fbbbf31a712b32734b58cc9a22426fa5ee632d0885124" +url = "https://dl.google.com/go/go1.25.11.linux-arm64.tar.gz" + +[tools.go."platforms.linux-arm64-musl"] +checksum = "sha256:c30bf9e156a54ea4e31fbbbf31a712b32734b58cc9a22426fa5ee632d0885124" +url = "https://dl.google.com/go/go1.25.11.linux-arm64.tar.gz" + +[tools.go."platforms.linux-x64"] +checksum = "sha256:34f14304e856893f4ba30c2cacfe93906e9de7915c5f6aaaf3a81cdccd7ba30b" +url = "https://dl.google.com/go/go1.25.11.linux-amd64.tar.gz" + +[tools.go."platforms.linux-x64-musl"] +checksum = "sha256:34f14304e856893f4ba30c2cacfe93906e9de7915c5f6aaaf3a81cdccd7ba30b" +url = "https://dl.google.com/go/go1.25.11.linux-amd64.tar.gz" + +[tools.go."platforms.macos-arm64"] +checksum = "sha256:cd8d4920e7930d55da1a5a57ba43a64b1305f71cdf2ca3c76cd8c549272b1680" +url = "https://dl.google.com/go/go1.25.11.darwin-arm64.tar.gz" + +[tools.go."platforms.macos-x64"] +checksum = "sha256:26d0ee4071de42b5c332337db9fdd234072877697c547e46e85efb0f59507c66" +url = "https://dl.google.com/go/go1.25.11.darwin-amd64.tar.gz" + +[tools.go."platforms.windows-x64"] +checksum = "sha256:b7401f1b41517428e537493316256fb7cf03c66a130a0103ab07f3a2152e2112" +url = "https://dl.google.com/go/go1.25.11.windows-amd64.zip" + +[[tools."go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint"]] +version = "2.1.6" +backend = "go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint" + +[[tools.node]] +version = "24.18.0" +backend = "core:node" + +[tools.node."platforms.linux-arm64"] +checksum = "sha256:6b4484c2190274175df9aa8f28e2d758a819cb1c1fe6ab481e2f95b463ab8508" +url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-arm64.tar.gz" + +[tools.node."platforms.linux-arm64-musl"] +checksum = "sha256:b1c6c2dc31b46dd8fb2322f4fe75b07e775c5120bc37251deeea28f529d4567b" +url = "https://unofficial-builds.nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-arm64-musl.tar.gz" + +[tools.node."platforms.linux-x64"] +checksum = "sha256:783130984963db7ba9cbd01089eaf2c2efb055c7c1693c943174b967b3050cb8" +url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.gz" + +[tools.node."platforms.linux-x64-musl"] +checksum = "sha256:ea58409911e141ec6b19d9178efa2d9185a13295005b1cbf5521b3157eed1d95" +url = "https://unofficial-builds.nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-x64-musl.tar.gz" + +[tools.node."platforms.macos-arm64"] +checksum = "sha256:e1a97e14c99c803e96c7339403282ea05a499c32f8d83defe9ef5ec66f979ed1" +url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-darwin-arm64.tar.gz" + +[tools.node."platforms.macos-x64"] +checksum = "sha256:dfd0dbd3e721503434df7b7205e719f61b3a3a31b2bcf9729b8b91fea240f080" +url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-darwin-x64.tar.gz" + +[tools.node."platforms.windows-x64"] +checksum = "sha256:0ae68406b42d7725661da979b1403ec9926da205c6770827f33aac9d8f26e821" +url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-win-x64.zip" + +[[tools.pnpm]] +version = "11.4.0" +backend = "aqua:pnpm/pnpm" + +[tools.pnpm."platforms.linux-arm64"] +checksum = "sha256:cc38ebd5b2610a5744f84576b963c49e6609a8df5aed714ae3de749998d4478c" +url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-arm64.tar.gz" +provenance = "github-attestations" + +[tools.pnpm."platforms.linux-arm64-musl"] +checksum = "sha256:a1e2ec9123c709fd04b704227cfcf3b50cd2bbbc1bd39d2df414530b5697eb75" +url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-arm64-musl.tar.gz" +provenance = "github-attestations" + +[tools.pnpm."platforms.linux-x64"] +checksum = "sha256:f3f8d1217eef013bbc71a24d52efb1f1041e4aff55edd80e0b08e25f409305a4" +url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-x64.tar.gz" +provenance = "github-attestations" + +[tools.pnpm."platforms.linux-x64-musl"] +checksum = "sha256:60010ad00a96b71e20d1618acaca7a71395e710cbd5e88946c030a1d07c56916" +url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-x64-musl.tar.gz" +provenance = "github-attestations" + +[tools.pnpm."platforms.macos-arm64"] +checksum = "sha256:ba59014c2c1ce8b76af9f559385206a2623de4ff2b694b5c91598a8f44abb4e2" +url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-darwin-arm64.tar.gz" +provenance = "github-attestations" + +[tools.pnpm."platforms.windows-x64"] +checksum = "sha256:84ce90e38bc0b1164173eb853a0fbffc7edcb050cb0d5c8ce4ca609f5c808e0a" +url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-win32-x64.zip" +provenance = "github-attestations" diff --git a/mise.toml b/mise.toml index 78f7662769..1164e695ff 100644 --- a/mise.toml +++ b/mise.toml @@ -1,6 +1,9 @@ +min_version = '2026.7.0' + [tools] go = "1.25.11" "go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint" = "2.1.6" [settings] idiomatic_version_file_enable_tools = ["node", "bun", "pnpm"] +lockfile = true From 3f2b9a63e67f29bb5a36da2dd5b0d90f7f2be8c9 Mon Sep 17 00:00:00 2001 From: kanad Date: Fri, 3 Jul 2026 10:46:15 -0700 Subject: [PATCH 31/48] chore(deps): bump `golangci-lint` to latest (#5779) ## What kind of change does this PR introduce? Upgrades `golangci-lint` to the latest version, per my findings in https://github.com/supabase/cli/pull/5778#discussion_r3520926786 ## What is the current behavior? The build succeeds, but the `golangci-lint` version isn't pointing to an actual commit in the repo: ``` $ golangci-lint --version golangci-lint has version v2.1.6 built with go1.25.11 from (unknown, modified: ?, mod sum: "h1:LXqShFfAGM5BDzEOWD2SL1IzJAgUOqES/HRBsfKjI+w=") on (unknown) ``` ## What is the new behavior? The `golangci-lint` version now points to an actual commit in the repo: ```sh $ golangci-lint --version golangci-lint has version 2.12.2 built with go1.26.2 from c0d3ddc9 on 2026-05-06T11:07:58Z ``` Hopefully no other changes in behavior! --- .github/workflows/cli-go-ci.yml | 2 +- apps/cli-go/go.mod | 174 +++++++------- apps/cli-go/go.sum | 412 ++++++++++++++++---------------- mise.lock | 128 +++++++++- mise.toml | 2 +- 5 files changed, 421 insertions(+), 297 deletions(-) diff --git a/.github/workflows/cli-go-ci.yml b/.github/workflows/cli-go-ci.yml index fdf2dcb52f..2178446d15 100644 --- a/.github/workflows/cli-go-ci.yml +++ b/.github/workflows/cli-go-ci.yml @@ -84,7 +84,7 @@ jobs: install: true install_args: >- go - go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint + golangci-lint - uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1 with: diff --git a/apps/cli-go/go.mod b/apps/cli-go/go.mod index c9961ac356..8205e55032 100644 --- a/apps/cli-go/go.mod +++ b/apps/cli-go/go.mod @@ -65,31 +65,40 @@ require ( require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect + charm.land/lipgloss/v2 v2.0.3 // indirect + codeberg.org/chavacava/garif v0.2.0 // indirect + codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect dario.cat/mergo v1.0.2 // indirect - github.com/4meepo/tagalign v1.4.2 // indirect - github.com/Abirdcfly/dupword v0.1.3 // indirect - github.com/Antonboom/errname v1.1.0 // indirect - github.com/Antonboom/nilnil v1.1.0 // indirect - github.com/Antonboom/testifylint v1.6.1 // indirect + dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect + dev.gaijin.team/go/golib v0.6.0 // indirect + github.com/4meepo/tagalign v1.4.3 // indirect + github.com/Abirdcfly/dupword v0.1.7 // indirect + github.com/AdminBenni/iota-mixing v1.0.0 // indirect + github.com/AlwxSin/noinlineerr v1.0.5 // indirect + github.com/Antonboom/errname v1.1.1 // indirect + github.com/Antonboom/nilnil v1.1.1 // indirect + github.com/Antonboom/testifylint v1.6.4 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/ClickHouse/clickhouse-go-linter v1.2.0 // indirect github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e // indirect - github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect - github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Djarvur/go-err113 v0.1.1 // indirect + github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/MirrexOne/unqueryvet v1.5.4 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // 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/chroma/v2 v2.24.1 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.6 // indirect - github.com/alexkohler/prealloc v1.0.0 // indirect + github.com/alexkohler/prealloc v1.1.0 // indirect + github.com/alfatraining/structtag v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/alingse/nilnesserr v0.2.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/ashanbrown/forbidigo v1.6.0 // indirect - github.com/ashanbrown/makezero v1.2.0 // indirect + github.com/ashanbrown/forbidigo/v2 v2.3.1 // indirect + github.com/ashanbrown/makezero/v2 v2.2.1 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.32.7 // indirect @@ -112,26 +121,29 @@ require ( github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bombsimon/wsl/v4 v4.7.0 // indirect + github.com/bombsimon/wsl/v5 v5.8.0 // indirect github.com/breml/bidichk v0.3.3 // indirect github.com/breml/errchkjson v0.4.1 // indirect github.com/buger/goterm v1.0.4 // indirect - github.com/butuzov/ireturn v0.4.0 // indirect + github.com/butuzov/ireturn v0.4.1 // indirect github.com/butuzov/mirror v1.3.0 // indirect - github.com/catenacyber/perfsprint v0.9.1 // indirect - github.com/ccojocar/zxcvbn-go v1.0.2 // indirect + github.com/catenacyber/perfsprint v0.10.1 // indirect + github.com/ccojocar/zxcvbn-go v1.0.4 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/charithe/durationcheck v0.0.10 // indirect - github.com/charmbracelet/colorprofile v0.4.1 // indirect + github.com/charithe/durationcheck v0.0.11 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect - github.com/charmbracelet/x/ansi v0.11.6 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect + github.com/charmbracelet/x/ansi v0.11.7 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect github.com/charmbracelet/x/term v0.2.2 // indirect - github.com/chavacava/garif v0.1.0 // indirect + github.com/charmbracelet/x/termios v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.2.2 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect - github.com/clipperhouse/displaywidth v0.10.0 // indirect - github.com/clipperhouse/uax29/v2 v2.6.0 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/console v1.0.5 // indirect github.com/containerd/containerd/api v1.10.0 // indirect @@ -145,14 +157,14 @@ require ( github.com/containers/storage v1.59.1 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect - github.com/daixiang0/gci v0.13.6 // indirect + github.com/daixiang0/gci v0.13.7 // indirect github.com/danieljoos/wincred v1.2.3 // indirect github.com/dave/dst v0.27.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/dlclark/regexp2 v1.12.0 // indirect github.com/dnephin/pflag v1.0.7 // indirect github.com/docker/buildx v0.29.1 // indirect github.com/docker/cli-docs-tool v0.10.0 // indirect @@ -168,7 +180,7 @@ require ( github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/ethereum/go-ethereum v1.17.0 // indirect github.com/ettle/strcase v0.2.0 // indirect - github.com/fatih/color v1.18.0 // indirect + github.com/fatih/color v1.19.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/firefart/nonamedreturns v1.0.6 // indirect @@ -178,8 +190,8 @@ require ( 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/ghostiam/protogetter v0.3.20 // indirect + github.com/go-critic/go-critic v0.14.3 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -212,38 +224,42 @@ 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/godoc-lint/godoc-lint v0.11.2 // 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 - github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect - github.com/golangci/go-printf-func-name v0.1.0 // indirect + github.com/golangci/asciicheck v0.5.0 // indirect + github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect + github.com/golangci/go-printf-func-name v0.1.1 // indirect github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect - github.com/golangci/golangci-lint/v2 v2.1.6 // indirect - github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 // indirect - github.com/golangci/misspell v0.6.0 // indirect - github.com/golangci/plugin-module-register v0.1.1 // indirect + github.com/golangci/golangci-lint/v2 v2.12.2 // indirect + github.com/golangci/golines v0.15.0 // indirect + github.com/golangci/misspell v0.8.0 // indirect + github.com/golangci/plugin-module-register v0.1.2 // indirect github.com/golangci/revgrep v0.8.0 // indirect + github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba // indirect + github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // 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/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/gordonklaus/ineffassign v0.1.0 // indirect + github.com/gordonklaus/ineffassign v0.2.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/mux v1.8.1 // 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/gostaticanalysis/nilerr v0.1.2 // 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 github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/in-toto/attestation v1.1.2 // indirect @@ -257,45 +273,46 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgtype v1.14.4 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jgautheron/goconst v1.8.1 // indirect - github.com/jingyugao/rowserrcheck v1.1.1 // indirect - github.com/jjti/go-spancheck v0.6.4 // indirect + github.com/jgautheron/goconst v1.10.0 // indirect + github.com/jjti/go-spancheck v0.6.5 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/julz/importas v0.2.0 // indirect - github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect + github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/kisielk/errcheck v1.9.0 // indirect + github.com/kisielk/errcheck v1.10.0 // indirect github.com/kkHAIKE/contextcheck v1.1.6 // 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 - github.com/kunwardeep/paralleltest v1.0.14 // indirect + github.com/kulti/thelper v0.7.1 // indirect + github.com/kunwardeep/paralleltest v1.0.15 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect - github.com/ldez/exptostd v0.4.3 // indirect - github.com/ldez/gomoddirectives v0.6.1 // indirect - github.com/ldez/grignotin v0.9.0 // indirect - github.com/ldez/tagliatelle v0.7.1 // indirect - github.com/ldez/usetesting v0.4.3 // indirect + github.com/ldez/exptostd v0.4.5 // indirect + github.com/ldez/gomoddirectives v0.8.0 // indirect + github.com/ldez/grignotin v0.10.1 // indirect + github.com/ldez/structtags v0.6.1 // indirect + github.com/ldez/tagliatelle v0.7.2 // indirect + github.com/ldez/usetesting v0.5.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/lib/pq v1.10.9 // indirect - github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/lib/pq v1.12.3 // indirect + github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/macabu/inamedparam v0.2.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect - github.com/manuelarte/funcorder v0.2.1 // indirect - github.com/maratori/testableexamples v1.0.0 // indirect - github.com/maratori/testpackage v1.1.1 // indirect + github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect + github.com/manuelarte/funcorder v0.6.0 // indirect + github.com/maratori/testableexamples v1.0.1 // indirect + github.com/maratori/testpackage v1.1.2 // indirect github.com/matoous/godox v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.23 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect - github.com/mgechev/revive v1.9.0 // indirect + github.com/mgechev/revive v1.15.0 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -332,7 +349,7 @@ require ( github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.19.1 // indirect + github.com/nunnatsa/ginkgolinter v0.23.0 // indirect github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect github.com/oapi-codegen/runtime v1.4.2 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect @@ -343,13 +360,12 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pelletier/go-toml/v2 v2.3.1 // indirect github.com/perimeterx/marshmallow v1.1.5 // 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 - github.com/polyfloyd/go-errorlint v1.8.0 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.4 // indirect @@ -363,15 +379,16 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect - github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect + github.com/ryancurrah/gomodguard/v2 v2.1.3 // indirect + github.com/ryanrolds/sqlclosecheck v0.6.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect - github.com/securego/gosec/v2 v2.22.3 // indirect + github.com/securego/gosec/v2 v2.26.1 // 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 @@ -379,21 +396,20 @@ require ( 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 - github.com/sonatard/noctx v0.1.0 // indirect - github.com/sourcegraph/go-diff v0.7.0 // indirect + github.com/sonatard/noctx v0.5.1 // indirect + github.com/sourcegraph/go-diff v0.8.0 // indirect github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect - github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect + github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tdakkota/asciicheck v0.4.1 // indirect - github.com/tetafro/godot v1.5.1 // indirect + github.com/tetafro/godot v1.5.6 // indirect github.com/theupdateframework/notary v0.7.0 // indirect github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect - github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect + github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 // indirect github.com/timonwong/loggercheck v0.11.0 // indirect - github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect + github.com/tomarrell/wrapcheck/v2 v2.12.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-20251211185533-a2aa163d723f // indirect @@ -402,8 +418,8 @@ require ( github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect - github.com/uudashr/gocognit v1.2.0 // indirect - github.com/uudashr/iface v1.3.1 // indirect + github.com/uudashr/gocognit v1.2.1 // indirect + github.com/uudashr/iface v1.4.2 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect @@ -420,9 +436,10 @@ require ( github.com/yuin/goldmark-emoji v1.0.6 // indirect github.com/zclconf/go-cty v1.17.0 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect - go-simpler.org/musttag v0.13.1 // indirect - go-simpler.org/sloglint v0.11.0 // indirect - go.augendre.info/fatcontext v0.8.0 // indirect + go-simpler.org/musttag v0.14.0 // indirect + go-simpler.org/sloglint v0.12.0 // indirect + go.augendre.info/arangolint v0.4.0 // indirect + go.augendre.info/fatcontext v0.9.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.63.0 // indirect @@ -437,20 +454,17 @@ require ( 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.53.0 // indirect - golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect golang.org/x/sync v0.21.0 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/text v0.38.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.45.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-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/protobuf v1.36.11 // indirect @@ -460,15 +474,15 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gotest.tools/gotestsum v1.12.2 // indirect - honnef.co/go/tools v0.6.1 // indirect + honnef.co/go/tools v0.7.0 // 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-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 + mvdan.cc/gofumpt v0.9.2 // indirect + mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // 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 diff --git a/apps/cli-go/go.sum b/apps/cli-go/go.sum index 1069d1a832..948d460a26 100644 --- a/apps/cli-go/go.sum +++ b/apps/cli-go/go.sum @@ -2,43 +2,59 @@ 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= +charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= +charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= +codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= +codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= +codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI= +codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8= 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= +dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= +dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= +dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= +dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= 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= -github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= +github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= +github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= +github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ= +github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Antonboom/errname v1.1.0 h1:A+ucvdpMwlo/myWrkHEUEBWc/xuXdud23S8tmTb/oAE= -github.com/Antonboom/errname v1.1.0/go.mod h1:O1NMrzgUcVBGIfi3xlVuvX8Q/VP/73sseCaAppfjqZw= -github.com/Antonboom/nilnil v1.1.0 h1:jGxJxjgYS3VUUtOTNk8Z1icwT5ESpLH/426fjmQG+ng= -github.com/Antonboom/nilnil v1.1.0/go.mod h1:b7sAlogQjFa1wV8jUW3o4PMzDVFLbTux+xnQdvzdcIE= -github.com/Antonboom/testifylint v1.6.1 h1:6ZSytkFWatT8mwZlmRCHkWz1gPi+q6UBSbieji2Gj/o= -github.com/Antonboom/testifylint v1.6.1/go.mod h1:k+nEkathI2NFjKO6HvwmSrbzUcQ6FAnbZV+ZRrnXPLI= +github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo= +github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY= +github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= +github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= +github.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q= +github.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ= +github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ= +github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II= +github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ= +github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/ClickHouse/clickhouse-go-linter v1.2.0 h1:zbm174up3hTKjp0wKZVnTzRiG7tSF5XZF0FJG/MuCBI= +github.com/ClickHouse/clickhouse-go-linter v1.2.0/go.mod h1:pLorS7ffPTfuUV9M0SJgfHA/h/WQPQUk2FWG9x74cQ4= github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e h1:rd4bOvKmDIx0WeTv9Qz+hghsgyjikFiPrseXHlKepO0= github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= +github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= +github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.14.1 h1:CMuB3fqQVfPdhyXhUqYdUmPUIOhJkmghCx3dJet8Cqs= github.com/Microsoft/hcsshim v0.14.1/go.mod h1:VnzvPLyWUhxiPVsJ31P6XadxCcTogTguBFDy/1GR/OM= +github.com/MirrexOne/unqueryvet v1.5.4 h1:38QOxShO7JmMWT+eCdDMbcUgGCOeJphVkzzRgyLJgsQ= +github.com/MirrexOne/unqueryvet v1.5.4/go.mod h1:fs9Zq6eh1LRIhsDIsxf9PONVUjYdFHdtkHIgZdJnyPU= 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= @@ -52,18 +68,20 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= -github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= +github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM= +github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI= github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= -github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= -github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= -github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= -github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/alexkohler/prealloc v1.1.0 h1:cKGRBqlXw5iyQGLYhrXrDlcHxugXpTq4tQ5c91wkf8M= +github.com/alexkohler/prealloc v1.1.0/go.mod h1:fT39Jge3bQrfA7nPMDngUfvUbQGQeJyGQnR+913SCig= +github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc= +github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= @@ -80,10 +98,10 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= -github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= -github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= -github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= +github.com/ashanbrown/forbidigo/v2 v2.3.1 h1:KAZijvQ7zeIBKbhikT4jCm0TLYXC4u78bTiLh/8JROI= +github.com/ashanbrown/forbidigo/v2 v2.3.1/go.mod h1:2QDkLTzU6TV937eFROamXrW92M3paehdae4HCDCOZCM= +github.com/ashanbrown/makezero/v2 v2.2.1 h1:A7uU8dgB1PA9aelTxHMfHIQ8Qev8AB3JLxJUBUsejqM= +github.com/ashanbrown/makezero/v2 v2.2.1/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY= 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.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= @@ -116,8 +134,8 @@ 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= -github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= +github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= +github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -137,6 +155,8 @@ github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvF github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= +github.com/bombsimon/wsl/v5 v5.8.0 h1:JTkyfs4yl8SPejrCF2GdABXE+mO1WvM7iUYzRWlsxDs= +github.com/bombsimon/wsl/v5 v5.8.0/go.mod h1:AbOLsulgkqP4ZnitHf9gwPtCOGlrzkk0jb0uNxRSY0o= github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= @@ -149,55 +169,59 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E= -github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70= +github.com/butuzov/ireturn v0.4.1 h1:vWb3NO4t77iku/sjCQ/2pHTQeOmxEhjIriJqRLg1Y+I= +github.com/butuzov/ireturn v0.4.1/go.mod h1:q+DXKzTDV5guNuXLnIab9fKXizTn2miZHLhxH7V/GB4= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= -github.com/catenacyber/perfsprint v0.9.1 h1:5LlTp4RwTooQjJCvGEFV6XksZvWE7wCOUvjD2z0vls0= -github.com/catenacyber/perfsprint v0.9.1/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= -github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= -github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= +github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ= +github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc= +github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= +github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= -github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= +github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk= +github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4= github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc= github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= -github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= -github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= github.com/charmbracelet/glamour v1.0.0 h1:AWMLOVFHTsysl4WV8T8QgkQ0s/ZNZo7CiE4WKhk8l08= github.com/charmbracelet/glamour v1.0.0/go.mod h1:DSdohgOBkMr2ZQNhw4LZxSGpx3SvpeujNoXrQyH2hxo= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= -github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= -github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= +github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 h1:OqDqxQZliC7C8adA7KjelW3OjtAxREfeHkNcd66wpeI= +github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI= +github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= +github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI= github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= -github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= -github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= +github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= -github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= -github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= -github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= -github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= @@ -254,8 +278,8 @@ github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+f github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= 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/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= +github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ= github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= @@ -273,8 +297,8 @@ github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okeg github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= -github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8= +github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= github.com/docker/buildx v0.29.1 h1:58hxM5Z4mnNje3G5NKfULT9xCr8ooM8XFtlfUK9bKaA= @@ -325,8 +349,8 @@ github.com/ethereum/go-ethereum v1.17.0 h1:2D+1Fe23CwZ5tQoAS5DfwKFNI1HGcTwi65/kR github.com/ethereum/go-ethereum v1.17.0/go.mod h1:2W3msvdosS/MCWytpqTcqgFiRYbTH59FxDJzqah120o= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -351,12 +375,12 @@ github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4p github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/getsentry/sentry-go v0.47.0 h1:AnSMSyrYA5qZCIN/2xpgAAwv63sVULV+vBq37ajouc8= github.com/getsentry/sentry-go v0.47.0/go.mod h1:h+b4VHpKnK7aUXB5wc+KDnPgp9ZtfliRD4eV85FbiSA= -github.com/ghostiam/protogetter v0.3.15 h1:1KF5sXel0HE48zh1/vn0Loiw25A9ApyseLzQuif1mLY= -github.com/ghostiam/protogetter v0.3.15/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= +github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-critic/go-critic v0.13.0 h1:kJzM7wzltQasSUXtYyTl6UaPVySO6GkaR1thFnJ6afY= -github.com/go-critic/go-critic v0.13.0/go.mod h1:M/YeuJ3vOCQDnP2SU+ZhjgRzwzcBW87JqLpMJLrZDLI= +github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog= +github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= 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= @@ -460,6 +484,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/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM= +github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo= 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= @@ -487,22 +513,28 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= -github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= -github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= -github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= +github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= +github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= +github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 h1:CbTB8KpqnViI6lIXxp03Oclc4VFHi3K4BWC1TacsZ+A= +github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= +github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U= +github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint/v2 v2.1.6 h1:LXqShFfAGM5BDzEOWD2SL1IzJAgUOqES/HRBsfKjI+w= -github.com/golangci/golangci-lint/v2 v2.1.6/go.mod h1:EPj+fgv4TeeBq3TcqaKZb3vkiV5dP4hHHKhXhEhzci8= -github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95 h1:AkK+w9FZBXlU/xUmBtSJN1+tAI4FIvy5WtnUnY8e4p8= -github.com/golangci/golines v0.0.0-20250217134842-442fd0091d95/go.mod h1:k9mmcyWKSTMcPPvQUCfRWWQ9VHJ1U9Dc0R7kaXAgtnQ= -github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= -github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= -github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= -github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= +github.com/golangci/golangci-lint/v2 v2.12.2 h1:7+d1uY0bq1MU2UV3R5pW5Q7QWdcoq4naMRXM+gsJKrs= +github.com/golangci/golangci-lint/v2 v2.12.2/go.mod h1:opqHHuIcTG2R+4akzWMd4o1BnD9/1LcjICWOujr91U8= +github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0= +github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10= +github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg= +github.com/golangci/misspell v0.8.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg= +github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= +github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba h1:lqtcnSMDuuJdu/LrKWi5RJzpSNLOJXYe/nzQutTI5kg= +github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba/go.mod h1:sCBNcpRmhJCtbFGz49+IM3ETTFf7QdJ30AeYCd43NKk= +github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM= +github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= 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/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= @@ -513,11 +545,9 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -530,15 +560,15 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -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/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= 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= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= -github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= +github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -548,14 +578,13 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T 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= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= -github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= -github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= +github.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU= +github.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA= 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= @@ -579,8 +608,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -651,17 +680,15 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jgautheron/goconst v1.8.1 h1:PPqCYp3K/xlOj5JmIe6O1Mj6r1DbkdbLtR3AJuZo414= -github.com/jgautheron/goconst v1.8.1/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= -github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= -github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jgautheron/goconst v1.10.0 h1:Ptt+OoE4NaEWKhLrWrrN3IpZdGLiqaf7WLnEX/iv4Jw= +github.com/jgautheron/goconst v1.10.0/go.mod h1:0p+wv1lFOiUr0IlNNT1nrm6+8DB8u2sU6KHGzFRXHDc= github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8 h1:CZkYfurY6KGhVtlalI4QwQ6T0Cu6iuY3e0x5RLu96WE= github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVtDElaEIDdKwpPqUIZJfzkNLV34htpEc= github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= -github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= +github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= +github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= @@ -677,13 +704,13 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= -github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= -github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= +github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= +github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= -github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= +github.com/kisielk/errcheck v1.10.0 h1:Lvs/YAHP24YKg08LA8oDw2z9fJVme090RAXd90S+rrw= +github.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= 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= @@ -702,24 +729,26 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= -github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.14 h1:wAkMoMeGX/kGfhQBPODT/BL8XhK23ol/nuQ3SwFaUw8= -github.com/kunwardeep/paralleltest v1.0.14/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= +github.com/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98= +github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs= +github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w= +github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/exptostd v0.4.3 h1:Ag1aGiq2epGePuRJhez2mzOpZ8sI9Gimcb4Sb3+pk9Y= -github.com/ldez/exptostd v0.4.3/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= -github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= -github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= -github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= -github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= -github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= -github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= -github.com/ldez/usetesting v0.4.3 h1:pJpN0x3fMupdTf/IapYjnkhiY1nSTN+pox1/GyBRw3k= -github.com/ldez/usetesting v0.4.3/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= +github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ= +github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM= +github.com/ldez/gomoddirectives v0.8.0 h1:JqIuTtgvFC2RdH1s357vrE23WJF2cpDCPFgA/TWDGpk= +github.com/ldez/gomoddirectives v0.8.0/go.mod h1:jutzamvZR4XYJLr0d5Honycp4Gy6GEg2mS9+2YX3F1Q= +github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= +github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= +github.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk= +github.com/ldez/structtags v0.6.1/go.mod h1:YDxVSgDy/MON6ariaxLF2X09bh19qL7MtGBN5MrvbdY= +github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk= +github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI= +github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= +github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= @@ -729,21 +758,23 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= -github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= +github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 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.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= -github.com/manuelarte/funcorder v0.2.1/go.mod h1:BQQ0yW57+PF9ZpjpeJDKOffEsQbxDFKW8F8zSMe/Zd0= -github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= -github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= -github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= -github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= +github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= +github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM= +github.com/manuelarte/funcorder v0.6.0 h1:0hBngc4fa1IgNiI65A7sFGkMvoMCc878RjqB5V7rWP0= +github.com/manuelarte/funcorder v0.6.0/go.mod h1:id3NDhXdQBmeqXH7eVC6Z89xS6JxvZ8kF9xUxpArU/g= +github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8= +github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= +github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= +github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc= github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= @@ -760,14 +791,14 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.9.0 h1:8LaA62XIKrb8lM6VsBSQ92slt/o92z5+hTw3CmrvSrM= -github.com/mgechev/revive v1.9.0/go.mod h1:LAPq3+MgOf7GcL5PlWIkHb0PT7XH4NuC2LdWymhb9Mo= +github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= +github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -856,8 +887,8 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4= -github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= +github.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8= +github.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -886,16 +917,16 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= -github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= +github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= +github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -919,8 +950,8 @@ github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= +github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= @@ -939,12 +970,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.8.0 h1:DL4RestQqRLr8U4LygLw8g2DX6RN1eBJOpa2mzsrl1Q= -github.com/polyfloyd/go-errorlint v1.8.0/go.mod h1:G2W0Q5roxbLCt0ZQbdoxQxXktTjwNyDbEaj3n7jvl4s= github.com/posthog/posthog-go v1.16.2 h1:zjbAZZELbt8/GNTWyFmZuVNURf8prGdlo1BcJOX9RK4= github.com/posthog/posthog-go v1.16.2/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -993,25 +1020,27 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= -github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= -github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= +github.com/ryancurrah/gomodguard/v2 v2.1.3 h1:E7sz3PJwE9Ba1reVxSpF6XLCPJZ74Kfw/LabTNM4GIA= +github.com/ryancurrah/gomodguard/v2 v2.1.3/go.mod h1:CQicdLGatWMxLX53JzoBjYlsNZhHbmLv2AVa0s2aivU= +github.com/ryanrolds/sqlclosecheck v0.6.0 h1:pEyL9okISdg1F1SEpJNlrEotkTGerv5BMk7U4AG0eVg= +github.com/ryanrolds/sqlclosecheck v0.6.0/go.mod h1:xyX16hsDaCMXHrMJ3JMzGf5OpDfHTOTTQrT7HOFUmeU= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= -github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= +github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 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/securego/gosec/v2 v2.26.1 h1:gdkttGhQFVehqRJ8grKH4DrpqM/QlPKNHBnl8QgcEC4= +github.com/securego/gosec/v2 v2.26.1/go.mod h1:57UW4p0uoP3kxoTkhoo3axLdVAi+OWrLg/Ax/kdqtPE= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= @@ -1022,8 +1051,6 @@ github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -1037,10 +1064,10 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= -github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= -github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= -github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= -github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/sonatard/noctx v0.5.1 h1:wklWg9c9ZYugOAk7qG4yP4PBrlQsmSLPTvW1K4PRQMs= +github.com/sonatard/noctx v0.5.1/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= +github.com/sourcegraph/go-diff v0.8.0 h1:ipIyu4cTsLbIrln4l0qtHA3r0a7gyK4ntKjtQytHhvY= +github.com/sourcegraph/go-diff v0.8.0/go.mod h1:hWlcO7Al+UZStZAP8rBumHpCK5ZHQ5BXsMls8p4+F5E= github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg= @@ -1065,8 +1092,8 @@ github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjb github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= -github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= +github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g= +github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -1082,33 +1109,30 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stripe/pg-schema-diff v1.0.5 h1:TNHkiRNMn7ttiBd+YBypAbx9v0SfVls+NQZFtamy1K4= github.com/stripe/pg-schema-diff v1.0.5/go.mod h1:3IctPaAqm+0LtWw/GiwyRoRlU1/N/+00+eXVk0KZIHs= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= -github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.5.1 h1:PZnjCol4+FqaEzvZg5+O8IY2P3hfY9JzRBNPv1pEDS4= -github.com/tetafro/godot v1.5.1/go.mod h1:cCdPtEndkmqqrhiCfkmxDodMQJ/f3L1BCNskCUZdTwk= +github.com/tetafro/godot v1.5.6 h1:IEkrFCwXaYHlOn4mGzGS3F3dkP6m9t0jpwqBFPIkKiA= +github.com/tetafro/godot v1.5.6/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c= github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw= github.com/tidwall/jsonc v0.3.3 h1:RVQqL3xFfDkKKXIDsrBiVQiEpBtxoKbmMXONb2H/y2w= github.com/tidwall/jsonc v0.3.3/go.mod h1:dw+3CIxqHi+t8eFSpzzMlcVYxKp08UP5CD8/uSFCyJE= github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA= github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g= -github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= -github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= +github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 h1:SiHe5XLTn9sFWJ5pBwJ5FN/4j34q9ZlOAD//kMoMYp0= +github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M= github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= -github.com/tomarrell/wrapcheck/v2 v2.11.0 h1:BJSt36snX9+4WTIXeJ7nvHBQBcm1h2SjQMSlmQ6aFSU= -github.com/tomarrell/wrapcheck/v2 v2.11.0/go.mod h1:wFL9pDWDAbXhhPZZt+nG8Fu+h29TtnZ2MW6Lx4BRXIU= +github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is= +github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= 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= @@ -1127,10 +1151,10 @@ github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLk github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= -github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= -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/uudashr/gocognit v1.2.1 h1:CSJynt5txTnORn/DkhiB4mZjwPuifyASC8/6Q0I/QS4= +github.com/uudashr/gocognit v1.2.1/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q= +github.com/uudashr/iface v1.4.2 h1:06Vq5RKVYThBsj0Bnw4oasMjD1r+7CE/bcKOA8dVSvg= +github.com/uudashr/iface v1.4.2/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= 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= @@ -1165,7 +1189,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= @@ -1181,12 +1204,14 @@ gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/musttag v0.13.1 h1:lw2sJyu7S1X8lc8zWUAdH42y+afdcCnHhWpnkWvd6vU= -go-simpler.org/musttag v0.13.1/go.mod h1:8r450ehpMLQgvpb6sg+hV5Ur47eH6olp/3yEanfG97k= -go-simpler.org/sloglint v0.11.0 h1:JlR1X4jkbeaffiyjLtymeqmGDKBDO1ikC6rjiuFAOco= -go-simpler.org/sloglint v0.11.0/go.mod h1:CFDO8R1i77dlciGfPEPvYke2ZMx4eyGiEIWkyeW2Pvw= -go.augendre.info/fatcontext v0.8.0 h1:2dfk6CQbDGeu1YocF59Za5Pia7ULeAM6friJ3LP7lmk= -go.augendre.info/fatcontext v0.8.0/go.mod h1:oVJfMgwngMsHO+KB2MdgzcO+RvtNdiCEOlWvSFtax/s= +go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo= +go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE= +go-simpler.org/sloglint v0.12.0 h1:UzWDlLWNE5FLqsvyq3tWYHuQMbqrervOhT8qPl4Mmw4= +go-simpler.org/sloglint v0.12.0/go.mod h1:jBjjC2bm8rYrs88oTRlFX497kWjJsyZWYoNaXkGRI6I= +go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50= +go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA= +go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= +go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -1225,8 +1250,6 @@ 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= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -1261,8 +1284,6 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -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.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= @@ -1271,22 +1292,17 @@ golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJk 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= -golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk= +golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1301,18 +1317,13 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 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.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= @@ -1328,8 +1339,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1354,10 +1363,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1369,24 +1376,17 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.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.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.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= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 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.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= @@ -1396,11 +1396,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -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.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= @@ -1415,25 +1412,16 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= 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.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= @@ -1507,8 +1495,8 @@ gotest.tools/gotestsum v1.12.2/go.mod h1:kjRtCglPZVsSU0hFHX3M5VWBM6Y63emHuB14ER1 gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= 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= +honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= +honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= 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= @@ -1521,10 +1509,10 @@ k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOP 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= +mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= +mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= +mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= +mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU= 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= diff --git a/mise.lock b/mise.lock index df835f53f2..dd05625d5f 100644 --- a/mise.lock +++ b/mise.lock @@ -64,10 +64,18 @@ url = "https://dl.google.com/go/go1.25.11.linux-arm64.tar.gz" checksum = "sha256:34f14304e856893f4ba30c2cacfe93906e9de7915c5f6aaaf3a81cdccd7ba30b" url = "https://dl.google.com/go/go1.25.11.linux-amd64.tar.gz" +[tools.go."platforms.linux-x64-baseline"] +checksum = "sha256:34f14304e856893f4ba30c2cacfe93906e9de7915c5f6aaaf3a81cdccd7ba30b" +url = "https://dl.google.com/go/go1.25.11.linux-amd64.tar.gz" + [tools.go."platforms.linux-x64-musl"] checksum = "sha256:34f14304e856893f4ba30c2cacfe93906e9de7915c5f6aaaf3a81cdccd7ba30b" url = "https://dl.google.com/go/go1.25.11.linux-amd64.tar.gz" +[tools.go."platforms.linux-x64-musl-baseline"] +checksum = "sha256:34f14304e856893f4ba30c2cacfe93906e9de7915c5f6aaaf3a81cdccd7ba30b" +url = "https://dl.google.com/go/go1.25.11.linux-amd64.tar.gz" + [tools.go."platforms.macos-arm64"] checksum = "sha256:cd8d4920e7930d55da1a5a57ba43a64b1305f71cdf2ca3c76cd8c549272b1680" url = "https://dl.google.com/go/go1.25.11.darwin-arm64.tar.gz" @@ -76,13 +84,87 @@ url = "https://dl.google.com/go/go1.25.11.darwin-arm64.tar.gz" checksum = "sha256:26d0ee4071de42b5c332337db9fdd234072877697c547e46e85efb0f59507c66" url = "https://dl.google.com/go/go1.25.11.darwin-amd64.tar.gz" +[tools.go."platforms.macos-x64-baseline"] +checksum = "sha256:26d0ee4071de42b5c332337db9fdd234072877697c547e46e85efb0f59507c66" +url = "https://dl.google.com/go/go1.25.11.darwin-amd64.tar.gz" + [tools.go."platforms.windows-x64"] checksum = "sha256:b7401f1b41517428e537493316256fb7cf03c66a130a0103ab07f3a2152e2112" url = "https://dl.google.com/go/go1.25.11.windows-amd64.zip" -[[tools."go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint"]] -version = "2.1.6" -backend = "go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint" +[tools.go."platforms.windows-x64-baseline"] +checksum = "sha256:b7401f1b41517428e537493316256fb7cf03c66a130a0103ab07f3a2152e2112" +url = "https://dl.google.com/go/go1.25.11.windows-amd64.zip" + +[[tools.golangci-lint]] +version = "2.12.2" +backend = "aqua:golangci/golangci-lint" + +[tools.golangci-lint."platforms.linux-arm64"] +checksum = "sha256:44cd40a8c76c86755375adfeea52cfd3533cb43d7bd647771e0ae065e166df3a" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413470996" +provenance = "github-attestations" + +[tools.golangci-lint."platforms.linux-arm64-musl"] +checksum = "sha256:44cd40a8c76c86755375adfeea52cfd3533cb43d7bd647771e0ae065e166df3a" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413470996" +provenance = "github-attestations" + +[tools.golangci-lint."platforms.linux-x64"] +checksum = "sha256:8df580d2670fed8fa984aac0507099af8df275e665215f5c7a2ae3943893a553" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413471054" +provenance = "github-attestations" + +[tools.golangci-lint."platforms.linux-x64-baseline"] +checksum = "sha256:8df580d2670fed8fa984aac0507099af8df275e665215f5c7a2ae3943893a553" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413471054" +provenance = "github-attestations" + +[tools.golangci-lint."platforms.linux-x64-musl"] +checksum = "sha256:8df580d2670fed8fa984aac0507099af8df275e665215f5c7a2ae3943893a553" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413471054" +provenance = "github-attestations" + +[tools.golangci-lint."platforms.linux-x64-musl-baseline"] +checksum = "sha256:8df580d2670fed8fa984aac0507099af8df275e665215f5c7a2ae3943893a553" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-linux-amd64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413471054" +provenance = "github-attestations" + +[tools.golangci-lint."platforms.macos-arm64"] +checksum = "sha256:a9c54498731b3128f79e090be6110f3e5fffccc617b08142ed244d4126c73f29" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-darwin-arm64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413470950" +provenance = "github-attestations" + +[tools.golangci-lint."platforms.macos-x64"] +checksum = "sha256:f6f06d94b6241521c53d15450c5209b028270bf966f842afb11c030c79f5bc16" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-darwin-amd64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413470980" +provenance = "github-attestations" + +[tools.golangci-lint."platforms.macos-x64-baseline"] +checksum = "sha256:f6f06d94b6241521c53d15450c5209b028270bf966f842afb11c030c79f5bc16" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-darwin-amd64.tar.gz" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413470980" +provenance = "github-attestations" + +[tools.golangci-lint."platforms.windows-x64"] +checksum = "sha256:bd42e3ebc8cb4ececb86941983baaf1dc221bbb04d838e94ce63b49cc91e02bb" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-windows-amd64.zip" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413471017" +provenance = "github-attestations" + +[tools.golangci-lint."platforms.windows-x64-baseline"] +checksum = "sha256:bd42e3ebc8cb4ececb86941983baaf1dc221bbb04d838e94ce63b49cc91e02bb" +url = "https://github.com/golangci/golangci-lint/releases/download/v2.12.2/golangci-lint-2.12.2-windows-amd64.zip" +url_api = "https://api.github.com/repos/golangci/golangci-lint/releases/assets/413471017" +provenance = "github-attestations" [[tools.node]] version = "24.18.0" @@ -100,10 +182,18 @@ url = "https://unofficial-builds.nodejs.org/download/release/v24.18.0/node-v24.1 checksum = "sha256:783130984963db7ba9cbd01089eaf2c2efb055c7c1693c943174b967b3050cb8" url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.gz" +[tools.node."platforms.linux-x64-baseline"] +checksum = "sha256:783130984963db7ba9cbd01089eaf2c2efb055c7c1693c943174b967b3050cb8" +url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.gz" + [tools.node."platforms.linux-x64-musl"] checksum = "sha256:ea58409911e141ec6b19d9178efa2d9185a13295005b1cbf5521b3157eed1d95" url = "https://unofficial-builds.nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-x64-musl.tar.gz" +[tools.node."platforms.linux-x64-musl-baseline"] +checksum = "sha256:ea58409911e141ec6b19d9178efa2d9185a13295005b1cbf5521b3157eed1d95" +url = "https://unofficial-builds.nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-x64-musl.tar.gz" + [tools.node."platforms.macos-arm64"] checksum = "sha256:e1a97e14c99c803e96c7339403282ea05a499c32f8d83defe9ef5ec66f979ed1" url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-darwin-arm64.tar.gz" @@ -112,10 +202,18 @@ url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-darwin-arm64.tar.gz" checksum = "sha256:dfd0dbd3e721503434df7b7205e719f61b3a3a31b2bcf9729b8b91fea240f080" url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-darwin-x64.tar.gz" +[tools.node."platforms.macos-x64-baseline"] +checksum = "sha256:dfd0dbd3e721503434df7b7205e719f61b3a3a31b2bcf9729b8b91fea240f080" +url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-darwin-x64.tar.gz" + [tools.node."platforms.windows-x64"] checksum = "sha256:0ae68406b42d7725661da979b1403ec9926da205c6770827f33aac9d8f26e821" url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-win-x64.zip" +[tools.node."platforms.windows-x64-baseline"] +checksum = "sha256:0ae68406b42d7725661da979b1403ec9926da205c6770827f33aac9d8f26e821" +url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-win-x64.zip" + [[tools.pnpm]] version = "11.4.0" backend = "aqua:pnpm/pnpm" @@ -123,29 +221,53 @@ backend = "aqua:pnpm/pnpm" [tools.pnpm."platforms.linux-arm64"] checksum = "sha256:cc38ebd5b2610a5744f84576b963c49e6609a8df5aed714ae3de749998d4478c" url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/431121491" provenance = "github-attestations" [tools.pnpm."platforms.linux-arm64-musl"] checksum = "sha256:a1e2ec9123c709fd04b704227cfcf3b50cd2bbbc1bd39d2df414530b5697eb75" url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-arm64-musl.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/431121493" provenance = "github-attestations" [tools.pnpm."platforms.linux-x64"] checksum = "sha256:f3f8d1217eef013bbc71a24d52efb1f1041e4aff55edd80e0b08e25f409305a4" url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-x64.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/431121492" +provenance = "github-attestations" + +[tools.pnpm."platforms.linux-x64-baseline"] +checksum = "sha256:f3f8d1217eef013bbc71a24d52efb1f1041e4aff55edd80e0b08e25f409305a4" +url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-x64.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/431121492" provenance = "github-attestations" [tools.pnpm."platforms.linux-x64-musl"] checksum = "sha256:60010ad00a96b71e20d1618acaca7a71395e710cbd5e88946c030a1d07c56916" url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-x64-musl.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/431121489" +provenance = "github-attestations" + +[tools.pnpm."platforms.linux-x64-musl-baseline"] +checksum = "sha256:60010ad00a96b71e20d1618acaca7a71395e710cbd5e88946c030a1d07c56916" +url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-x64-musl.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/431121489" provenance = "github-attestations" [tools.pnpm."platforms.macos-arm64"] checksum = "sha256:ba59014c2c1ce8b76af9f559385206a2623de4ff2b694b5c91598a8f44abb4e2" url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-darwin-arm64.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/431121490" provenance = "github-attestations" [tools.pnpm."platforms.windows-x64"] checksum = "sha256:84ce90e38bc0b1164173eb853a0fbffc7edcb050cb0d5c8ce4ca609f5c808e0a" url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-win32-x64.zip" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/431121494" +provenance = "github-attestations" + +[tools.pnpm."platforms.windows-x64-baseline"] +checksum = "sha256:84ce90e38bc0b1164173eb853a0fbffc7edcb050cb0d5c8ce4ca609f5c808e0a" +url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-win32-x64.zip" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/431121494" provenance = "github-attestations" diff --git a/mise.toml b/mise.toml index 1164e695ff..ba262f3374 100644 --- a/mise.toml +++ b/mise.toml @@ -2,7 +2,7 @@ min_version = '2026.7.0' [tools] go = "1.25.11" -"go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint" = "2.1.6" +golangci-lint = "2.12.2" [settings] idiomatic_version_file_enable_tools = ["node", "bun", "pnpm"] From 5a0e4a9383482808007fb6c1261b79157ee5ff5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Jul 2026 00:10:45 +0000 Subject: [PATCH 32/48] fix(deps): bump the npm-major group with 3 updates (#5782) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-major group with 3 updates: [@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript), [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) and [@typescript/native-preview](https://github.com/microsoft/typescript-go). Updates `@anthropic-ai/claude-agent-sdk` from 0.3.193 to 0.3.195
Release notes

Sourced from @​anthropic-ai/claude-agent-sdk's releases.

v0.3.195

What's changed

  • Added Query.reinitialize() to re-send the initialize control request and redeliver pending permission/dialog prompts after a transport gap
  • Fixed commands_changed event not being emitted for synced skills when the skill list resolves before the change-detector subscribes

Update

npm install @anthropic-ai/claude-agent-sdk@0.3.195
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.195
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.195
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.195
Changelog

Sourced from @​anthropic-ai/claude-agent-sdk's changelog.

0.3.195

  • Added Query.reinitialize() to re-send the initialize control request and redeliver pending permission/dialog prompts after a transport gap
  • Fixed commands_changed event not being emitted for synced skills when the skill list resolves before the change-detector subscribes

0.3.194

  • Updated to parity with Claude Code v2.1.194
Commits

Updates `posthog-node` from 5.38.5 to 5.38.6
Release notes

Sourced from posthog-node's releases.

posthog-node@5.38.6

5.38.6

Patch Changes

Changelog

Sourced from posthog-node's changelog.

5.38.6

Patch Changes

Commits

Updates `@typescript/native-preview` from 7.0.0-dev.20260624.1 to 7.0.0-dev.20260626.1
Commits
Maintainer changes

This version was pushed to npm by microsoft1es, a new releaser for @​typescript/native-preview since your current version.


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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli/package.json | 4 +- pnpm-lock.yaml | 180 +++++++++++++++++++++--------------------- pnpm-workspace.yaml | 2 +- 3 files changed, 93 insertions(+), 93 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index fea2505b46..e22b4791bb 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -43,7 +43,7 @@ "jose": "^6.2.3" }, "devDependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.3.193", + "@anthropic-ai/claude-agent-sdk": "^0.3.195", "@anthropic-ai/sdk": "^0.106.0", "@clack/prompts": "^1.6.0", "@effect/atom-react": "catalog:", @@ -76,7 +76,7 @@ "oxlint-tsgolint": "catalog:", "pg": "^8.22.0", "pg-copy-streams": "^7.0.0", - "posthog-node": "^5.38.5", + "posthog-node": "^5.38.6", "react": "^19.2.7", "react-devtools-core": "^7.0.1", "semantic-release": "^25.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9160ae34ae..d796396d78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,8 +37,8 @@ catalogs: specifier: ^1.3.14 version: 1.3.14 '@typescript/native-preview': - specifier: 7.0.0-dev.20260624.1 - version: 7.0.0-dev.20260624.1 + specifier: 7.0.0-dev.20260626.1 + version: 7.0.0-dev.20260626.1 '@vitest/coverage-istanbul': specifier: ^4.1.9 version: 4.1.9 @@ -97,8 +97,8 @@ importers: version: 6.2.3 devDependencies: '@anthropic-ai/claude-agent-sdk': - specifier: ^0.3.193 - version: 0.3.193(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) + specifier: ^0.3.195 + version: 0.3.195(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) '@anthropic-ai/sdk': specifier: ^0.106.0 version: 0.106.0(zod@4.4.3) @@ -155,7 +155,7 @@ importers: version: 19.2.17 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260624.1 + version: 7.0.0-dev.20260626.1 '@vercel/detect-agent': specifier: ^1.2.3 version: 1.2.3 @@ -196,8 +196,8 @@ importers: specifier: ^7.0.0 version: 7.0.0 posthog-node: - specifier: ^5.38.5 - version: 5.38.5 + specifier: ^5.38.6 + version: 5.38.6 react: specifier: ^19.2.7 version: 19.2.7 @@ -259,7 +259,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260624.1 + version: 7.0.0-dev.20260626.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -339,7 +339,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260624.1 + version: 7.0.0-dev.20260626.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -381,7 +381,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260624.1 + version: 7.0.0-dev.20260626.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -431,7 +431,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260624.1 + version: 7.0.0-dev.20260626.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -471,7 +471,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260624.1 + version: 7.0.0-dev.20260626.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -523,7 +523,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260624.1 + version: 7.0.0-dev.20260626.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -570,52 +570,52 @@ packages: resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} engines: {node: '>=18'} - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.193': - resolution: {integrity: sha512-1hT7b+KIm/3E1OSJofr7PF21Xq2zT1ccnjzuVcWQ5LYXJ09lgCMvA/ZfcDBKuoyCZ4lSnuicKXZ/h5vRbWfqrA==} + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.195': + resolution: {integrity: sha512-WIMM/8HRCLsTDHFTIwQvvE8WCA/oaMJtdQxsP7iNyfzIGwXbuOyU95V8vYIhZfaO2yaSpbBRncunq4CtR5H4ng==} cpu: [arm64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.193': - resolution: {integrity: sha512-9x5Y/L6iwETwEJFmPaYXfsE8q0cVgx75V7nL60HvSzi8K1XQNcG5u53jOzRvqDNah8mvGYOb+9AWrMCSJvmFyA==} + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.195': + resolution: {integrity: sha512-RY7DB+4LXosE0MJ+XELmakfPrDN1YX4lkk9CTDm28jGCVcESRz9kAEqbyaiC48dZcmN9V1NCLutzINGdcr1TBg==} cpu: [x64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.193': - resolution: {integrity: sha512-a3qsVTBe4G6ndsVavfIXEVAXLVXM8uvBUNYpm4jKqk2+ovWZQe/96CXOwmerg9U+/bllg4QJ9lSyYCsdtVIr6w==} + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.195': + resolution: {integrity: sha512-ZmyBA/AFzhgutcxb7dbhCm6GTjJytwNYXTxJoKE2B3A409WCYccjMqeji6vCMNxyyfylglGo5D8dVMIxW9aoug==} cpu: [arm64] os: [linux] libc: [musl] - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.193': - resolution: {integrity: sha512-gvfD9pKHXWxCkkIX6bC4/FOALTxqHYjAu83iT1bzn5mCv6QWSZRl6WLRRtvKpWtWuY1jpML1lL3YfKADsAX/rA==} + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.195': + resolution: {integrity: sha512-JuIq5Fnz/F1snl0aqi1gcuRZqPWoPNrL9dJ0DuievCxKkO8hnEz/Mmn5Zos7x1X8HE//ZnEvmQXoEQEZXonJew==} cpu: [arm64] os: [linux] libc: [glibc] - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.193': - resolution: {integrity: sha512-DLXlO4tlcWygz0Ft4nu6ai5KssByYt2tOeWdc4dFXKt6uBKXpbZVziUUq3ePO5zuAFyU6w7EjYLv8MMPURbAiQ==} + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.195': + resolution: {integrity: sha512-nf8Q/LauB+ZOC6QDjxNhbsvwUtYjKYnaWJLTYFwhkmsLujePnety1AtT/1ubaUoq5AM1j297DhMlYTasa79OUA==} cpu: [x64] os: [linux] libc: [musl] - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.193': - resolution: {integrity: sha512-1sz+7cn0iuh0ThInuAYF1jpFvLyOaZ0PZYQIF7eb9JDZbBohUf6INeTCwjAwUL0ASCq2xS7Odu/qDVuTtLTeDA==} + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.195': + resolution: {integrity: sha512-s1lNi1cL93luoqsItH+fNO4KpIhdkvnVhWGGQUQ/8ftwa2gfmcIQnOg1hG8Ks+KzeD3UUQ8L9YEVHVADnFI/9A==} cpu: [x64] os: [linux] libc: [glibc] - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.193': - resolution: {integrity: sha512-36LJKiGuKusgaPTVeh9QanL00UcaE0RcC4pgK800/0SenApbh979ndxI0XKUVfLHzlGkqlhkhT3foMdqS+zx1w==} + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.195': + resolution: {integrity: sha512-hbkDE+xPIZzRWm+D+BKrH9uJH6USIZdDIlsyrIlGi3JFHoieYoA1vdUNyldSS9+F3ZqQtfPjr2Qy08IVB6akYA==} cpu: [arm64] os: [win32] - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.193': - resolution: {integrity: sha512-VyyKZlWQpbD6nkUTeNvgmLvpqt1QaPQQBOC30tbEYwYsV5MC1I35Li0H7nWwngGqPSTtMvHpjpz6Eb2FSTn7/A==} + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.195': + resolution: {integrity: sha512-av0piEB3X1Dzhpr8A+DqHVZ9y8s1jpn8enzwX0TKKUPBn5IqLTWC7wD6v66aoUgu4f+g4ThZirmDZA6shyPEZQ==} cpu: [x64] os: [win32] - '@anthropic-ai/claude-agent-sdk@0.3.193': - resolution: {integrity: sha512-WzL03VJE1sT0Nz3rEpsYMYR+9n6iyQtLVt7ghMWnYC9pvDsiy6kwcMplZniWSjH8Dm6CfkUBN5t6KB4i/JfouA==} + '@anthropic-ai/claude-agent-sdk@0.3.195': + resolution: {integrity: sha512-FVmXu9pvOMbuBKWrF8YsYQdQ/upOpv5rS8lFAnFO5jbyXT/2hN7kEPd2vd2GJpaMvNcO/KptyQUK5AxjjTz3+w==} engines: {node: '>=18.0.0'} peerDependencies: '@anthropic-ai/sdk': '>=0.93.0' @@ -2107,11 +2107,11 @@ packages: resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==} engines: {node: '>=12'} - '@posthog/core@1.39.3': - resolution: {integrity: sha512-oR+B8Q5O61N+W2+HVOBG9dbxAT/+OVxX+XvpNj6KYgptN2EB14JiKQ9Rm7DNpErNTLV7WApZEK/URkZgldOxfg==} + '@posthog/core@1.39.6': + resolution: {integrity: sha512-o6ajIwN5zXoNP0D4H/QPmOyibNTUkSyOR6ya7AG5U2ywXx4awo72L2KnCoiZPQM5x/bXv6jPBdimH8M18Ax0aw==} - '@posthog/types@1.392.0': - resolution: {integrity: sha512-nctNujXL3FC1v99FktaTMSugSD9ZOZekEpahUSafkU2TSvW+XGKNkQZbokuJtiWvPBK208dwMJva8UfBkChqpw==} + '@posthog/types@1.392.1': + resolution: {integrity: sha512-Qg6Gl7/1vlr8+gPtBi5gwnLgAgiyFoKOVmTvTtDcvya9cpTwZfna7rQmkGQ4B63CunUYNNbOlqcwiUwUDyTK6w==} '@radix-ui/number@1.1.2': resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==} @@ -2864,50 +2864,50 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260624.1': - resolution: {integrity: sha512-g8CqDkYCHTCYdhBHXs5cMraBurOS+KrcMFxE0SsaKZoI6Tnp+le1aWvxUBbzNKJYyThHJqb/1mLopzEJxJCuKA==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260626.1': + resolution: {integrity: sha512-VDPHf8RZRzsBH6cgArK1u9sH8MODMNkvkNczEt1EXpOLfZeplIhcpKld5Zc9Da8/S9YB4768rjfBkdFokxBu+Q==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260624.1': - resolution: {integrity: sha512-P00JVvSV90eioYDuINAKmOSA8yhFTWLq6RvS5lrCfUuDlcgr2kSOgZAfFHIksHBVz6ZXpAXpa0dHPmc5SJ3Ymw==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260626.1': + resolution: {integrity: sha512-bi7iQZe2A90cFJ3EQnigezEI7F0e5vX6E/QUGluQ1mKZmcbbQCdAwDMDQFV8Z6w3xNrk9AYyNQbvq0DtzJX46w==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260624.1': - resolution: {integrity: sha512-cppM2yTZ/Gd1hOXy8NEJcUBxJ0O0zl9CU3OU1ZWZ/OHWWX/ukEzCCr94SUwJhjIWOylBCpIYkrvYoTwxNa94XQ==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260626.1': + resolution: {integrity: sha512-/iptuCYiucdY0HK0nE5ydRjIx0KOf3AY7fieRaQHA4X9/s4ey37rc/58aBX3dtx0x2EhlzpXT5f0ikYY65Zynw==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260624.1': - resolution: {integrity: sha512-eWHELvfQMkVRjafMd+3ATgM9p9yAergJaM4AOY8AekCNWnHFwUrp/ohh+ryyMUIqque5jjb/kuTiOiGj728I2Q==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260626.1': + resolution: {integrity: sha512-ilB0Ew5GWLrqMklNVrMdCEyNePypoEMnd4l5aUopnDRebgtXmtu0RE2GDl9LOTZ1BwlbXsuYAkEHt18Ta25hxg==} engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260624.1': - resolution: {integrity: sha512-FaB8rS+rKYz4nDrEsHsF3b4cn7eCKCYroMJReA375OuQ6PHcmCNQ6QlVetA0dfFBxTTgejmoKyfw9xgAA5P4Yw==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260626.1': + resolution: {integrity: sha512-4o80l1+RoJLkR1G4KOZjTYN1yOAGbq0K2CAP0zF35MPnD8O559Tw8OMuYA+XPpEFE0fkb7mmcxL8J9cxM/kAbw==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260624.1': - resolution: {integrity: sha512-BgkqbCmSHDb5UxqWaFlFFJ/DHNT3lEUO4W8627ap6+QthJZuXk2imiHAX3PgYXC6en9fLLyR6jjcseAa4CCshg==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260626.1': + resolution: {integrity: sha512-xHxewRWY74zJnwt4bj+Kdf6Owfs6L0Fbggb37psSa6CvofqcvSI3AyuwiBNjC0T8Eb/d/BMMXttbPJ4XXqMpXQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260624.1': - resolution: {integrity: sha512-WaZ+ue63NgB2j/lqjirfevh/TqcsCxSqnKhGGiRnlxHyYIBcoq+x7KngyEnyGIaywJE1PcFeXA+2EMSIPlSEiQ==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260626.1': + resolution: {integrity: sha512-Iuf5nqTY4m5kxEvraDpieEf0XS6gDdjBBkw3g74pseznhpMeDFzgRvVCbSaf2pdjBNGugZbiBDVTEOclmD9cjA==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260624.1': - resolution: {integrity: sha512-ogwfNo1xuAutOF8RbTCo3Ut0q/65u2ucOeHizi6O14q+3vnelNS+u8qVC2QWXubMcwtuN5E9cbfPslvGC4kdwA==} + '@typescript/native-preview@7.0.0-dev.20260626.1': + resolution: {integrity: sha512-2RC5omeJPqWKebi5zM3nkMSrTpKpq2zLB3SfceqmFZY3vMCXtIJSE6IpA/AszlKQmNcbT/kWscmgE/PXEvnguQ==} engines: {node: '>=16.20.0'} hasBin: true @@ -5663,8 +5663,8 @@ packages: postgres-range@1.1.4: resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} - posthog-node@5.38.5: - resolution: {integrity: sha512-ZqMK2MFXQWkfdc0PDzPMRBrZXHCZLvl9jbvaaIyXTgbfn802/hMJ8M24BJiChPrMpZejsTzfAq8ZsQYF8zcOoA==} + posthog-node@5.38.6: + resolution: {integrity: sha512-Sm2mCAa9/lTTYppnKyy0AhQrriq8fOd8B2vwd3EE/9uihyIx9qkJ1xGYbvADxQJlF7HqM1/TIFCKsI0JvF8gjQ==} engines: {node: ^20.20.0 || >=22.22.0} peerDependencies: rxjs: ^7.0.0 @@ -6853,44 +6853,44 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.193': + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.195': optional: true - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.193': + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.195': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.193': + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.195': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.193': + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.195': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.193': + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.195': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.193': + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.195': optional: true - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.193': + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.195': optional: true - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.193': + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.195': optional: true - '@anthropic-ai/claude-agent-sdk@0.3.193(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': + '@anthropic-ai/claude-agent-sdk@0.3.195(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': dependencies: '@anthropic-ai/sdk': 0.106.0(zod@4.4.3) '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) zod: 4.4.3 optionalDependencies: - '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.193 - '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.193 - '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.193 - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.193 - '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.193 - '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.193 - '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.193 - '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.193 + '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.195 + '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.195 + '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.195 + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.195 + '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.195 + '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.195 + '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.195 + '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.195 '@anthropic-ai/sdk@0.106.0(zod@4.4.3)': dependencies: @@ -8006,11 +8006,11 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@posthog/core@1.39.3': + '@posthog/core@1.39.6': dependencies: - '@posthog/types': 1.392.0 + '@posthog/types': 1.392.1 - '@posthog/types@1.392.0': {} + '@posthog/types@1.392.1': {} '@radix-ui/number@1.1.2': {} @@ -8746,36 +8746,36 @@ snapshots: dependencies: '@types/node': 26.0.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260624.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260626.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260624.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260626.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260624.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260626.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260624.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260626.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260624.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260626.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260624.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260626.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260624.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260626.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260624.1': + '@typescript/native-preview@7.0.0-dev.20260626.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260624.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260624.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260624.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260624.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260624.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260624.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260624.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260626.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260626.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260626.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260626.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260626.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260626.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260626.1 '@ungap/structured-clone@1.3.2': {} @@ -12066,9 +12066,9 @@ snapshots: postgres-range@1.1.4: {} - posthog-node@5.38.5: + posthog-node@5.38.6: dependencies: - '@posthog/core': 1.39.3 + '@posthog/core': 1.39.6 pretty-ms@9.3.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d0200739fe..85f88b9b51 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -22,7 +22,7 @@ catalog: "@swc/core": "^1.15.43" "@tsconfig/bun": "^1.0.10" "@types/bun": "^1.3.14" - "@typescript/native-preview": "7.0.0-dev.20260624.1" + "@typescript/native-preview": "7.0.0-dev.20260626.1" "@vitest/coverage-istanbul": "^4.1.9" "effect": "4.0.0-beta.93" "knip": "^6.17.1" From 31d6fb9703f2145634a8b4b5a2bdd07c275bd97d Mon Sep 17 00:00:00 2001 From: Andrew Valleteau Date: Sat, 4 Jul 2026 16:11:47 +0200 Subject: [PATCH 33/48] fix(cli): generate non-TypeScript types from project refs (#5622) ## What changed - Lets legacy `supabase gen types --project-id` generate Go, Swift, and Python types by resolving a temporary database connection and running pg-meta. - Keeps TypeScript project-ref generation on the Management API typegen endpoint, while non-TypeScript project-ref flows use the project database host plus a temporary login role. - Updates side-effect docs and integration coverage for project-ref type generation across TypeScript, Go, Swift, and Python. - Adds e2e coverage for tokenless local typegen and an env-gated live remote project matrix across all supported typegen languages. ## Why Python type generation with `--project-id` failed before it could reach pg-meta, and the first fix used the preview-branch config endpoint. Production project refs return `404 Preview branch not found` from that endpoint because `/v1/branches/{ref}` is branch-only. Non-TypeScript type generation is still pg-meta-based, so project-ref flows need to construct a live database connection instead of calling the TypeScript-only typegen endpoint or asking the user to provide `--db-url`. ## Reviewer context Non-TypeScript project-ref generation still requires Docker because it continues to use pg-meta. The change is limited to how the CLI resolves the database connection for project refs: it fetches the project database host and creates a temporary login role rather than reading or storing the project's database password. The live remote e2e matrix is skipped unless `SUPABASE_TYPEGEN_E2E_REMOTE=1`, `SUPABASE_TEST_PROJECT_REF`, and `SUPABASE_ACCESS_TOKEN` are set. The local e2e matrix runs without a token and points the API profile at an unreachable local URL to catch accidental Management API usage on the `--local` path. Closes CLI-1812 --------- Co-authored-by: Claude Co-authored-by: Julien Goux --- .../interactions.json | 29 - apps/cli-e2e/src/tests/gen.e2e.test.ts | 6 - apps/cli/docs/go-cli-porting-status.md | 210 +-- .../commands/branches/branches.format.ts | 3 + .../commands/db/diff/diff.integration.test.ts | 5 +- .../commands/db/pull/pull.integration.test.ts | 5 +- ...eclarative.orchestrate.integration.test.ts | 1 + .../generate/generate.integration.test.ts | 5 +- .../declarative/sync/sync.integration.test.ts | 5 +- .../shared/legacy-pgdelta.integration.test.ts | 1 + .../db/shared/legacy-pooler-fallback.ts | 51 +- .../legacy/commands/gen/types/SIDE_EFFECTS.md | 92 +- .../commands/gen/types/types.e2e.test.ts | 390 +++++ .../commands/gen/types/types.handler.ts | 392 +++-- .../gen/types/types.integration.test.ts | 1408 ++++++++++++++++- .../legacy/commands/gen/types/types.layers.ts | 16 + .../legacy/commands/gen/types/types.shared.ts | 50 - .../commands/gen/types/types.unit.test.ts | 87 - .../services/services.integration.test.ts | 2 +- .../legacy/shared/legacy-connect-errors.ts | 53 + .../shared/legacy-connect-errors.unit.test.ts | 82 +- .../legacy-db-config.integration.test.ts | 465 +++++- .../legacy/shared/legacy-db-config.layer.ts | 121 +- .../legacy/shared/legacy-db-config.parse.ts | 58 + .../legacy-db-config.parse.unit.test.ts | 78 + .../legacy/shared/legacy-db-config.types.ts | 23 + ...gacy-pgdelta-ssl-probe.integration.test.ts | 67 + .../shared/legacy-pgdelta-ssl-probe.layer.ts | 108 +- .../legacy-pgdelta-ssl-probe.service.ts | 5 + .../shared/legacy-pgdelta-ssl.unit.test.ts | 4 + .../legacy/shared/legacy-pooler-fallback.ts | 71 + .../legacy-pooler-fallback.unit.test.ts | 177 +++ 32 files changed, 3473 insertions(+), 597 deletions(-) delete mode 100644 apps/cli-e2e/fixtures/scenarios/gen-types-exits-non-zero-with-lang-go-when-using-project-id/interactions.json create mode 100644 apps/cli/src/legacy/commands/gen/types/types.e2e.test.ts create mode 100644 apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.integration.test.ts create mode 100644 apps/cli/src/legacy/shared/legacy-pooler-fallback.ts create mode 100644 apps/cli/src/legacy/shared/legacy-pooler-fallback.unit.test.ts diff --git a/apps/cli-e2e/fixtures/scenarios/gen-types-exits-non-zero-with-lang-go-when-using-project-id/interactions.json b/apps/cli-e2e/fixtures/scenarios/gen-types-exits-non-zero-with-lang-go-when-using-project-id/interactions.json deleted file mode 100644 index b090bbeaed..0000000000 --- a/apps/cli-e2e/fixtures/scenarios/gen-types-exits-non-zero-with-lang-go-when-using-project-id/interactions.json +++ /dev/null @@ -1,29 +0,0 @@ -[ - { - "request": { - "method": "GET", - "path": "/v1/projects/__PROJECT_REF__", - "query": {}, - "headers": { - "accept-encoding": "gzip", - "authorization": "Bearer __ACCESS_TOKEN__", - "host": "localhost:__PORT__", - "user-agent": "SupabaseCLI/" - }, - "body": null - }, - "response": { - "status": 400, - "headers": { - "content-type": "application/json; charset=utf-8", - "x-gotrue-id": "__UUID__", - "x-ratelimit-limit": "120", - "x-ratelimit-remaining": "119", - "x-ratelimit-reset": "60" - }, - "body": { - "message": "Resource has been removed" - } - } - } -] diff --git a/apps/cli-e2e/src/tests/gen.e2e.test.ts b/apps/cli-e2e/src/tests/gen.e2e.test.ts index cdb173382c..aa92480a80 100644 --- a/apps/cli-e2e/src/tests/gen.e2e.test.ts +++ b/apps/cli-e2e/src/tests/gen.e2e.test.ts @@ -67,12 +67,6 @@ describe("gen", () => { expect(result.stderr).toContain("Project not found"); }); - testBehaviour("exits non-zero with --lang go when using --project-id", async ({ run }) => { - const result = await run(["gen", "types", "--project-id", PROJECT_REF, "--lang", "go"]); - expect(result.exitCode).not.toBe(0); - expect(result.stderr).toContain("db-url"); - }); - testBehaviour("exits non-zero with no data source specified", async ({ runNoProjectId }) => { const result = await runNoProjectId(["gen", "types"]); expect(result.exitCode).not.toBe(0); diff --git a/apps/cli/docs/go-cli-porting-status.md b/apps/cli/docs/go-cli-porting-status.md index 2780e1c396..e6138adacd 100644 --- a/apps/cli/docs/go-cli-porting-status.md +++ b/apps/cli/docs/go-cli-porting-status.md @@ -211,111 +211,111 @@ Legend: - `wrapped`: Phase 0 proxy wrapper exists in the legacy shell - `missing`: no legacy shell command yet -| Command | Legacy status | Legacy command path | -| -------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `orgs list` | `ported` | [`../src/legacy/commands/orgs/list/list.command.ts`](../src/legacy/commands/orgs/list/list.command.ts) | -| `orgs create` | `ported` | [`../src/legacy/commands/orgs/create/create.command.ts`](../src/legacy/commands/orgs/create/create.command.ts) | -| `projects list` | `ported` | [`../src/legacy/commands/projects/list/list.command.ts`](../src/legacy/commands/projects/list/list.command.ts) | -| `projects create` | `ported` | [`../src/legacy/commands/projects/create/create.command.ts`](../src/legacy/commands/projects/create/create.command.ts) | -| `projects delete` | `ported` | [`../src/legacy/commands/projects/delete/delete.command.ts`](../src/legacy/commands/projects/delete/delete.command.ts) | -| `projects api-keys` | `ported` | [`../src/legacy/commands/projects/api-keys/api-keys.command.ts`](../src/legacy/commands/projects/api-keys/api-keys.command.ts) | -| `branches list` | `ported` | [`../src/legacy/commands/branches/list/list.command.ts`](../src/legacy/commands/branches/list/list.command.ts) | -| `branches create` | `ported` | [`../src/legacy/commands/branches/create/create.command.ts`](../src/legacy/commands/branches/create/create.command.ts) | -| `branches get` | `ported` | [`../src/legacy/commands/branches/get/get.command.ts`](../src/legacy/commands/branches/get/get.command.ts) | -| `branches update` | `ported` | [`../src/legacy/commands/branches/update/update.command.ts`](../src/legacy/commands/branches/update/update.command.ts) | -| `branches pause` | `ported` | [`../src/legacy/commands/branches/pause/pause.command.ts`](../src/legacy/commands/branches/pause/pause.command.ts) | -| `branches unpause` | `ported` | [`../src/legacy/commands/branches/unpause/unpause.command.ts`](../src/legacy/commands/branches/unpause/unpause.command.ts) | -| `branches delete` | `ported` | [`../src/legacy/commands/branches/delete/delete.command.ts`](../src/legacy/commands/branches/delete/delete.command.ts) | -| `branches disable` | `ported` | [`../src/legacy/commands/branches/disable/disable.command.ts`](../src/legacy/commands/branches/disable/disable.command.ts) | -| `secrets list` | `ported` | [`../src/legacy/commands/secrets/list/list.command.ts`](../src/legacy/commands/secrets/list/list.command.ts) | -| `secrets set` | `ported` | [`../src/legacy/commands/secrets/set/set.command.ts`](../src/legacy/commands/secrets/set/set.command.ts) | -| `secrets unset` | `ported` | [`../src/legacy/commands/secrets/unset/unset.command.ts`](../src/legacy/commands/secrets/unset/unset.command.ts) | -| `config push` | `ported` | [`../src/legacy/commands/config/push/push.command.ts`](../src/legacy/commands/config/push/push.command.ts) | -| `backups list` | `ported` | [`../src/legacy/commands/backups/list/list.command.ts`](../src/legacy/commands/backups/list/list.command.ts) | -| `backups restore` | `ported` | [`../src/legacy/commands/backups/restore/restore.command.ts`](../src/legacy/commands/backups/restore/restore.command.ts) | -| `snippets list` | `ported` | [`../src/legacy/commands/snippets/list/list.command.ts`](../src/legacy/commands/snippets/list/list.command.ts) | -| `snippets download` | `ported` | [`../src/legacy/commands/snippets/download/download.command.ts`](../src/legacy/commands/snippets/download/download.command.ts) | -| `sso list` | `ported` | [`../src/legacy/commands/sso/list/list.command.ts`](../src/legacy/commands/sso/list/list.command.ts) | -| `sso add` | `ported` | [`../src/legacy/commands/sso/add/add.command.ts`](../src/legacy/commands/sso/add/add.command.ts) | -| `sso remove` | `ported` | [`../src/legacy/commands/sso/remove/remove.command.ts`](../src/legacy/commands/sso/remove/remove.command.ts) | -| `sso update` | `ported` | [`../src/legacy/commands/sso/update/update.command.ts`](../src/legacy/commands/sso/update/update.command.ts) | -| `sso show` | `ported` | [`../src/legacy/commands/sso/show/show.command.ts`](../src/legacy/commands/sso/show/show.command.ts) | -| `sso info` | `ported` | [`../src/legacy/commands/sso/info/info.command.ts`](../src/legacy/commands/sso/info/info.command.ts) | -| `domains create` | `ported` | [`../src/legacy/commands/domains/create/create.command.ts`](../src/legacy/commands/domains/create/create.command.ts) | -| `domains get` | `ported` | [`../src/legacy/commands/domains/get/get.command.ts`](../src/legacy/commands/domains/get/get.command.ts) | -| `domains reverify` | `ported` | [`../src/legacy/commands/domains/reverify/reverify.command.ts`](../src/legacy/commands/domains/reverify/reverify.command.ts) | -| `domains activate` | `ported` | [`../src/legacy/commands/domains/activate/activate.command.ts`](../src/legacy/commands/domains/activate/activate.command.ts) | -| `domains delete` | `ported` | [`../src/legacy/commands/domains/delete/delete.command.ts`](../src/legacy/commands/domains/delete/delete.command.ts) | -| `vanity-subdomains get` | `ported` | [`../src/legacy/commands/vanity-subdomains/get/get.command.ts`](../src/legacy/commands/vanity-subdomains/get/get.command.ts) | -| `vanity-subdomains check-availability` | `ported` | [`../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts`](../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts) | -| `vanity-subdomains activate` | `ported` | [`../src/legacy/commands/vanity-subdomains/activate/activate.command.ts`](../src/legacy/commands/vanity-subdomains/activate/activate.command.ts) | -| `vanity-subdomains delete` | `ported` | [`../src/legacy/commands/vanity-subdomains/delete/delete.command.ts`](../src/legacy/commands/vanity-subdomains/delete/delete.command.ts) | -| `network-bans get` | `ported` | [`../src/legacy/commands/network-bans/get/get.command.ts`](../src/legacy/commands/network-bans/get/get.command.ts) | -| `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` | `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) | -| `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` | `ported` | [`../src/legacy/commands/login/login.command.ts`](../src/legacy/commands/login/login.command.ts) | -| `logout` | `ported` | [`../src/legacy/commands/logout/logout.command.ts`](../src/legacy/commands/logout/logout.command.ts) | -| `link` | `ported` | [`../src/legacy/commands/link/link.command.ts`](../src/legacy/commands/link/link.command.ts) | -| `unlink` | `ported` | [`../src/legacy/commands/unlink/unlink.command.ts`](../src/legacy/commands/unlink/unlink.command.ts) | -| `bootstrap` | `ported` | [`../src/legacy/commands/bootstrap/bootstrap.command.ts`](../src/legacy/commands/bootstrap/bootstrap.command.ts) (native; `db push` step delegated to the Go binary — interim) | -| `init` | `ported` | [`../src/legacy/commands/init/init.command.ts`](../src/legacy/commands/init/init.command.ts) | -| `services` | `ported` | [`../src/legacy/commands/services/services.command.ts`](../src/legacy/commands/services/services.command.ts) | -| `start` | `wrapped` | [`../src/legacy/commands/start/start.command.ts`](../src/legacy/commands/start/start.command.ts) | -| `stop` | `wrapped` | [`../src/legacy/commands/stop/stop.command.ts`](../src/legacy/commands/stop/stop.command.ts) | -| `status` | `wrapped` | [`../src/legacy/commands/status/status.command.ts`](../src/legacy/commands/status/status.command.ts) | -| `telemetry enable` | `ported` | [`../src/legacy/commands/telemetry/enable/enable.command.ts`](../src/legacy/commands/telemetry/enable/enable.command.ts) | -| `telemetry disable` | `ported` | [`../src/legacy/commands/telemetry/disable/disable.command.ts`](../src/legacy/commands/telemetry/disable/disable.command.ts) | -| `telemetry status` | `ported` | [`../src/legacy/commands/telemetry/status/status.command.ts`](../src/legacy/commands/telemetry/status/status.command.ts) | -| `migration list` | `ported` | [`../src/legacy/commands/migration/list/list.command.ts`](../src/legacy/commands/migration/list/list.command.ts) — native; merged Local/Remote/Time-UTC Glamour table | -| `migration new` | `ported` | [`../src/legacy/commands/migration/new/new.command.ts`](../src/legacy/commands/migration/new/new.command.ts) — native; writes `supabase/migrations/_.sql` from piped stdin | -| `migration repair` | `ported` | [`../src/legacy/commands/migration/repair/repair.command.ts`](../src/legacy/commands/migration/repair/repair.command.ts) — native; transactional TRUNCATE/UPSERT/DELETE, repair-all prompt | -| `migration squash` | `wrapped` | [`../src/legacy/commands/migration/squash/squash.command.ts`](../src/legacy/commands/migration/squash/squash.command.ts) | -| `migration up` | `ported` | [`../src/legacy/commands/migration/up/up.command.ts`](../src/legacy/commands/migration/up/up.command.ts) — native; pending compute + vault upsert + per-file apply | -| `migration down` | `ported` | [`../src/legacy/commands/migration/down/down.command.ts`](../src/legacy/commands/migration/down/down.command.ts) — native; drop + vault + migrate&seed to target version | -| `migration fetch` | `ported` | [`../src/legacy/commands/migration/fetch/fetch.command.ts`](../src/legacy/commands/migration/fetch/fetch.command.ts) — native; writes history rows to `supabase/migrations/` | -| `gen types` | `ported` | [`../src/legacy/commands/gen/types/types.command.ts`](../src/legacy/commands/gen/types/types.command.ts) | -| `gen signing-key` | `ported` | [`../src/legacy/commands/gen/signing-key/signing-key.command.ts`](../src/legacy/commands/gen/signing-key/signing-key.command.ts) | -| `gen bearer-jwt` | `wrapped` | [`../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts`](../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts) | -| `gen keys` | `wrapped` | [`../src/legacy/commands/gen/keys/keys.command.ts`](../src/legacy/commands/gen/keys/keys.command.ts) | -| `functions list` | `wrapped` | [`../src/legacy/commands/functions/list/list.command.ts`](../src/legacy/commands/functions/list/list.command.ts) | -| `functions delete` | `ported` | [`../src/legacy/commands/functions/delete/delete.command.ts`](../src/legacy/commands/functions/delete/delete.command.ts) | -| `functions download` | `ported` | [`../src/legacy/commands/functions/download/download.command.ts`](../src/legacy/commands/functions/download/download.command.ts) | -| `functions deploy` | `ported` | [`../src/legacy/commands/functions/deploy/deploy.command.ts`](../src/legacy/commands/functions/deploy/deploy.command.ts) | -| `functions new` | `ported` | [`../src/legacy/commands/functions/new/new.command.ts`](../src/legacy/commands/functions/new/new.command.ts) | -| `functions serve` | `ported` | [`../src/legacy/commands/functions/serve/serve.command.ts`](../src/legacy/commands/functions/serve/serve.command.ts) | -| `storage ls` | `ported` | [`../src/legacy/commands/storage/ls/ls.command.ts`](../src/legacy/commands/storage/ls/ls.command.ts) | -| `storage cp` | `ported` | [`../src/legacy/commands/storage/cp/cp.command.ts`](../src/legacy/commands/storage/cp/cp.command.ts) | -| `storage mv` | `ported` | [`../src/legacy/commands/storage/mv/mv.command.ts`](../src/legacy/commands/storage/mv/mv.command.ts) | -| `storage rm` | `ported` | [`../src/legacy/commands/storage/rm/rm.command.ts`](../src/legacy/commands/storage/rm/rm.command.ts) | -| `test db` | `ported` | [`../src/legacy/commands/test/db/db.command.ts`](../src/legacy/commands/test/db/db.command.ts) | -| `test new` | `ported` | [`../src/legacy/commands/test/new/new.command.ts`](../src/legacy/commands/test/new/new.command.ts) | -| `seed buckets` | `ported` | [`../src/legacy/commands/seed/buckets/buckets.command.ts`](../src/legacy/commands/seed/buckets/buckets.command.ts) | -| `db diff` | `ported` | [`../src/legacy/commands/db/diff/diff.command.ts`](../src/legacy/commands/db/diff/diff.command.ts) — native pg-delta / migra; `--use-pgadmin` / `--use-pg-schema` delegate to Go | -| `db dump` | `ported` | [`../src/legacy/commands/db/dump/dump.command.ts`](../src/legacy/commands/db/dump/dump.command.ts) | -| `db push` | `wrapped` | [`../src/legacy/commands/db/push/push.command.ts`](../src/legacy/commands/db/push/push.command.ts) | -| `db pull` | `ported` | [`../src/legacy/commands/db/pull/pull.command.ts`](../src/legacy/commands/db/pull/pull.command.ts) — native pg-delta / migra; `--declarative` (deprecated alias `--use-pg-delta`) + `--diff-engine` (migra\|pg-delta); initial-migra pull dumps the schema natively (`pg_dump`) + appends the diff; only `--experimental` structured dump still delegates to Go (needs a TS DDL parser for `WriteStructuredSchemas`) | -| `db reset` | `wrapped` | [`../src/legacy/commands/db/reset/reset.command.ts`](../src/legacy/commands/db/reset/reset.command.ts) — includes Go-parity `--sql-paths` override for `[db.seed].sql_paths` | -| `db lint` | `ported` | [`../src/legacy/commands/db/lint/lint.command.ts`](../src/legacy/commands/db/lint/lint.command.ts) | -| `db start` | `wrapped` | [`../src/legacy/commands/db/start/start.command.ts`](../src/legacy/commands/db/start/start.command.ts) | -| `db query` | `ported` | [`../src/legacy/commands/db/query/query.command.ts`](../src/legacy/commands/db/query/query.command.ts) | -| `db advisors` | `ported` | [`../src/legacy/commands/db/advisors/advisors.command.ts`](../src/legacy/commands/db/advisors/advisors.command.ts) | -| `db test` | `wrapped` | [`../src/legacy/commands/db/test/test.command.ts`](../src/legacy/commands/db/test/test.command.ts) | -| `db branch create` | `wrapped` | [`../src/legacy/commands/db/branch/create/create.command.ts`](../src/legacy/commands/db/branch/create/create.command.ts) | -| `db branch delete` | `wrapped` | [`../src/legacy/commands/db/branch/delete/delete.command.ts`](../src/legacy/commands/db/branch/delete/delete.command.ts) | -| `db branch list` | `wrapped` | [`../src/legacy/commands/db/branch/list/list.command.ts`](../src/legacy/commands/db/branch/list/list.command.ts) | -| `db branch switch` | `wrapped` | [`../src/legacy/commands/db/branch/switch/switch.command.ts`](../src/legacy/commands/db/branch/switch/switch.command.ts) | -| `db remote changes` | `wrapped` | [`../src/legacy/commands/db/remote/changes/changes.command.ts`](../src/legacy/commands/db/remote/changes/changes.command.ts) | -| `db remote commit` | `wrapped` | [`../src/legacy/commands/db/remote/commit/commit.command.ts`](../src/legacy/commands/db/remote/commit/commit.command.ts) | -| `db schema declarative sync` | `ported` | [`../src/legacy/commands/db/schema/declarative/sync/sync.command.ts`](../src/legacy/commands/db/schema/declarative/sync/sync.command.ts) | -| `db schema declarative generate` | `ported` | [`../src/legacy/commands/db/schema/declarative/generate/generate.command.ts`](../src/legacy/commands/db/schema/declarative/generate/generate.command.ts) | +| Command | Legacy status | Legacy command path | +| -------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `orgs list` | `ported` | [`../src/legacy/commands/orgs/list/list.command.ts`](../src/legacy/commands/orgs/list/list.command.ts) | +| `orgs create` | `ported` | [`../src/legacy/commands/orgs/create/create.command.ts`](../src/legacy/commands/orgs/create/create.command.ts) | +| `projects list` | `ported` | [`../src/legacy/commands/projects/list/list.command.ts`](../src/legacy/commands/projects/list/list.command.ts) | +| `projects create` | `ported` | [`../src/legacy/commands/projects/create/create.command.ts`](../src/legacy/commands/projects/create/create.command.ts) | +| `projects delete` | `ported` | [`../src/legacy/commands/projects/delete/delete.command.ts`](../src/legacy/commands/projects/delete/delete.command.ts) | +| `projects api-keys` | `ported` | [`../src/legacy/commands/projects/api-keys/api-keys.command.ts`](../src/legacy/commands/projects/api-keys/api-keys.command.ts) | +| `branches list` | `ported` | [`../src/legacy/commands/branches/list/list.command.ts`](../src/legacy/commands/branches/list/list.command.ts) | +| `branches create` | `ported` | [`../src/legacy/commands/branches/create/create.command.ts`](../src/legacy/commands/branches/create/create.command.ts) | +| `branches get` | `ported` | [`../src/legacy/commands/branches/get/get.command.ts`](../src/legacy/commands/branches/get/get.command.ts) | +| `branches update` | `ported` | [`../src/legacy/commands/branches/update/update.command.ts`](../src/legacy/commands/branches/update/update.command.ts) | +| `branches pause` | `ported` | [`../src/legacy/commands/branches/pause/pause.command.ts`](../src/legacy/commands/branches/pause/pause.command.ts) | +| `branches unpause` | `ported` | [`../src/legacy/commands/branches/unpause/unpause.command.ts`](../src/legacy/commands/branches/unpause/unpause.command.ts) | +| `branches delete` | `ported` | [`../src/legacy/commands/branches/delete/delete.command.ts`](../src/legacy/commands/branches/delete/delete.command.ts) | +| `branches disable` | `ported` | [`../src/legacy/commands/branches/disable/disable.command.ts`](../src/legacy/commands/branches/disable/disable.command.ts) | +| `secrets list` | `ported` | [`../src/legacy/commands/secrets/list/list.command.ts`](../src/legacy/commands/secrets/list/list.command.ts) | +| `secrets set` | `ported` | [`../src/legacy/commands/secrets/set/set.command.ts`](../src/legacy/commands/secrets/set/set.command.ts) | +| `secrets unset` | `ported` | [`../src/legacy/commands/secrets/unset/unset.command.ts`](../src/legacy/commands/secrets/unset/unset.command.ts) | +| `config push` | `ported` | [`../src/legacy/commands/config/push/push.command.ts`](../src/legacy/commands/config/push/push.command.ts) | +| `backups list` | `ported` | [`../src/legacy/commands/backups/list/list.command.ts`](../src/legacy/commands/backups/list/list.command.ts) | +| `backups restore` | `ported` | [`../src/legacy/commands/backups/restore/restore.command.ts`](../src/legacy/commands/backups/restore/restore.command.ts) | +| `snippets list` | `ported` | [`../src/legacy/commands/snippets/list/list.command.ts`](../src/legacy/commands/snippets/list/list.command.ts) | +| `snippets download` | `ported` | [`../src/legacy/commands/snippets/download/download.command.ts`](../src/legacy/commands/snippets/download/download.command.ts) | +| `sso list` | `ported` | [`../src/legacy/commands/sso/list/list.command.ts`](../src/legacy/commands/sso/list/list.command.ts) | +| `sso add` | `ported` | [`../src/legacy/commands/sso/add/add.command.ts`](../src/legacy/commands/sso/add/add.command.ts) | +| `sso remove` | `ported` | [`../src/legacy/commands/sso/remove/remove.command.ts`](../src/legacy/commands/sso/remove/remove.command.ts) | +| `sso update` | `ported` | [`../src/legacy/commands/sso/update/update.command.ts`](../src/legacy/commands/sso/update/update.command.ts) | +| `sso show` | `ported` | [`../src/legacy/commands/sso/show/show.command.ts`](../src/legacy/commands/sso/show/show.command.ts) | +| `sso info` | `ported` | [`../src/legacy/commands/sso/info/info.command.ts`](../src/legacy/commands/sso/info/info.command.ts) | +| `domains create` | `ported` | [`../src/legacy/commands/domains/create/create.command.ts`](../src/legacy/commands/domains/create/create.command.ts) | +| `domains get` | `ported` | [`../src/legacy/commands/domains/get/get.command.ts`](../src/legacy/commands/domains/get/get.command.ts) | +| `domains reverify` | `ported` | [`../src/legacy/commands/domains/reverify/reverify.command.ts`](../src/legacy/commands/domains/reverify/reverify.command.ts) | +| `domains activate` | `ported` | [`../src/legacy/commands/domains/activate/activate.command.ts`](../src/legacy/commands/domains/activate/activate.command.ts) | +| `domains delete` | `ported` | [`../src/legacy/commands/domains/delete/delete.command.ts`](../src/legacy/commands/domains/delete/delete.command.ts) | +| `vanity-subdomains get` | `ported` | [`../src/legacy/commands/vanity-subdomains/get/get.command.ts`](../src/legacy/commands/vanity-subdomains/get/get.command.ts) | +| `vanity-subdomains check-availability` | `ported` | [`../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts`](../src/legacy/commands/vanity-subdomains/check-availability/check-availability.command.ts) | +| `vanity-subdomains activate` | `ported` | [`../src/legacy/commands/vanity-subdomains/activate/activate.command.ts`](../src/legacy/commands/vanity-subdomains/activate/activate.command.ts) | +| `vanity-subdomains delete` | `ported` | [`../src/legacy/commands/vanity-subdomains/delete/delete.command.ts`](../src/legacy/commands/vanity-subdomains/delete/delete.command.ts) | +| `network-bans get` | `ported` | [`../src/legacy/commands/network-bans/get/get.command.ts`](../src/legacy/commands/network-bans/get/get.command.ts) | +| `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` | `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) | +| `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` | `ported` | [`../src/legacy/commands/login/login.command.ts`](../src/legacy/commands/login/login.command.ts) | +| `logout` | `ported` | [`../src/legacy/commands/logout/logout.command.ts`](../src/legacy/commands/logout/logout.command.ts) | +| `link` | `ported` | [`../src/legacy/commands/link/link.command.ts`](../src/legacy/commands/link/link.command.ts) | +| `unlink` | `ported` | [`../src/legacy/commands/unlink/unlink.command.ts`](../src/legacy/commands/unlink/unlink.command.ts) | +| `bootstrap` | `ported` | [`../src/legacy/commands/bootstrap/bootstrap.command.ts`](../src/legacy/commands/bootstrap/bootstrap.command.ts) (native; `db push` step delegated to the Go binary — interim) | +| `init` | `ported` | [`../src/legacy/commands/init/init.command.ts`](../src/legacy/commands/init/init.command.ts) | +| `services` | `ported` | [`../src/legacy/commands/services/services.command.ts`](../src/legacy/commands/services/services.command.ts) | +| `start` | `wrapped` | [`../src/legacy/commands/start/start.command.ts`](../src/legacy/commands/start/start.command.ts) | +| `stop` | `wrapped` | [`../src/legacy/commands/stop/stop.command.ts`](../src/legacy/commands/stop/stop.command.ts) | +| `status` | `wrapped` | [`../src/legacy/commands/status/status.command.ts`](../src/legacy/commands/status/status.command.ts) | +| `telemetry enable` | `ported` | [`../src/legacy/commands/telemetry/enable/enable.command.ts`](../src/legacy/commands/telemetry/enable/enable.command.ts) | +| `telemetry disable` | `ported` | [`../src/legacy/commands/telemetry/disable/disable.command.ts`](../src/legacy/commands/telemetry/disable/disable.command.ts) | +| `telemetry status` | `ported` | [`../src/legacy/commands/telemetry/status/status.command.ts`](../src/legacy/commands/telemetry/status/status.command.ts) | +| `migration list` | `ported` | [`../src/legacy/commands/migration/list/list.command.ts`](../src/legacy/commands/migration/list/list.command.ts) — native; merged Local/Remote/Time-UTC Glamour table | +| `migration new` | `ported` | [`../src/legacy/commands/migration/new/new.command.ts`](../src/legacy/commands/migration/new/new.command.ts) — native; writes `supabase/migrations/_.sql` from piped stdin | +| `migration repair` | `ported` | [`../src/legacy/commands/migration/repair/repair.command.ts`](../src/legacy/commands/migration/repair/repair.command.ts) — native; transactional TRUNCATE/UPSERT/DELETE, repair-all prompt | +| `migration squash` | `wrapped` | [`../src/legacy/commands/migration/squash/squash.command.ts`](../src/legacy/commands/migration/squash/squash.command.ts) | +| `migration up` | `ported` | [`../src/legacy/commands/migration/up/up.command.ts`](../src/legacy/commands/migration/up/up.command.ts) — native; pending compute + vault upsert + per-file apply | +| `migration down` | `ported` | [`../src/legacy/commands/migration/down/down.command.ts`](../src/legacy/commands/migration/down/down.command.ts) — native; drop + vault + migrate&seed to target version | +| `migration fetch` | `ported` | [`../src/legacy/commands/migration/fetch/fetch.command.ts`](../src/legacy/commands/migration/fetch/fetch.command.ts) — native; writes history rows to `supabase/migrations/` | +| `gen types` | `ported` | [`../src/legacy/commands/gen/types/types.command.ts`](../src/legacy/commands/gen/types/types.command.ts) - native; non-TypeScript project refs use pg-meta with IPv4 pooler retry instead of Go's "Try using --db-url" failure; `--swift-access-control` requires `--lang swift`; explicit source flags with `--query-timeout` on the remote TypeScript path error, while the implicit linked TypeScript path warns and continues | +| `gen signing-key` | `ported` | [`../src/legacy/commands/gen/signing-key/signing-key.command.ts`](../src/legacy/commands/gen/signing-key/signing-key.command.ts) | +| `gen bearer-jwt` | `wrapped` | [`../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts`](../src/legacy/commands/gen/bearer-jwt/bearer-jwt.command.ts) | +| `gen keys` | `wrapped` | [`../src/legacy/commands/gen/keys/keys.command.ts`](../src/legacy/commands/gen/keys/keys.command.ts) | +| `functions list` | `wrapped` | [`../src/legacy/commands/functions/list/list.command.ts`](../src/legacy/commands/functions/list/list.command.ts) | +| `functions delete` | `ported` | [`../src/legacy/commands/functions/delete/delete.command.ts`](../src/legacy/commands/functions/delete/delete.command.ts) | +| `functions download` | `ported` | [`../src/legacy/commands/functions/download/download.command.ts`](../src/legacy/commands/functions/download/download.command.ts) | +| `functions deploy` | `ported` | [`../src/legacy/commands/functions/deploy/deploy.command.ts`](../src/legacy/commands/functions/deploy/deploy.command.ts) | +| `functions new` | `ported` | [`../src/legacy/commands/functions/new/new.command.ts`](../src/legacy/commands/functions/new/new.command.ts) | +| `functions serve` | `ported` | [`../src/legacy/commands/functions/serve/serve.command.ts`](../src/legacy/commands/functions/serve/serve.command.ts) | +| `storage ls` | `ported` | [`../src/legacy/commands/storage/ls/ls.command.ts`](../src/legacy/commands/storage/ls/ls.command.ts) | +| `storage cp` | `ported` | [`../src/legacy/commands/storage/cp/cp.command.ts`](../src/legacy/commands/storage/cp/cp.command.ts) | +| `storage mv` | `ported` | [`../src/legacy/commands/storage/mv/mv.command.ts`](../src/legacy/commands/storage/mv/mv.command.ts) | +| `storage rm` | `ported` | [`../src/legacy/commands/storage/rm/rm.command.ts`](../src/legacy/commands/storage/rm/rm.command.ts) | +| `test db` | `ported` | [`../src/legacy/commands/test/db/db.command.ts`](../src/legacy/commands/test/db/db.command.ts) | +| `test new` | `ported` | [`../src/legacy/commands/test/new/new.command.ts`](../src/legacy/commands/test/new/new.command.ts) | +| `seed buckets` | `ported` | [`../src/legacy/commands/seed/buckets/buckets.command.ts`](../src/legacy/commands/seed/buckets/buckets.command.ts) | +| `db diff` | `ported` | [`../src/legacy/commands/db/diff/diff.command.ts`](../src/legacy/commands/db/diff/diff.command.ts) — native pg-delta / migra; `--use-pgadmin` / `--use-pg-schema` delegate to Go | +| `db dump` | `ported` | [`../src/legacy/commands/db/dump/dump.command.ts`](../src/legacy/commands/db/dump/dump.command.ts) | +| `db push` | `wrapped` | [`../src/legacy/commands/db/push/push.command.ts`](../src/legacy/commands/db/push/push.command.ts) | +| `db pull` | `ported` | [`../src/legacy/commands/db/pull/pull.command.ts`](../src/legacy/commands/db/pull/pull.command.ts) — native pg-delta / migra; `--declarative` (deprecated alias `--use-pg-delta`) + `--diff-engine` (migra\|pg-delta); initial-migra pull dumps the schema natively (`pg_dump`) + appends the diff; only `--experimental` structured dump still delegates to Go (needs a TS DDL parser for `WriteStructuredSchemas`) | +| `db reset` | `wrapped` | [`../src/legacy/commands/db/reset/reset.command.ts`](../src/legacy/commands/db/reset/reset.command.ts) — includes Go-parity `--sql-paths` override for `[db.seed].sql_paths` | +| `db lint` | `ported` | [`../src/legacy/commands/db/lint/lint.command.ts`](../src/legacy/commands/db/lint/lint.command.ts) | +| `db start` | `wrapped` | [`../src/legacy/commands/db/start/start.command.ts`](../src/legacy/commands/db/start/start.command.ts) | +| `db query` | `ported` | [`../src/legacy/commands/db/query/query.command.ts`](../src/legacy/commands/db/query/query.command.ts) | +| `db advisors` | `ported` | [`../src/legacy/commands/db/advisors/advisors.command.ts`](../src/legacy/commands/db/advisors/advisors.command.ts) | +| `db test` | `wrapped` | [`../src/legacy/commands/db/test/test.command.ts`](../src/legacy/commands/db/test/test.command.ts) | +| `db branch create` | `wrapped` | [`../src/legacy/commands/db/branch/create/create.command.ts`](../src/legacy/commands/db/branch/create/create.command.ts) | +| `db branch delete` | `wrapped` | [`../src/legacy/commands/db/branch/delete/delete.command.ts`](../src/legacy/commands/db/branch/delete/delete.command.ts) | +| `db branch list` | `wrapped` | [`../src/legacy/commands/db/branch/list/list.command.ts`](../src/legacy/commands/db/branch/list/list.command.ts) | +| `db branch switch` | `wrapped` | [`../src/legacy/commands/db/branch/switch/switch.command.ts`](../src/legacy/commands/db/branch/switch/switch.command.ts) | +| `db remote changes` | `wrapped` | [`../src/legacy/commands/db/remote/changes/changes.command.ts`](../src/legacy/commands/db/remote/changes/changes.command.ts) | +| `db remote commit` | `wrapped` | [`../src/legacy/commands/db/remote/commit/commit.command.ts`](../src/legacy/commands/db/remote/commit/commit.command.ts) | +| `db schema declarative sync` | `ported` | [`../src/legacy/commands/db/schema/declarative/sync/sync.command.ts`](../src/legacy/commands/db/schema/declarative/sync/sync.command.ts) | +| `db schema declarative generate` | `ported` | [`../src/legacy/commands/db/schema/declarative/generate/generate.command.ts`](../src/legacy/commands/db/schema/declarative/generate/generate.command.ts) | Flag divergences from the Go reference: diff --git a/apps/cli/src/legacy/commands/branches/branches.format.ts b/apps/cli/src/legacy/commands/branches/branches.format.ts index c410c5dcd7..2cf1249d39 100644 --- a/apps/cli/src/legacy/commands/branches/branches.format.ts +++ b/apps/cli/src/legacy/commands/branches/branches.format.ts @@ -111,6 +111,9 @@ export type PoolerParseResult = * error message (e.g. `failed to parse pooler URL: parse "...": invalid port`), * never the URL itself. Returning the URL would leak the pooler username, * host, and port into stderr logs. + * + * This display-only parser intentionally does not enforce the profile-domain or + * tenant-ref guards used by `legacyPoolerConfigFromConnectionString`. */ export function parsePoolerConnectionString(connString: string): PoolerParseResult { const sanitized = connString.replaceAll(POOLER_PASSWORD_PLACEHOLDER, ""); diff --git a/apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts b/apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts index bc2d759e52..0278e7e41e 100644 --- a/apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts @@ -157,7 +157,10 @@ function setup(workdir: string, opts: SetupOpts = {}) { LegacyNetworkIdFlag, opts.networkId === undefined ? Option.none() : Option.some(opts.networkId), ), - Layer.succeed(LegacyPgDeltaSslProbe, { requireSsl: () => Effect.succeed(false) }), + Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), mockRuntimeInfo(), BunServices.layer, ); diff --git a/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts b/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts index 745521c02f..1e15768888 100644 --- a/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts @@ -242,7 +242,10 @@ function setup(workdir: string, opts: SetupOpts = {}) { Layer.succeed(LegacyExperimentalFlag, opts.experimental ?? false), Layer.succeed(LegacyDnsResolverFlag, "native"), Layer.succeed(LegacyNetworkIdFlag, Option.none()), - Layer.succeed(LegacyPgDeltaSslProbe, { requireSsl: () => Effect.succeed(false) }), + Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), Layer.succeed(CliArgs, { args: opts.args ?? [] }), mockRuntimeInfo(), BunServices.layer, diff --git a/apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.integration.test.ts b/apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.integration.test.ts index 19da790379..ad474a9a77 100644 --- a/apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.integration.test.ts @@ -51,6 +51,7 @@ function mockEdge(stdout: string) { // reports "not required", so no CA bundle/SSL env is injected. const probe = Layer.succeed(LegacyPgDeltaSslProbe, { requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), }); const ctx = (declarativeDir: string): LegacyDeclarativeRunContext => ({ diff --git a/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.integration.test.ts b/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.integration.test.ts index 938ceab885..8241336bd5 100644 --- a/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.integration.test.ts @@ -151,7 +151,10 @@ function setup(workdir: string, opts: SetupOpts = {}) { Layer.succeed(LegacyNetworkIdFlag, opts.networkId ?? Option.none()), Layer.succeed(LegacyDnsResolverFlag, "native"), // The remote ref is a non-Supabase host that refuses TLS → no SSL env. - Layer.succeed(LegacyPgDeltaSslProbe, { requireSsl: () => Effect.succeed(false) }), + Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), BunServices.layer, ); return { diff --git a/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.integration.test.ts b/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.integration.test.ts index aa2718ac62..d1e83e7290 100644 --- a/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.integration.test.ts @@ -158,7 +158,10 @@ function setup(workdir: string, opts: SetupOpts = {}) { ), Layer.succeed(LegacyDnsResolverFlag, "native"), // Sync diffs against the local DB, which refuses TLS → no SSL env injected. - Layer.succeed(LegacyPgDeltaSslProbe, { requireSsl: () => Effect.succeed(false) }), + Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), BunServices.layer, ); return { layer, out, execInheritCalls, dbExec, cache, localPostgresImageChecks }; diff --git a/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.integration.test.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.integration.test.ts index a90d2476e4..f6c816c461 100644 --- a/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.integration.test.ts +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.integration.test.ts @@ -48,6 +48,7 @@ function fakeEdgeRuntime(outcome: { stdout?: string; stderr?: string; fail?: str // "not required" — matching the no-SSL-env passthrough these tests assert. const probe = Layer.succeed(LegacyPgDeltaSslProbe, { requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), }); const failError = (exit: Exit.Exit) => diff --git a/apps/cli/src/legacy/commands/db/shared/legacy-pooler-fallback.ts b/apps/cli/src/legacy/commands/db/shared/legacy-pooler-fallback.ts index 18b7d819ad..9278b05691 100644 --- a/apps/cli/src/legacy/commands/db/shared/legacy-pooler-fallback.ts +++ b/apps/cli/src/legacy/commands/db/shared/legacy-pooler-fallback.ts @@ -1,10 +1,15 @@ import { Effect, Option } from "effect"; import { Output } from "../../../../shared/output/output.service.ts"; -import { legacyYellow } from "../../../shared/legacy-colors.ts"; import { legacyIsIPv6ConnectivityError } from "../../../shared/legacy-connect-errors.ts"; import type { LegacyPgConnInput } from "../../../shared/legacy-db-connection.service.ts"; import type { LegacyDbConnType } from "../../../shared/legacy-db-target-flags.ts"; +import { + legacyIsDirectDbHost, + legacyRunWithPoolerFallback as legacyRunWithSharedPoolerFallback, +} from "../../../shared/legacy-pooler-fallback.ts"; + +export { legacyEmitPoolerFallbackWarning } from "../../../shared/legacy-pooler-fallback.ts"; /** The exit/stderr pair a dump attempt surfaces for pooler-fallback classification. */ interface LegacyPoolerFallbackResult { @@ -30,24 +35,7 @@ export const legacyIsDirectLinkedHost = (params: { }): boolean => params.connType === "linked" && !params.isLocal && - params.host.startsWith("db.") && - params.host.endsWith(`.${params.projectHost}`); - -/** - * Go's IPv6 pooler-fallback warning (`internal/utils/connect.go:283-289`), to stderr, - * `Yellow`-wrapped, byte-for-byte. Emitted just before the IPv4 pooler retry by every - * pooler-fallback path. - */ -export const legacyEmitPoolerFallbackWarning = (host: string): Effect.Effect => - Effect.gen(function* () { - const output = yield* Output; - yield* output.raw( - `${legacyYellow( - `Warning: Direct connection to ${host} is unavailable because this environment does not support IPv6.\nRetrying via the IPv4 connection pooler.`, - )}\n`, - "stderr", - ); - }); + legacyIsDirectDbHost(params.host, params.projectHost); /** * Container-level IPv6 → IPv4-pooler retry shared by `db dump` and `db pull`'s initial @@ -96,23 +84,18 @@ export const legacyRunWithPoolerFallback = Effect.fnUntraced(function* /** `db dump` re-prints "Dumping ..." on retry; `db pull` passes `Effect.void`. */ readonly reprintOnRetry: Effect.Effect; }) { - const eligible = - params.result.exitCode !== 0 && - legacyIsDirectLinkedHost({ + return yield* legacyRunWithSharedPoolerFallback({ + run: Effect.succeed(params.result), + retry: (pooler) => params.reprintOnRetry.pipe(Effect.andThen(params.runWithConn(pooler))), + directHost: params.host, + eligible: legacyIsDirectLinkedHost({ connType: params.connType, host: params.host, isLocal: params.isLocal, projectHost: params.projectHost, - }) && - legacyIsIPv6ConnectivityError(params.result.stderr); - if (!eligible) { - return params.result; - } - const pooler = yield* params.resolvePooler(); - if (Option.isNone(pooler)) { - return params.result; - } - yield* legacyEmitPoolerFallbackWarning(params.host); - yield* params.reprintOnRetry; - return yield* params.runWithConn(pooler.value); + }), + resolveFallback: Effect.suspend(params.resolvePooler), + classifyResult: (result) => + result.exitCode !== 0 && legacyIsIPv6ConnectivityError(result.stderr), + }); }); diff --git a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md index fbfc293fa2..511b89d5f3 100644 --- a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md @@ -5,7 +5,7 @@ | Path | Format | When | | ----------------------------------------- | ---------- | ---------------------------------------------------------------------------------------- | | `~/.supabase/access-token` | plain text | when `SUPABASE_ACCESS_TOKEN` unset and `--linked` or `--project-id` | -| `/supabase/config.toml` | TOML | when `--local` (required) or `--db-url` (best-effort) is specified | +| `/supabase/config.toml` | TOML | when selecting schemas from config; required for `--local`, best-effort otherwise | | `/supabase/.temp/rest-version` | plain text | `--local` only, when `db.major_version > 14` — forces v9 compat if the tag contains `v9` | | `/supabase/.temp/pgmeta-version` | plain text | `--local` only — overrides the pg-meta docker image tag | @@ -16,50 +16,66 @@ | — | — | — | No files are written. Container env (including the DB URL and TLS CA bundle) is -passed via `docker run --env KEY=VALUE` arguments, mirroring Go's +passed via container CLI `run --env KEY=VALUE` arguments, mirroring Go's `container.Config.Env`; no temporary env-file is created. ## API Routes -| Method | Path | Auth | Request body | Response (used fields) | -| ------ | ------------------------------------- | ------------ | ------------ | -------------------------------- | -| `GET` | `/v1/projects/{ref}/types/typescript` | Bearer token | none | TypeScript type definitions text | - -Called only for `--linked`, `--project-id`, and the implicit linked-project -fallback. `--local` and `--db-url` do not call the Management API. +| Method | Path | Auth | Request body | Response (used fields) | +| ------ | ------------------------------------------- | ------------ | ---------------------- | ------------------------------------------ | +| `GET` | `/v1/projects/{ref}/types/typescript` | Bearer token | none | TypeScript type definitions text | +| `GET` | `/v1/projects/{ref}` | Bearer token | none | (presence only; `404` ⇒ branch ref) | +| `GET` | `/v1/branches/{branch_id_or_ref}` | Bearer token | none | `db_host`, `db_port`, `db_user`, `db_pass` | +| `POST` | `/v1/projects/{ref}/cli/login-role` | Bearer token | `{ read_only: false }` | temporary `role` and `password` | +| `GET` | `/v1/projects/{ref}/config/database/pooler` | Bearer token | none | primary pooler `connection_string` | + +The TypeScript endpoint is called for `--linked`, `--project-id`, and the implicit +linked-project fallback when `--lang=typescript`. For other languages on those +project-ref paths, the project endpoint is probed first: a `404` means the ref is a +preview branch (any 404 body), so the branch endpoint supplies the branch database +host/port and credentials for pg-meta. Otherwise the database connection is resolved +for the ref and the login-role endpoint supplies temporary credentials for pg-meta. +On an IPv4-only network where the direct database host is unreachable, project-ref +pg-meta generation retries once through the IPv4 pooler only when the current target +host is the project's direct `db.` host and the pooler URL matches the expected +tenant and pooler domain. An explicit `--project-id` ref fetches the primary pooler +config for that ref to build the fallback connection (the saved workdir +`.temp/pooler-url` is ignored because the ref may differ from the linked workdir). +`--local` and `--db-url` do not call the Management API. ## Subprocesses -| Command | When | Purpose | -| ----------------------------------------------------------------------------- | --------------------- | -------------------------------------------------- | -| `docker container inspect supabase_db_` | `--local` | assert `supabase start` is running | -| `docker run --rm --network --env … node dist/server/server.js` | `--local`, `--db-url` | run pg-meta to generate types from a live database | +| Command | When | Purpose | +| -------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------- | +| `docker`/`podman container inspect supabase_db_` | `--local` | assert `supabase start` is running | +| `docker`/`podman run --rm --network --env … node dist/server/server.js` | `--local`, `--db-url`, project-ref paths with non-TypeScript `--lang` | run pg-meta to generate types from a live database | A raw TCP `SSLRequest` probe is also opened to the target database host/port to -detect TLS support before launching pg-meta (mirrors Go's `isRequireSSL`). +detect TLS support before launching pg-meta (mirrors Go's `isRequireSSL`) with the +default 10s pg-delta probe timeout. ## Environment Variables -| Variable | Purpose | Required? | -| ---------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------- | -| `SUPABASE_ACCESS_TOKEN` | auth token for linked/project-id mode | no (falls back to keyring → `~/.supabase/access-token`) | -| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | -| `SUPABASE_DB_PASSWORD` | local database password for `--local` | no (defaults to `postgres`) | -| `SUPABASE_SERVICES_HOSTNAME` | host used for the local TLS probe | no (defaults to `127.0.0.1`) | -| `SUPABASE_INTERNAL_IMAGE_REGISTRY` | pg-meta image registry override (`docker.io` → Docker Hub) | no (defaults to the ECR registry) | -| `SUPABASE_CA_SKIP_VERIFY` | when `true`, prints a TLS-verification-disabled warning to stderr | no | +| Variable | Purpose | Required? | +| ---------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | +| `SUPABASE_ACCESS_TOKEN` | auth token for linked/project-id mode | no (falls back to keyring → `~/.supabase/access-token`) | +| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | +| `SUPABASE_DB_PASSWORD` | database password for `--local` and the `--linked` workdir project | no (defaults to `postgres`; **ignored** for ad-hoc `--project-id`, which always mints a temporary login role) | +| `SUPABASE_SERVICES_HOSTNAME` | host used for the local TLS probe | no (defaults to `127.0.0.1`) | +| `SUPABASE_INTERNAL_IMAGE_REGISTRY` | pg-meta image registry override (`docker.io` → Docker Hub) | no (defaults to the ECR registry) | +| `SUPABASE_CA_SKIP_VERIFY` | when `true`, prints a TLS-verification-disabled warning to stderr | no | ## Exit Codes -| Code | Condition | -| ---- | ---------------------------------------------------------------- | -| `0` | success — types printed to stdout | -| `1` | no target specified (must use one flag) | -| `1` | mutually exclusive flags combined | -| `1` | `--postgrest-v9-compat` used without `--db-url` | -| `1` | invalid `--query-timeout` duration or invalid `--db-url` | -| `1` | `supabase start` not running (`--local`) or db inspection failed | -| `1` | API error, TLS probe failure, or pg-meta container non-zero exit | +| Code | Condition | +| ---- | --------------------------------------------------------------------------------------------------------------------------- | +| `0` | success — types printed to stdout | +| `1` | no target specified (must use one flag) | +| `1` | mutually exclusive flags combined | +| `1` | pg-meta-only flags used with remote TypeScript generation, except implicit TypeScript `--query-timeout` warns and continues | +| `1` | invalid `--query-timeout` duration or invalid `--db-url` | +| `1` | `supabase start` not running (`--local`) or db inspection failed | +| `1` | API error, TLS probe failure, or pg-meta container non-zero exit | ## Output @@ -79,12 +95,18 @@ Not applicable. ## Notes - Exactly one of `--local`, `--linked`, `--project-id`, or `--db-url` must be specified. -- `--lang` flag accepts `typescript` (default), `go`, `swift`, or `python`. Non-typescript - languages require a direct database connection (`--local` or `--db-url`). +- `--lang` flag accepts `typescript` (default), `go`, `swift`, or `python`. Project-ref + paths use the Management API for TypeScript, and use a project database host + + temporary login role + pg-meta for other languages. - `--schema` / `-s` accepts a comma-separated list of schemas to include. -- `--swift-access-control` accepts `internal` (default) or `public`. -- `--postgrest-v9-compat` generates types compatible with PostgREST v9 and below (requires `--db-url`). -- `--query-timeout` sets the maximum timeout for the database query (default 15s, direct connection only). +- `--swift-access-control` accepts `internal` (default) or `public`, and requires + `--lang swift`. +- `--postgrest-v9-compat` generates types compatible with PostgREST v9 and below for pg-meta + generation (`--local`, `--db-url`, or non-TypeScript project-ref paths). +- `--query-timeout` sets the maximum timeout for pg-meta database queries (default 15s). + On remote TypeScript generation, explicit `--linked` or `--project-id` invocations + error because pg-meta is not used; the implicit linked TypeScript fallback prints a + warning and ignores the flag. - The legacy positional language argument (`supabase gen types typescript`) is still accepted; any other positional language requires an explicit `--lang` flag. - The linked-project telemetry cache is written only when a project ref is resolved diff --git a/apps/cli/src/legacy/commands/gen/types/types.e2e.test.ts b/apps/cli/src/legacy/commands/gen/types/types.e2e.test.ts new file mode 100644 index 0000000000..6d9a4032fc --- /dev/null +++ b/apps/cli/src/legacy/commands/gen/types/types.e2e.test.ts @@ -0,0 +1,390 @@ +import { spawn } from "node:child_process"; +import { mkdir, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { DEFAULT_VERSIONS, dockerImageForService } from "@supabase/stack/effect"; +import { describe, expect, test } from "vitest"; +import { + makeTempHome, + makeTempStackProject, + runSupabase, +} from "../../../../../tests/helpers/cli.ts"; +import { localDbContainerId, localNetworkId } from "../../../shared/legacy-docker-ids.ts"; + +const TYPEGEN_LANGS = ["typescript", "go", "swift", "python"] as const; +type TypegenLang = (typeof TYPEGEN_LANGS)[number]; + +const LOCAL_POSTGRES_IMAGE = dockerImageForService("postgres", DEFAULT_VERSIONS.postgres); +const LOCAL_POSTGRES_TIMEOUT_MS = 120_000; +const TYPEGEN_TIMEOUT_MS = 90_000; +const REMOTE_E2E_FLAG = "SUPABASE_TYPEGEN_E2E_REMOTE"; +const REMOTE_PROJECT_REF_ENV = "SUPABASE_TEST_PROJECT_REF"; +const OUTPUT_TAIL_LENGTH = 4_000; + +interface CommandResult { + readonly stdout: string; + readonly stderr: string; + readonly exitCode: number; +} + +function tokenlessEnv(profilePath: string, projectDir: string) { + return { + SUPABASE_ACCESS_TOKEN: "", + SUPABASE_DB_PASSWORD: "postgres", + SUPABASE_PROFILE: profilePath, + SUPABASE_WORKDIR: projectDir, + }; +} + +function remoteEnv(accessToken: string, projectDir: string) { + return { + SUPABASE_ACCESS_TOKEN: accessToken, + SUPABASE_DB_PASSWORD: "", + SUPABASE_WORKDIR: projectDir, + }; +} + +async function writeOfflineProfile(projectDir: string): Promise { + const profilePath = join(projectDir, "offline-profile.yaml"); + await writeFile( + profilePath, + [ + "name: cli-typegen-e2e", + 'api_url: "http://127.0.0.1:1"', + 'dashboard_url: "http://127.0.0.1:1/dashboard"', + 'docs_url: "http://127.0.0.1:1/docs"', + 'project_host: "example.invalid"', + 'pooler_host: ""', + "", + ].join("\n"), + ); + return profilePath; +} + +async function writeLocalConfig(projectDir: string, projectId: string, dbPort: number) { + const supabaseDir = join(projectDir, "supabase"); + await mkdir(supabaseDir, { recursive: true }); + await writeFile( + join(supabaseDir, "config.toml"), + [ + `project_id = "${projectId}"`, + "", + "[api]", + 'schemas = ["public"]', + "", + "[db]", + `port = ${dbPort}`, + "major_version = 17", + "", + ].join("\n"), + ); +} + +function combinedOutput(result: { stdout: string; stderr: string }) { + return `${result.stdout}\n${result.stderr}`; +} + +function expectSucceeded( + command: string, + result: { stdout: string; stderr: string; exitCode: number }, +) { + expect(result.exitCode, `${command}\n${combinedOutput(result)}`).toBe(0); +} + +function outputTail(output: string) { + return output.length > OUTPUT_TAIL_LENGTH + ? output.slice(output.length - OUTPUT_TAIL_LENGTH) + : output; +} + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function runCommand( + command: string, + args: ReadonlyArray, + options: { readonly timeoutMs?: number } = {}, +): Promise { + return new Promise((resolve) => { + const child = spawn(command, args, { + stdio: ["ignore", "pipe", "pipe"], + }); + let stdout = ""; + let stderr = ""; + let settled = false; + let timedOut = false; + const timer = + options.timeoutMs === undefined + ? undefined + : setTimeout(() => { + timedOut = true; + child.kill("SIGKILL"); + }, options.timeoutMs); + + child.stdout?.on("data", (data: Buffer) => { + stdout += data.toString(); + }); + child.stderr?.on("data", (data: Buffer) => { + stderr += data.toString(); + }); + child.once("error", (error) => { + if (settled) return; + settled = true; + if (timer !== undefined) clearTimeout(timer); + resolve({ stdout, stderr: `${stderr}${String(error)}`, exitCode: 1 }); + }); + child.once("close", (code) => { + if (settled) return; + settled = true; + if (timer !== undefined) clearTimeout(timer); + resolve({ + stdout, + stderr: timedOut ? `${stderr}\nTimed out after ${options.timeoutMs}ms` : stderr, + exitCode: code ?? 1, + }); + }); + }); +} + +function runDocker(args: ReadonlyArray, options?: { readonly timeoutMs?: number }) { + return runCommand("docker", args, options); +} + +async function expectDockerSucceeded(args: ReadonlyArray, timeoutMs?: number) { + const result = await runDocker(args, { timeoutMs }); + expectSucceeded(`docker ${args.join(" ")}`, result); + return result; +} + +async function waitForLocalPostgres(containerName: string) { + const startedAt = Date.now(); + let lastResult: CommandResult = { stdout: "", stderr: "", exitCode: 1 }; + let consecutiveReadyChecks = 0; + while (Date.now() - startedAt < LOCAL_POSTGRES_TIMEOUT_MS) { + lastResult = await runDocker( + [ + "exec", + "-e", + "PGPASSWORD=postgres", + containerName, + "psql", + "-U", + "postgres", + "-d", + "postgres", + "-tAc", + "select 1", + ], + { timeoutMs: 5_000 }, + ); + if (lastResult.exitCode === 0 && lastResult.stdout.trim() === "1") { + consecutiveReadyChecks += 1; + } else { + consecutiveReadyChecks = 0; + } + if (consecutiveReadyChecks >= 2) { + return; + } + await sleep(1_000); + } + + const logs = await runDocker(["logs", containerName], { timeoutMs: 10_000 }); + throw new Error( + [ + `Timed out waiting for ${containerName}`, + outputTail(combinedOutput(lastResult)), + outputTail(combinedOutput(logs)), + ].join("\n"), + ); +} + +async function startLocalPostgres(input: { readonly projectId: string; readonly dbPort: number }) { + const containerName = localDbContainerId(input.projectId); + const networkName = localNetworkId(input.projectId); + + await expectDockerSucceeded(["network", "create", networkName], 30_000); + await expectDockerSucceeded( + [ + "run", + "--detach", + "--rm", + "--name", + containerName, + "--network", + networkName, + "--network-alias", + "db", + "-p", + `${input.dbPort}:5432`, + "-e", + "POSTGRES_PASSWORD=postgres", + LOCAL_POSTGRES_IMAGE, + "postgres", + "-D", + "/etc/postgresql", + "-c", + "wal_level=logical", + "-c", + "max_wal_senders=5", + "-c", + "max_replication_slots=5", + ], + LOCAL_POSTGRES_TIMEOUT_MS, + ); + await waitForLocalPostgres(containerName); + + return { containerName, networkName }; +} + +async function seedSmokeTable(containerName: string) { + await expectDockerSucceeded( + [ + "exec", + "-e", + "PGPASSWORD=postgres", + containerName, + "psql", + "-U", + "postgres", + "-d", + "postgres", + "-v", + "ON_ERROR_STOP=1", + "-c", + [ + "create table if not exists public.typegen_smoke (", + "id bigint generated by default as identity primary key,", + "name text not null,", + "is_active boolean not null default true,", + "created_at timestamptz not null default now()", + ");", + ].join(" "), + ], + 30_000, + ); +} + +async function cleanupLocalPostgres(input: { + readonly containerName: string; + readonly networkName: string; +}) { + await runDocker(["rm", "-f", input.containerName], { timeoutMs: 30_000 }); + await runDocker(["network", "rm", input.networkName], { timeoutMs: 30_000 }); +} + +function expectNoRemoteAuthPath(result: { stdout: string; stderr: string }) { + const output = combinedOutput(result); + expect(output).not.toContain("Access token not provided"); + expect(output).not.toContain("api.supabase.com"); + expect(output).not.toContain("127.0.0.1:1"); +} + +function expectLanguageShape(lang: TypegenLang, stdout: string) { + expect(stdout.trim().length, `${lang} stdout`).toBeGreaterThan(0); + switch (lang) { + case "typescript": + expect(stdout).toContain("export type Database"); + break; + case "go": + expect(stdout).toMatch(/\btype\b/); + break; + case "swift": + expect(stdout).toMatch(/\bstruct\b/); + break; + case "python": + expect(stdout).toContain("from __future__ import annotations"); + break; + } +} + +function expectLocalSmokeTable(lang: TypegenLang, stdout: string) { + if (lang === "typescript") { + expect(stdout).toContain("typegen_smoke"); + return; + } + expect(stdout).toContain("TypegenSmoke"); +} + +describe("legacy gen types e2e", () => { + test( + "generates all supported languages from a tokenless local stack", + { timeout: LOCAL_POSTGRES_TIMEOUT_MS + TYPEGEN_TIMEOUT_MS * TYPEGEN_LANGS.length }, + async () => { + const home = makeTempHome(); + const project = await makeTempStackProject("supabase-typegen-local-e2e-"); + const projectId = `typegen${project.ports.dbPort}`; + const profilePath = await writeOfflineProfile(project.dir); + const env = tokenlessEnv(profilePath, project.dir); + const localPostgres = { + containerName: localDbContainerId(projectId), + networkName: localNetworkId(projectId), + }; + + try { + await writeLocalConfig(project.dir, projectId, project.ports.dbPort); + await cleanupLocalPostgres(localPostgres); + await startLocalPostgres({ projectId, dbPort: project.ports.dbPort }); + await seedSmokeTable(localPostgres.containerName); + + for (const lang of TYPEGEN_LANGS) { + const result = await runSupabase( + ["gen", "types", "--local", "--lang", lang, "--schema", "public"], + { + cwd: project.dir, + home: home.dir, + env, + entrypoint: "legacy", + exitTimeoutMs: TYPEGEN_TIMEOUT_MS, + }, + ); + expectSucceeded(`supabase gen types --local --lang ${lang}`, result); + expectNoRemoteAuthPath(result); + expectLanguageShape(lang, result.stdout); + expectLocalSmokeTable(lang, result.stdout); + } + } finally { + await cleanupLocalPostgres(localPostgres); + } + }, + ); + + const remoteProjectRef = process.env[REMOTE_PROJECT_REF_ENV]; + const remoteAccessToken = process.env["SUPABASE_ACCESS_TOKEN"]; + const remoteEnabled = process.env[REMOTE_E2E_FLAG] === "1"; + + const remoteTest = remoteEnabled ? test : test.skip; + + remoteTest( + "generates all supported languages from a remote project", + { timeout: TYPEGEN_TIMEOUT_MS * TYPEGEN_LANGS.length }, + async () => { + const home = makeTempHome(); + const project = await makeTempStackProject("supabase-typegen-remote-e2e-"); + if ( + remoteProjectRef === undefined || + remoteProjectRef.length === 0 || + remoteAccessToken === undefined || + remoteAccessToken.length === 0 + ) { + throw new Error( + `Set ${REMOTE_E2E_FLAG}=1, ${REMOTE_PROJECT_REF_ENV}, and SUPABASE_ACCESS_TOKEN to run remote typegen e2e.`, + ); + } + + for (const lang of TYPEGEN_LANGS) { + const result = await runSupabase( + ["gen", "types", "--project-id", remoteProjectRef, "--lang", lang, "--schema", "public"], + { + cwd: project.dir, + home: home.dir, + env: remoteEnv(remoteAccessToken, project.dir), + entrypoint: "legacy", + exitTimeoutMs: TYPEGEN_TIMEOUT_MS, + }, + ); + expectSucceeded(`supabase gen types --project-id --lang ${lang}`, result); + expectLanguageShape(lang, result.stdout); + } + }, + ); +}); diff --git a/apps/cli/src/legacy/commands/gen/types/types.handler.ts b/apps/cli/src/legacy/commands/gen/types/types.handler.ts index 156d5367b2..6008a3849e 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.handler.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.handler.ts @@ -1,7 +1,11 @@ import { loadProjectConfig } from "@supabase/config"; import { ChildProcessSpawner } from "effect/unstable/process"; import { Effect, FileSystem, Option, Path, Stdio, Stream } from "effect"; -import { LegacyDebugFlag, LegacyNetworkIdFlag } from "../../../../shared/legacy/global-flags.ts"; +import { + LegacyDebugFlag, + LegacyDnsResolverFlag, + LegacyNetworkIdFlag, +} from "../../../../shared/legacy/global-flags.ts"; import { Output } from "../../../../shared/output/output.service.ts"; import { LegacyCliConfig } from "../../../config/legacy-cli-config.service.ts"; import { LegacyProjectNotLinkedError } from "../../../config/legacy-project-ref.errors.ts"; @@ -10,23 +14,36 @@ import { PROJECT_NOT_LINKED_MESSAGE, } from "../../../config/legacy-project-ref.service.ts"; import { spawnContainerCli } from "../../../shared/legacy-container-cli.ts"; +import { + legacyIsIPv6ConnectivityError, + legacyIsIPv6ConnectivityErrorCause, +} from "../../../shared/legacy-connect-errors.ts"; import { mapLegacyHttpError } from "../../../shared/legacy-http-errors.ts"; +import { LegacyDbConfigResolver } from "../../../shared/legacy-db-config.service.ts"; +import type { LegacyDbConfigFlags } from "../../../shared/legacy-db-config.types.ts"; +import { legacyPoolerConfigFromConnectionString } from "../../../shared/legacy-db-config.parse.ts"; +import type { LegacyPgConnInput } from "../../../shared/legacy-db-connection.service.ts"; +import { legacyToPostgresURL } from "../../../shared/legacy-postgres-url.ts"; import { legacyTempPaths } from "../../../shared/legacy-temp-paths.ts"; import { LegacyLinkedProjectCache } from "../../../telemetry/legacy-linked-project-cache.service.ts"; import { LegacyTelemetryState } from "../../../telemetry/legacy-telemetry-state.service.ts"; +import { LegacyPgDeltaSslProbe } from "../../../shared/legacy-pgdelta-ssl-probe.service.ts"; +import { + legacyIsDirectDbHost, + legacyRunWithPoolerFallback, +} from "../../../shared/legacy-pooler-fallback.ts"; import type { LegacyGenTypesFlags } from "./types.command.ts"; import { LegacyGenTypesNetworkError, LegacyGenTypesUnexpectedStatusError } from "./types.errors.ts"; import { legacyGetHostname } from "../../../shared/legacy-hostname.ts"; import { LegacyPlatformApiFactory } from "../../../auth/legacy-platform-api-factory.service.ts"; import { - buildPostgresUrl, defaultSchemas, + buildPostgresUrl, localDbContainerId, localDbPassword, localNetworkId, parseDatabaseUrl, parseQueryTimeoutSeconds, - probeTlsSupport, legacyRootCaBundle, resolvePgmetaImage, } from "./types.shared.ts"; @@ -38,6 +55,30 @@ const mapProjectTypesError = mapLegacyHttpError({ statusMessage: (_status, body) => `failed to retrieve generated types: ${body}`, }); +const mapProjectDatabaseHostError = mapLegacyHttpError({ + networkError: LegacyGenTypesNetworkError, + statusError: LegacyGenTypesUnexpectedStatusError, + networkMessage: (cause) => `failed to get project database config: ${cause}`, + statusMessage: (status, body) => `unexpected project database config status ${status}: ${body}`, +}); + +const mapBranchDatabaseConfigError = mapLegacyHttpError({ + networkError: LegacyGenTypesNetworkError, + statusError: LegacyGenTypesUnexpectedStatusError, + networkMessage: (cause) => `failed to get preview branch database config: ${cause}`, + statusMessage: (status, body) => + `unexpected preview branch database config status ${status}: ${body}`, +}); + +// A 404 from `GET /v1/projects/{ref}` means the ref is a preview branch rather +// than a project, so fall back to the branch config endpoint. Mirror the link +// handler, which treats *any* 404 as the branch case +// (`link.handler.ts:46-50` / Go's `checkRemoteProjectStatus`); do not narrow on +// the response body, since the Management API's 404 wording is not guaranteed. +function isProjectNotFound(cause: unknown) { + return cause instanceof LegacyGenTypesUnexpectedStatusError && cause.status === 404; +} + function ensureMutuallyExclusive( group: ReadonlyArray, present: ReadonlyArray, @@ -167,12 +208,15 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le const path = yield* Path.Path; const stdio = yield* Stdio.Stdio; const networkId = yield* LegacyNetworkIdFlag; + const dnsResolver = yield* LegacyDnsResolverFlag; const debug = yield* LegacyDebugFlag; const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; const rawArgs = yield* stdio.args; const platformApi = yield* LegacyPlatformApiFactory; const projectRef = yield* LegacyProjectRefResolver; const linkedProjectCache = yield* LegacyLinkedProjectCache; + const dbConfig = yield* LegacyDbConfigResolver; + const sslProbe = yield* LegacyPgDeltaSslProbe; yield* ensureMutuallyExclusive( ["local", "linked", "project-id", "db-url"], @@ -183,34 +227,6 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le ...(Option.isSome(flags.dbUrl) ? ["db-url"] : []), ], ); - yield* ensureMutuallyExclusive( - ["linked", "project-id", "swift-access-control"], - [ - ...(flags.linked ? ["linked"] : []), - ...(Option.isSome(flags.projectId) ? ["project-id"] : []), - ...(hasExplicitLongFlag(rawArgs, "swift-access-control") ? ["swift-access-control"] : []), - ], - ); - yield* ensureMutuallyExclusive( - ["linked", "project-id", "postgrest-v9-compat"], - [ - ...(flags.linked ? ["linked"] : []), - ...(Option.isSome(flags.projectId) ? ["project-id"] : []), - ...(flags.postgrestV9Compat ? ["postgrest-v9-compat"] : []), - ], - ); - yield* ensureMutuallyExclusive( - ["linked", "project-id", "query-timeout"], - [ - ...(flags.linked ? ["linked"] : []), - ...(Option.isSome(flags.projectId) ? ["project-id"] : []), - ...(hasExplicitLongFlag(rawArgs, "query-timeout") ? ["query-timeout"] : []), - ], - ); - - if (flags.postgrestV9Compat && Option.isNone(flags.dbUrl)) { - return yield* Effect.fail(new Error("--postgrest-v9-compat must used together with --db-url")); - } const legacyLang = findLegacyPositionalLanguage(rawArgs); if ( Option.isSome(legacyLang) && @@ -226,20 +242,88 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le const queryTimeoutSeconds = yield* parseQueryTimeoutSeconds(flags.queryTimeout); const lang = flags.lang; const swiftAccessControl = flags.swiftAccessControl; + const usesPgMeta = flags.local || Option.isSome(flags.dbUrl) || flags.lang !== "typescript"; - const loadConfig = () => loadProjectConfig(cliConfig.workdir); + if (hasExplicitLongFlag(rawArgs, "swift-access-control") && lang !== "swift") { + return yield* Effect.fail( + new Error("--swift-access-control can only be used with --lang swift"), + ); + } + if (flags.postgrestV9Compat && !usesPgMeta) { + return yield* Effect.fail( + new Error("--postgrest-v9-compat can only be used with pg-meta type generation"), + ); + } + if (hasExplicitLongFlag(rawArgs, "query-timeout") && !usesPgMeta) { + if (flags.linked || Option.isSome(flags.projectId)) { + return yield* Effect.fail( + new Error("--query-timeout can only be used with pg-meta type generation"), + ); + } + yield* output.raw( + "Warning: --query-timeout is ignored for remote TypeScript type generation.\n", + "stderr", + ); + } - const runProjectTypes = (projectRef: string, includedSchemas: ReadonlyArray) => + const loadConfig = () => loadProjectConfig(cliConfig.workdir); + const loadConfigForRef = (projectRef: string) => + loadProjectConfig(cliConfig.workdir, { projectRef }); + + const schemasFromConfig = (apiSchemas: ReadonlyArray | undefined) => + defaultSchemas(apiSchemas); + + const runProjectTypes = ( + projectRef: string, + includedSchemas: ReadonlyArray, + // True for an explicit `--project-id ` (an ad-hoc remote project that may + // differ from the current workdir); false for `--linked` / the linked fallback. + adHocProjectRef: boolean, + ) => Effect.gen(function* () { + const api = yield* platformApi.make; + if (lang !== "typescript") { - return yield* Effect.fail( - new Error( - `Unable to generate ${lang} types for selected project. Try using --db-url flag instead.`, + const projectResult = yield* api.v1.getProject({ ref: projectRef }).pipe( + Effect.catch(mapProjectDatabaseHostError), + Effect.as("project" as const), + Effect.catch((cause) => + isProjectNotFound(cause) + ? runPreviewBranchTypes(projectRef, includedSchemas).pipe( + Effect.as("branch" as const), + ) + : Effect.fail(cause), ), ); + if (projectResult === "branch") return; + + const resolveFlags: LegacyDbConfigFlags = { + dbUrl: Option.none(), + connType: "linked", + dnsResolver, + linkedProjectRef: Option.some(projectRef), + adHocProjectRef, + }; + const resolved = yield* dbConfig.resolve(resolveFlags); + const conn = resolved.conn; + yield* runPgMeta({ + url: legacyToPostgresURL(conn), + host: conn.host, + port: conn.port, + probeHost: conn.host, + probePort: conn.port, + networkMode: "host", + includedSchemas: includedSchemas.join(","), + postgrestV9Compat: flags.postgrestV9Compat, + poolerFallback: { + directHost: conn.host, + eligible: !resolved.isLocal && legacyIsDirectDbHost(conn.host, cliConfig.projectHost), + resolve: dbConfig.resolvePoolerFallback(resolveFlags), + }, + }); + return; } - const api = yield* platformApi.make; const response = yield* api.v1 .generateTypescriptTypes({ ref: projectRef, @@ -250,6 +334,58 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le yield* output.raw(response.types); }).pipe(Effect.ensuring(linkedProjectCache.cache(projectRef))); + const runPreviewBranchTypes = (branchRef: string, includedSchemas: ReadonlyArray) => + Effect.gen(function* () { + const api = yield* platformApi.make; + const branch = yield* api.v1 + .getABranchConfig({ branch_id_or_ref: branchRef }) + .pipe(Effect.catch(mapBranchDatabaseConfigError)); + + if (branch.db_user === undefined || branch.db_pass === undefined) { + return yield* Effect.fail(new Error("Preview branch database credentials are unavailable")); + } + const branchUser = branch.db_user; + const branchPassword = branch.db_pass; + + const poolerFallback = api.v1.getPoolerConfig({ ref: branch.ref }).pipe( + Effect.map((configs) => { + const primary = configs.find((config) => config.database_type === "PRIMARY"); + if (primary === undefined) return Option.none(); + const parsed = legacyPoolerConfigFromConnectionString( + branch.ref, + primary.connection_string, + cliConfig.poolerHost, + ); + return parsed._tag === "ok" + ? Option.some({ ...parsed.conn, password: branchPassword }) + : Option.none(); + }), + Effect.orElseSucceed(() => Option.none()), + ); + + yield* runPgMeta({ + url: legacyToPostgresURL({ + host: branch.db_host, + port: branch.db_port, + user: branchUser, + password: branchPassword, + database: "postgres", + }), + host: branch.db_host, + port: branch.db_port, + probeHost: branch.db_host, + probePort: branch.db_port, + networkMode: "host", + includedSchemas: includedSchemas.join(","), + postgrestV9Compat: flags.postgrestV9Compat, + poolerFallback: { + directHost: branch.db_host, + eligible: legacyIsDirectDbHost(branch.db_host, cliConfig.projectHost), + resolve: poolerFallback, + }, + }); + }); + const runPgMeta = (input: { readonly url: string; readonly host: string; @@ -260,71 +396,114 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le readonly includedSchemas: string; readonly postgrestV9Compat: boolean; readonly pgmetaVersionOverride?: string; + readonly poolerFallback?: { + readonly directHost: string; + readonly eligible: boolean; + readonly resolve: Effect.Effect, unknown>; + }; }) => Effect.scoped( Effect.gen(function* () { - yield* output.raw(`Connecting to ${input.host} ${input.port}\n`, "stderr"); - - // Mirrors Go's container.Config.Env ([]string of "KEY=VALUE"). We pass each - // entry as a `--env KEY=VALUE` argument rather than a `--env-file`: env-files - // split on newlines, so they cannot carry the multi-line PEM CA bundle, and a - // value containing a newline could inject an extra variable. Passing argv - // elements keeps each entry as exactly one variable regardless of its contents. - const env = [ - `PG_META_DB_URL=${input.url}`, - `PG_CONN_TIMEOUT_SECS=${queryTimeoutSeconds}`, - `PG_QUERY_TIMEOUT_SECS=${queryTimeoutSeconds}`, - `PG_META_GENERATE_TYPES=${lang}`, - `PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=${input.includedSchemas}`, - `PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL=${swiftAccessControl}`, - `PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS=${String(!input.postgrestV9Compat)}`, - ]; - - // Go's isRequireSSL emits this warning to stderr when the probe runs with - // certificate verification disabled. Our wire-level SSLRequest probe never - // verifies certificates, so honour the same env var for stderr parity. - if (process.env["SUPABASE_CA_SKIP_VERIFY"] === "true") { - yield* output.raw( - "WARNING: TLS certificate verification disabled for SSL probe (SUPABASE_CA_SKIP_VERIFY=true)\n", - "stderr", - ); - } - - const useTls = yield* probeTlsSupport(input.probeHost, input.probePort); - if (useTls && !debug) { - env.push(`PG_META_DB_SSL_ROOT_CERT=${legacyRootCaBundle()}`); - } - - // Go's DockerStart applies `--network-id` over any base network mode (even the - // "host" mode used for --db-url), so honour the override here too. - const networkMode = Option.isSome(networkId) ? networkId.value : input.networkMode; - const args = [ - "run", - "--rm", - "--network", - networkMode, - ...env.flatMap((entry) => ["--env", entry]), - resolvePgmetaImage(input.pgmetaVersionOverride), - "node", - "dist/server/server.js", - ]; - const child = yield* spawnContainerCli(spawner, args, { - stdin: "ignore", - stdout: "pipe", - stderr: "pipe", - }); - - const [exitCode] = yield* Effect.all( - [ - child.exitCode.pipe(Effect.map(Number)), - forwardByteStream(child.stdout, (text) => output.raw(text, "stdout")), - forwardByteStream(child.stderr, (text) => output.raw(text, "stderr")), - ], - { concurrency: "unbounded" }, - ); - - if (exitCode !== 0) { - return yield* Effect.fail(new Error(`error running container: exit ${exitCode}`)); + const buildRun = (target: { + readonly url: string; + readonly host: string; + readonly port: number; + readonly probeHost: string; + readonly probePort: number; + }) => + Effect.gen(function* () { + yield* output.raw(`Connecting to ${target.host} ${target.port}\n`, "stderr"); + + // Mirrors Go's container.Config.Env ([]string of "KEY=VALUE"). We pass each + // entry as a `--env KEY=VALUE` argument rather than a `--env-file`: env-files + // split on newlines, so they cannot carry the multi-line PEM CA bundle, and a + // value containing a newline could inject an extra variable. Passing argv + // elements keeps each entry as exactly one variable regardless of its contents. + const env = [ + `PG_META_DB_URL=${target.url}`, + `PG_CONN_TIMEOUT_SECS=${queryTimeoutSeconds}`, + `PG_QUERY_TIMEOUT_SECS=${queryTimeoutSeconds}`, + `PG_META_GENERATE_TYPES=${lang}`, + `PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=${input.includedSchemas}`, + `PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL=${swiftAccessControl}`, + `PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS=${String(!input.postgrestV9Compat)}`, + ]; + + // Go's isRequireSSL emits this warning to stderr when the probe runs with + // certificate verification disabled. Our wire-level SSLRequest probe never + // verifies certificates, so honour the same env var for stderr parity. + if (process.env["SUPABASE_CA_SKIP_VERIFY"] === "true") { + yield* output.raw( + "WARNING: TLS certificate verification disabled for SSL probe (SUPABASE_CA_SKIP_VERIFY=true)\n", + "stderr", + ); + } + + const useTls = yield* sslProbe.requireSslForHost(target.probeHost, target.probePort); + if (useTls && !debug) { + env.push(`PG_META_DB_SSL_ROOT_CERT=${legacyRootCaBundle()}`); + } + + // Go's DockerStart applies `--network-id` over any base network mode (even the + // "host" mode used for --db-url), so honour the override here too. + const networkMode = Option.isSome(networkId) ? networkId.value : input.networkMode; + const args = [ + "run", + "--rm", + "--network", + networkMode, + ...env.flatMap((entry) => ["--env", entry]), + resolvePgmetaImage(input.pgmetaVersionOverride), + "node", + "dist/server/server.js", + ]; + const child = yield* spawnContainerCli(spawner, args, { + stdin: "ignore", + stdout: "pipe", + stderr: "pipe", + }); + + let stderrText = ""; + const [exitCode] = yield* Effect.all( + [ + child.exitCode.pipe(Effect.map(Number)), + forwardByteStream(child.stdout, (text) => output.raw(text, "stdout")), + forwardByteStream(child.stderr, (text) => + Effect.sync(() => { + stderrText += text; + }).pipe(Effect.andThen(output.raw(text, "stderr"))), + ), + ], + { concurrency: "unbounded" }, + ); + return { exitCode, stderrText }; + }); + + const runTarget = (conn: LegacyPgConnInput) => + buildRun({ + url: legacyToPostgresURL(conn), + host: conn.host, + port: conn.port, + probeHost: conn.host, + probePort: conn.port, + }); + + const result = + input.poolerFallback === undefined + ? yield* buildRun(input) + : yield* legacyRunWithPoolerFallback({ + run: buildRun(input), + retry: runTarget, + directHost: input.poolerFallback.directHost, + eligible: input.poolerFallback.eligible, + resolveFallback: input.poolerFallback.resolve, + classifyError: legacyIsIPv6ConnectivityErrorCause, + classifyResult: (result) => + result.exitCode !== 0 && legacyIsIPv6ConnectivityError(result.stderrText), + }); + + if (result.exitCode !== 0) { + return yield* Effect.fail(new Error(`error running container: exit ${result.exitCode}`)); } }), ); @@ -436,21 +615,23 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le } if (flags.linked) { - const loaded = yield* loadConfig(); const ref = yield* projectRef.resolve(Option.none()); + const loaded = schemas.length > 0 ? null : yield* loadConfigForRef(ref); yield* runProjectTypes( ref, - schemas.length > 0 ? schemas : defaultSchemas(loaded?.config.api.schemas), + schemas.length > 0 ? schemas : schemasFromConfig(loaded?.config.api.schemas), + false, ); return; } if (Option.isSome(flags.projectId)) { - const loaded = yield* loadConfig(); const ref = yield* projectRef.resolve(flags.projectId); + const loaded = schemas.length > 0 ? null : yield* loadConfigForRef(ref); yield* runProjectTypes( ref, - schemas.length > 0 ? schemas : defaultSchemas(loaded?.config.api.schemas), + schemas.length > 0 ? schemas : schemasFromConfig(loaded?.config.api.schemas), + true, ); return; } @@ -468,10 +649,11 @@ export const legacyGenTypes = Effect.fn("legacy.gen.types")(function* (flags: Le return Effect.fail(cause); }), ); - const loaded = yield* loadConfig(); + const loaded = schemas.length > 0 ? null : yield* loadConfigForRef(resolvedRef); yield* runProjectTypes( resolvedRef, - schemas.length > 0 ? schemas : defaultSchemas(loaded?.config.api.schemas), + schemas.length > 0 ? schemas : schemasFromConfig(loaded?.config.api.schemas), + false, ); }).pipe(Effect.ensuring(telemetryState.flush)); }); diff --git a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts index a66868941f..5407d1bd2d 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.integration.test.ts @@ -1,15 +1,25 @@ -import { existsSync, mkdtempSync, mkdirSync, writeFileSync } from "node:fs"; +import { existsSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { createServer } from "node:net"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "@effect/vitest"; import { BunServices } from "@effect/platform-bun"; +import type { + V1CreateLoginRoleOutput, + V1GetABranchConfigOutput, + V1GetPoolerConfigOutput, + V1GetProjectOutput, +} from "@supabase/api/effect"; import { ChildProcessSpawner } from "effect/unstable/process"; import { CliOutput, Command } from "effect/unstable/cli"; +import * as HttpClientError from "effect/unstable/http/HttpClientError"; +import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"; +import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; import { Deferred, Effect, Exit, Layer, Option, PlatformError, Sink, Stdio, Stream } from "effect"; import { LEGACY_GLOBAL_FLAGS, LegacyDebugFlag, + LegacyDnsResolverFlag, LegacyNetworkIdFlag, LegacyOutputFlag, } from "../../../../shared/legacy/global-flags.ts"; @@ -36,6 +46,19 @@ import { textCliOutputFormatter } from "../../../../shared/output/text-formatter import { processControlLayer } from "../../../../shared/runtime/process-control.layer.ts"; import { TelemetryRuntime } from "../../../../shared/telemetry/runtime.service.ts"; import { makeTelemetryIdentity } from "../../../../shared/telemetry/identity.ts"; +import type { LegacyPgConnInput } from "../../../shared/legacy-db-connection.service.ts"; +import type { LegacyDbConfigError } from "../../../shared/legacy-db-config.service.ts"; +import { LegacyDbConfigResolver } from "../../../shared/legacy-db-config.service.ts"; +import { LegacyDbConfigLoadError } from "../../../shared/legacy-db-config.errors.ts"; +import { + LegacyPgDeltaSslProbe, + LegacyPgDeltaSslProbeError, +} from "../../../shared/legacy-pgdelta-ssl-probe.service.ts"; +import { legacyPgDeltaSslProbeLayer } from "../../../shared/legacy-pgdelta-ssl-probe.layer.ts"; +import type { + LegacyDbConfigFlags, + LegacyResolvedDbConfig, +} from "../../../shared/legacy-db-config.types.ts"; import { legacyGenCommand } from "../gen.command.ts"; import type { LegacyGenTypesFlags } from "./types.command.ts"; import { legacyGenTypes } from "./types.handler.ts"; @@ -112,6 +135,71 @@ function defaultFlags(overrides: Partial = {}): LegacyGenTy }; } +function statusApiError(status: number, body: string) { + const request = HttpClientRequest.get("https://api.supabase.test/v1/projects/ref"); + const response = HttpClientResponse.fromWeb( + request, + new Response(body, { + status, + headers: { "content-type": "application/json" }, + }), + ); + return new HttpClientError.HttpClientError({ + reason: new HttpClientError.StatusCodeError({ request, response }), + }); +} + +function remoteResolvedConfig( + conn: LegacyPgConnInput, + ref = LEGACY_VALID_REF, +): LegacyResolvedDbConfig { + return { conn, isLocal: false, ref: Option.some(ref) }; +} + +function mockDbConfigResolver( + opts: { + readonly resolve?: ( + flags: LegacyDbConfigFlags, + ) => Effect.Effect; + readonly poolerFallback?: Option.Option; + readonly poolerFallbackFails?: boolean; + } = {}, +) { + const resolves: Array = []; + const poolerFallbacks: Array = []; + const layer = Layer.succeed(LegacyDbConfigResolver, { + resolve: (flags) => + Effect.gen(function* () { + resolves.push(flags); + return yield* ( + opts.resolve?.(flags) ?? + Effect.succeed( + remoteResolvedConfig({ + host: "127.0.0.1", + port: 5432, + user: "postgres", + password: "postgres", + database: "postgres", + }), + ) + ); + }), + resolvePoolerFallback: (flags) => + opts.poolerFallbackFails === true + ? Effect.fail(new LegacyDbConfigLoadError({ message: "pooler fallback failed" })) + : Effect.sync(() => { + poolerFallbacks.push(flags); + return opts.poolerFallback ?? Option.none(); + }), + }); + return { layer, resolves, poolerFallbacks }; +} + +type BranchConfig = typeof V1GetABranchConfigOutput.Type; +type LoginRole = typeof V1CreateLoginRoleOutput.Type; +type PoolerConfig = typeof V1GetPoolerConfigOutput.Type; +type Project = typeof V1GetProjectOutput.Type; + function setup( opts: { readonly workdir?: string; @@ -135,6 +223,23 @@ function setup( readonly ref: string; readonly included_schemas?: string; }) => Effect.Effect<{ readonly types: string }, unknown>; + readonly getABranchConfig?: (input: { + readonly branch_id_or_ref: string; + }) => Effect.Effect; + readonly getPoolerConfig?: (input: { + readonly ref: string; + }) => Effect.Effect; + readonly getProject?: (input: { readonly ref: string }) => Effect.Effect; + readonly createLoginRole?: (input: { + readonly ref: string; + readonly read_only: boolean; + }) => Effect.Effect; + readonly dbConfigResolve?: ( + flags: LegacyDbConfigFlags, + ) => Effect.Effect; + readonly poolerFallback?: Option.Option; + readonly poolerFallbackFails?: boolean; + readonly sslProbeLayer?: Layer.Layer; } = {}, ) { const workdir = opts.workdir ?? mkdtempSync(join(tmpdir(), "supabase-gen-types-")); @@ -147,6 +252,11 @@ function setup( }); const telemetry = mockLegacyTelemetryStateTracked(); const linkedProjectCache = mockLegacyLinkedProjectCacheTracked(); + const dbConfig = mockDbConfigResolver({ + resolve: opts.dbConfigResolve, + poolerFallback: opts.poolerFallback, + poolerFallbackFails: opts.poolerFallbackFails, + }); const processControl = mockProcessControl(); const child = mockChildProcessSpawner({ stdout: [...(opts.childStdout ?? [])], @@ -156,6 +266,67 @@ function setup( }); const api = mockLegacyPlatformApiService({ v1: { + getABranchConfig: + opts.getABranchConfig ?? + (({ branch_id_or_ref }) => + Effect.succeed({ + ref: branch_id_or_ref, + postgres_version: "15.1", + postgres_engine: "15", + release_channel: "ga", + status: "ACTIVE_HEALTHY", + db_host: "127.0.0.1", + db_port: 5432, + db_user: "postgres", + db_pass: "postgres", + jwt_secret: "secret", + })), + getProject: + opts.getProject ?? + (({ ref }) => + Effect.succeed({ + id: ref, + ref, + organization_id: "org-id", + organization_slug: "org", + name: "demo", + region: "us-east-1", + created_at: "2025-01-01T00:00:00Z", + status: "ACTIVE_HEALTHY", + database: { + host: `db.${ref}.supabase.co`, + version: "15.1", + postgres_engine: "15", + release_channel: "ga", + }, + })), + getPoolerConfig: + opts.getPoolerConfig ?? + (() => + Effect.succeed([ + { + identifier: "primary", + database_type: "PRIMARY", + is_using_scram_auth: true, + db_user: "postgres", + db_host: "db.example", + db_port: 5432, + db_name: "postgres", + connection_string: "postgres://postgres:[YOUR-PASSWORD]@127.0.0.1:6543/postgres", + connectionString: "postgres://postgres:[YOUR-PASSWORD]@127.0.0.1:6543/postgres", + default_pool_size: null, + max_client_conn: null, + pool_mode: "transaction", + }, + ])), + createLoginRole: + opts.createLoginRole ?? + (() => + Effect.succeed({ + role: "postgres", + password: "postgres", + ttl_seconds: 3600, + })), generateTypescriptTypes: opts.generateTypescriptTypes ?? (({ included_schemas }) => @@ -184,10 +355,16 @@ function setup( Stdio.layerTest({ args: Effect.succeed(opts.args ?? ["gen", "types"]) }), Layer.succeed(LegacyOutputFlag, opts.goOutput ?? Option.none()), Layer.succeed(LegacyDebugFlag, opts.debug ?? false), + Layer.succeed(LegacyDnsResolverFlag, "native" as const), Layer.succeed(LegacyNetworkIdFlag, opts.networkId ?? Option.none()), + opts.sslProbeLayer ?? + legacyPgDeltaSslProbeLayer.pipe( + Layer.provide(Layer.succeed(LegacyDebugFlag, opts.debug ?? false)), + ), Layer.succeed(LegacyPlatformApiFactory, { make: LegacyPlatformApi.pipe(Effect.provide(api.layer)), }), + dbConfig.layer, ); return { @@ -195,6 +372,7 @@ function setup( out, telemetry, linkedProjectCache, + dbConfig, processControl, child, api, @@ -338,7 +516,10 @@ function mockDockerMissingChildProcessSpawner( async function withSslProbeServer( run: (port: number) => Promise, response: "N" | "S" = "N", + options: { readonly host?: string; readonly port?: number } = {}, ): Promise { + const host = options.host ?? "127.0.0.1"; + const port = options.port ?? 0; const server = createServer((socket) => { socket.once("data", () => { socket.write(Buffer.from(response)); @@ -348,7 +529,7 @@ async function withSslProbeServer( await new Promise((resolve, reject) => { server.once("error", reject); - server.listen(0, "127.0.0.1", () => resolve()); + server.listen(port, host, () => resolve()); }); const address = server.address(); @@ -366,6 +547,15 @@ async function withSslProbeServer( } } +const nonTypescriptProjectRefScenarios = [ + { lang: "go", stdout: "type PublicMovies struct {}" }, + { lang: "swift", stdout: "struct PublicMovies: Codable {}" }, + { lang: "python", stdout: "class PublicMovies(BaseModel):" }, +] as const satisfies ReadonlyArray<{ + readonly lang: Exclude; + readonly stdout: string; +}>; + const legacyTestRoot = Command.make("supabase").pipe( Command.withGlobalFlags(LEGACY_GLOBAL_FLAGS), Command.withSubcommands([legacyGenCommand]), @@ -605,91 +795,1255 @@ describe("legacy gen types", () => { }); }); - it.live("rejects combining --linked with --swift-access-control", () => { + it.live("rejects --swift-access-control for non-Swift generation", () => { const { layer } = setup({ - args: ["gen", "types", "--linked", "--swift-access-control", "public"], + args: ["gen", "types", "--local", "--lang", "python", "--swift-access-control", "public"], }); return Effect.gen(function* () { const exit = yield* legacyGenTypes( - defaultFlags({ linked: true, swiftAccessControl: "public" }), + defaultFlags({ local: true, lang: "python", swiftAccessControl: "public" }), ).pipe(Effect.provide(layer), Effect.exit); expect(Exit.isFailure(exit)).toBe(true); if (Exit.isFailure(exit)) { expect(String(exit.cause)).toContain( - "if any flags in the group [linked project-id swift-access-control] are set none of the others can be; [linked swift-access-control] were all set", + "--swift-access-control can only be used with --lang swift", ); } }); }); - it.live("rejects combining --linked with --postgrest-v9-compat", () => { - const { layer } = setup({ args: ["gen", "types", "--linked", "--postgrest-v9-compat"] }); + it.live("rejects --postgrest-v9-compat for remote TypeScript generation", () => { + const { layer } = setup({ + args: ["gen", "types", "--project-id", LEGACY_VALID_REF, "--postgrest-v9-compat"], + }); return Effect.gen(function* () { const exit = yield* legacyGenTypes( - defaultFlags({ linked: true, postgrestV9Compat: true }), + defaultFlags({ projectId: Option.some(LEGACY_VALID_REF), postgrestV9Compat: true }), ).pipe(Effect.provide(layer), Effect.exit); expect(Exit.isFailure(exit)).toBe(true); if (Exit.isFailure(exit)) { expect(String(exit.cause)).toContain( - "if any flags in the group [linked project-id postgrest-v9-compat] are set none of the others can be; [linked postgrest-v9-compat] were all set", + "--postgrest-v9-compat can only be used with pg-meta type generation", ); } }); }); - it.live("rejects combining --linked with --query-timeout", () => { - const { layer } = setup({ args: ["gen", "types", "--linked", "--query-timeout", "20s"] }); + it.live("rejects --query-timeout for remote TypeScript generation", () => { + const { layer } = setup({ + args: ["gen", "types", "--project-id", LEGACY_VALID_REF, "--query-timeout", "20s"], + }); return Effect.gen(function* () { - const exit = yield* legacyGenTypes(defaultFlags({ linked: true, queryTimeout: "20s" })).pipe( - Effect.provide(layer), - Effect.exit, - ); + const exit = yield* legacyGenTypes( + defaultFlags({ projectId: Option.some(LEGACY_VALID_REF), queryTimeout: "20s" }), + ).pipe(Effect.provide(layer), Effect.exit); expect(Exit.isFailure(exit)).toBe(true); if (Exit.isFailure(exit)) { expect(String(exit.cause)).toContain( - "if any flags in the group [linked project-id query-timeout] are set none of the others can be; [linked query-timeout] were all set", + "--query-timeout can only be used with pg-meta type generation", ); } }); }); - it.live("requires --db-url when --postgrest-v9-compat is set", () => { - const { layer } = setup({ args: ["gen", "types", "--local", "--postgrest-v9-compat"] }); + it.live("rejects --query-timeout for explicit linked remote TypeScript generation", () => { + const { layer } = setup({ + args: ["gen", "types", "--linked", "--query-timeout", "20s"], + projectId: Option.some(LEGACY_VALID_REF), + }); return Effect.gen(function* () { - const exit = yield* legacyGenTypes( - defaultFlags({ local: true, postgrestV9Compat: true }), - ).pipe(Effect.provide(layer), Effect.exit); + const exit = yield* legacyGenTypes(defaultFlags({ linked: true, queryTimeout: "20s" })).pipe( + Effect.provide(layer), + Effect.exit, + ); expect(Exit.isFailure(exit)).toBe(true); if (Exit.isFailure(exit)) { expect(String(exit.cause)).toContain( - "--postgrest-v9-compat must used together with --db-url", + "--query-timeout can only be used with pg-meta type generation", ); } }); }); - it.live("rejects non-typescript project generation", () => { - const { layer } = setup({ args: ["gen", "types", "--lang", "go"] }); + it.live( + "warns and continues for implicit linked TypeScript generation with --query-timeout", + () => { + const { layer, out, api } = setup({ + args: ["gen", "types", "--query-timeout", "20s"], + projectId: Option.some(LEGACY_VALID_REF), + projectTypes: "ok", + }); + + return Effect.gen(function* () { + yield* legacyGenTypes(defaultFlags({ queryTimeout: "20s" })).pipe(Effect.provide(layer)); + + expect(out.stderrText).toContain( + "Warning: --query-timeout is ignored for remote TypeScript type generation.", + ); + expect(api.requests).toContainEqual({ + method: "generateTypescriptTypes", + input: { ref: LEGACY_VALID_REF, included_schemas: "public" }, + }); + }); + }, + ); + + it.live("allows --postgrest-v9-compat for local pg-meta generation", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const docker = captureDockerRun(); + const workdir = mkdtempSync(join(tmpdir(), "supabase-gen-types-local-v9-flag-")); + writeConfig( + workdir, + [ + 'project_id = "demo"', + "", + "[api]", + 'schemas = ["public"]', + "", + "[db]", + `port = ${port}`, + ].join("\n"), + ); + + const { layer } = setup({ + workdir, + args: ["gen", "types", "--local", "--postgrest-v9-compat"], + childStdout: ["generated"], + onSpawn: docker.onSpawn, + }); + + await Effect.runPromise( + legacyGenTypes(defaultFlags({ local: true, postgrestV9Compat: true })).pipe( + Effect.provide(layer), + ), + ); + + expect( + docker.env.has("PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS=false"), + ).toBe(true); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + for (const scenario of nonTypescriptProjectRefScenarios) { + it.live(`generates ${scenario.lang} types from a project ref through the DB resolver`, () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const docker = captureDockerRun(); + const { layer, out, child, api, linkedProjectCache, dbConfig } = setup({ + args: ["gen", "types", "--lang", scenario.lang, "--project-id", LEGACY_VALID_REF], + childStdout: [scenario.stdout], + dbConfigResolve: (input) => + Effect.succeed( + remoteResolvedConfig( + { + host: "127.0.0.1", + port, + user: `cli_login_${LEGACY_VALID_REF}`, + password: "temporary-password", + database: "postgres", + }, + (input.linkedProjectRef !== undefined + ? Option.getOrUndefined(input.linkedProjectRef) + : undefined) ?? LEGACY_VALID_REF, + ), + ), + getABranchConfig: ({ branch_id_or_ref }) => + Effect.fail(new Error(`unexpected preview branch lookup for ${branch_id_or_ref}`)), + getProject: ({ ref }) => + Effect.succeed({ + id: ref, + ref, + organization_id: "org-id", + organization_slug: "org", + name: "demo", + region: "us-east-1", + created_at: "2025-01-01T00:00:00Z", + status: "ACTIVE_HEALTHY", + database: { + host: `127.0.0.1:${port}`, + version: "15.1", + postgres_engine: "15", + release_channel: "ga", + }, + }), + createLoginRole: ({ ref }) => + Effect.fail(new Error(`unexpected login role creation for ${ref}`)), + onSpawn: docker.onSpawn, + }); + + await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: scenario.lang, + }), + ).pipe(Effect.provide(layer)), + ); + + expect(api.requests).toContainEqual({ + method: "getProject", + input: { ref: LEGACY_VALID_REF }, + }); + expect(api.requests).not.toContainEqual( + expect.objectContaining({ method: "createLoginRole" }), + ); + expect(api.requests).not.toContainEqual( + expect.objectContaining({ method: "getABranchConfig" }), + ); + expect(api.requests).not.toContainEqual( + expect.objectContaining({ method: "generateTypescriptTypes" }), + ); + expect(child.spawned[0]?.args).toContain("--network"); + expect(child.spawned[0]?.args).toContain("host"); + expect(out.stderrText).toContain(`Connecting to 127.0.0.1 ${port}`); + expect( + docker.env.has( + `PG_META_DB_URL=postgresql://cli_login_${LEGACY_VALID_REF}:temporary-password@127.0.0.1:${port}/postgres?connect_timeout=10`, + ), + ).toBe(true); + expect(dbConfig.resolves).toHaveLength(1); + expect(dbConfig.resolves[0]?.connType).toBe("linked"); + // --project-id is an ad-hoc remote ref: the resolver must not inherit + // the workdir's ambient password / saved pooler URL. + expect(dbConfig.resolves[0]?.adHocProjectRef).toBe(true); + const linkedProjectRef = dbConfig.resolves[0]?.linkedProjectRef; + expect( + linkedProjectRef !== undefined ? Option.getOrUndefined(linkedProjectRef) : undefined, + ).toBe(LEGACY_VALID_REF); + expect(docker.env.has(`PG_META_GENERATE_TYPES=${scenario.lang}`)).toBe(true); + expect(docker.env.has("PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=public")).toBe(true); + expect(out.stdoutText).toContain(scenario.stdout); + expect(linkedProjectCache.cached).toBe(true); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + } + + it.live("resolves the linked workdir DB without ad-hoc project-ref semantics", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const docker = captureDockerRun(); + const { layer, dbConfig } = setup({ + args: ["gen", "types", "--lang", "go", "--linked"], + projectId: Option.some(LEGACY_VALID_REF), + childStdout: ["type PublicMovies struct {}"], + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: "127.0.0.1", + port, + user: "postgres", + password: "workdir-password", + database: "postgres", + }), + ), + onSpawn: docker.onSpawn, + }); + + await Effect.runPromise( + legacyGenTypes(defaultFlags({ linked: true, lang: "go" })).pipe(Effect.provide(layer)), + ); + + expect(dbConfig.resolves).toHaveLength(1); + expect(dbConfig.resolves[0]?.connType).toBe("linked"); + // --linked is the workdir's own project: keep workdir-scoped credentials. + expect(dbConfig.resolves[0]?.adHocProjectRef).toBe(false); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("preserves resolver URL options for remote non-TypeScript typegen", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const docker = captureDockerRun(); + const { layer } = setup({ + args: ["gen", "types", "--lang", "go", "--project-id", LEGACY_VALID_REF], + childStdout: ["type PublicMovies struct {}"], + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: "127.0.0.1", + port, + user: `postgres.${LEGACY_VALID_REF}`, + password: "pooler-password", + database: "postgres", + options: `reference=${LEGACY_VALID_REF}`, + }), + ), + onSpawn: docker.onSpawn, + }); + + await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "go", + }), + ).pipe(Effect.provide(layer)), + ); + + expect( + docker.env.has( + `PG_META_DB_URL=postgresql://postgres.${LEGACY_VALID_REF}:pooler-password@127.0.0.1:${port}/postgres?connect_timeout=10&options=reference%3D${LEGACY_VALID_REF}`, + ), + ).toBe(true); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("retries remote pg-meta through the IPv4 pooler on a container IPv6 failure", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const child = mockSequentialChildProcessSpawner([ + { + exitCode: 1, + stderr: [ + 'could not translate host name "db.abcdefghijklmnopqrst.supabase.co" to address: No address associated with hostname', + ], + }, + { exitCode: 0, stdout: ["type RetriedViaPooler struct {}"] }, + ]); + const poolerConn: LegacyPgConnInput = { + host: "127.0.0.1", + port, + user: `postgres.${LEGACY_VALID_REF}`, + password: "pooler-password", + database: "postgres", + }; + const { layer, out, dbConfig } = setup({ + args: ["gen", "types", "--lang", "go", "--project-id", LEGACY_VALID_REF], + childLayer: child.layer, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: `db.${LEGACY_VALID_REF}.supabase.co`, + port, + user: "postgres", + password: "direct-password", + database: "postgres", + }), + ), + poolerFallback: Option.some(poolerConn), + }); + + await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "go", + }), + ).pipe(Effect.provide(layer)), + ); + + expect(out.stdoutText).toContain("type RetriedViaPooler struct {}"); + expect(out.stderrText).toContain("does not support IPv6"); + expect(out.stderrText).toContain("Retrying via the IPv4 connection pooler."); + expect(child.spawned).toHaveLength(2); + expect( + dockerEnv(child.spawned[0]?.args ?? []).has( + `PG_META_DB_URL=postgresql://postgres:direct-password@db.${LEGACY_VALID_REF}.supabase.co:${port}/postgres?connect_timeout=10`, + ), + ).toBe(true); + expect( + dockerEnv(child.spawned[1]?.args ?? []).has( + `PG_META_DB_URL=postgresql://postgres.${LEGACY_VALID_REF}:pooler-password@127.0.0.1:${port}/postgres?connect_timeout=10`, + ), + ).toBe(true); + expect(dbConfig.poolerFallbacks).toHaveLength(1); + expect(dbConfig.poolerFallbacks[0]?.connType).toBe("linked"); + expect(dbConfig.poolerFallbacks[0]?.adHocProjectRef).toBe(true); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("retries remote pg-meta through the IPv4 pooler on Node ENETUNREACH stderr", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const child = mockSequentialChildProcessSpawner([ + { + exitCode: 1, + stderr: ["connect ENETUNREACH 2600:1f18::1:5432 - Local (:::0)"], + }, + { exitCode: 0, stdout: ["type RetriedViaPooler struct {}"] }, + ]); + const poolerConn: LegacyPgConnInput = { + host: "127.0.0.1", + port, + user: `postgres.${LEGACY_VALID_REF}`, + password: "pooler-password", + database: "postgres", + }; + const { layer, out, dbConfig } = setup({ + args: ["gen", "types", "--lang", "go", "--project-id", LEGACY_VALID_REF], + childLayer: child.layer, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: `db.${LEGACY_VALID_REF}.supabase.co`, + port, + user: "postgres", + password: "direct-password", + database: "postgres", + }), + ), + poolerFallback: Option.some(poolerConn), + }); + + await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "go", + }), + ).pipe(Effect.provide(layer)), + ); + + expect(out.stdoutText).toContain("type RetriedViaPooler struct {}"); + expect(child.spawned).toHaveLength(2); + expect(dbConfig.poolerFallbacks).toHaveLength(1); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("does not retry remote pg-meta when the container failure is not IPv6", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const child = mockSequentialChildProcessSpawner([ + { exitCode: 1, stderr: ["permission denied for schema public"] }, + ]); + const { layer, dbConfig } = setup({ + args: ["gen", "types", "--lang", "go", "--project-id", LEGACY_VALID_REF], + childLayer: child.layer, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: `db.${LEGACY_VALID_REF}.supabase.co`, + port, + user: "postgres", + password: "direct-password", + database: "postgres", + }), + ), + poolerFallback: Option.some({ + host: "127.0.0.1", + port, + user: `postgres.${LEGACY_VALID_REF}`, + password: "pooler-password", + database: "postgres", + }), + }); + + const exit = await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "go", + }), + ).pipe(Effect.provide(layer), Effect.exit), + ); + + expect(Exit.isFailure(exit)).toBe(true); + expect(child.spawned).toHaveLength(1); + expect(dbConfig.poolerFallbacks).toHaveLength(0); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live( + "does not run pooler fallback a second time when the retry also exits with IPv6 stderr", + () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const child = mockSequentialChildProcessSpawner([ + { + exitCode: 1, + stderr: [ + `could not translate host name "db.${LEGACY_VALID_REF}.supabase.co" to address: No address associated with hostname`, + ], + }, + { + exitCode: 1, + stderr: [ + `could not translate host name "db.${LEGACY_VALID_REF}.supabase.co" to address: No address associated with hostname`, + ], + }, + ]); + const { layer, dbConfig } = setup({ + args: ["gen", "types", "--lang", "go", "--project-id", LEGACY_VALID_REF], + childLayer: child.layer, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: `db.${LEGACY_VALID_REF}.supabase.co`, + port, + user: "postgres", + password: "direct-password", + database: "postgres", + }), + ), + poolerFallback: Option.some({ + host: "127.0.0.1", + port, + user: `postgres.${LEGACY_VALID_REF}`, + password: "pooler-password", + database: "postgres", + }), + }); + + const exit = await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "go", + }), + ).pipe(Effect.provide(layer), Effect.exit), + ); + + expect(Exit.isFailure(exit)).toBe(true); + expect(child.spawned).toHaveLength(2); + expect(dbConfig.poolerFallbacks).toHaveLength(1); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live( + "does not retry remote pg-meta when the resolved connection is already a pooler host", + () => + Effect.tryPromise({ + try: () => + Effect.runPromise( + Effect.gen(function* () { + const child = mockSequentialChildProcessSpawner([ + { + exitCode: 1, + stderr: [ + `could not translate host name "db.${LEGACY_VALID_REF}.supabase.co" to address: No address associated with hostname`, + ], + }, + ]); + const { layer, out, dbConfig } = setup({ + args: ["gen", "types", "--lang", "go", "--project-id", LEGACY_VALID_REF], + childLayer: child.layer, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: `postgres.${LEGACY_VALID_REF}`, + password: "pooler-password", + database: "postgres", + }), + ), + poolerFallback: Option.some({ + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: `postgres.${LEGACY_VALID_REF}`, + password: "pooler-password", + database: "postgres", + }), + }); + + const exit = yield* legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "go", + }), + ).pipe(Effect.provide(layer), Effect.exit); + + expect(Exit.isFailure(exit)).toBe(true); + expect(child.spawned).toHaveLength(1); + expect(dbConfig.poolerFallbacks).toHaveLength(0); + expect(out.stderrText).not.toContain("Retrying via the IPv4 connection pooler."); + }), + ), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("retries remote pg-meta when the TLS probe fails with ENETUNREACH", () => + Effect.tryPromise({ + try: () => + Effect.runPromise( + Effect.gen(function* () { + let probeCalls = 0; + const child = mockSequentialChildProcessSpawner([ + { exitCode: 0, stdout: ["type RetriedAfterProbeFailure struct {}"] }, + ]); + const { layer, out, dbConfig } = setup({ + args: ["gen", "types", "--lang", "go", "--project-id", LEGACY_VALID_REF], + childLayer: child.layer, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => + Effect.gen(function* () { + probeCalls += 1; + if (probeCalls === 1) { + return yield* Effect.fail( + new LegacyPgDeltaSslProbeError({ + message: "network is unreachable", + cause: Object.assign(new Error(), { code: "ENETUNREACH" }), + }), + ); + } + return false; + }), + }), + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: `db.${LEGACY_VALID_REF}.supabase.co`, + port: 5432, + user: "postgres", + password: "direct-password", + database: "postgres", + }), + ), + poolerFallback: Option.some({ + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: `postgres.${LEGACY_VALID_REF}`, + password: "pooler-password", + database: "postgres", + }), + }); + + yield* legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "go", + }), + ).pipe(Effect.provide(layer)); + + expect(out.stdoutText).toContain("type RetriedAfterProbeFailure struct {}"); + expect(probeCalls).toBe(2); + expect(child.spawned).toHaveLength(1); + expect(dbConfig.poolerFallbacks).toHaveLength(1); + }), + ), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("does not retry remote pg-meta when the TLS probe fails with ECONNREFUSED", () => + Effect.tryPromise({ + try: () => + Effect.runPromise( + Effect.gen(function* () { + const child = mockSequentialChildProcessSpawner([ + { exitCode: 0, stdout: ["should not spawn"] }, + ]); + const { layer, dbConfig } = setup({ + args: ["gen", "types", "--lang", "go", "--project-id", LEGACY_VALID_REF], + childLayer: child.layer, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => + Effect.fail( + new LegacyPgDeltaSslProbeError({ + message: "connection refused", + cause: Object.assign(new Error(), { code: "ECONNREFUSED" }), + }), + ), + }), + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: `db.${LEGACY_VALID_REF}.supabase.co`, + port: 5432, + user: "postgres", + password: "direct-password", + database: "postgres", + }), + ), + poolerFallback: Option.some({ + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: `postgres.${LEGACY_VALID_REF}`, + password: "pooler-password", + database: "postgres", + }), + }); + + const exit = yield* legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "go", + }), + ).pipe(Effect.provide(layer), Effect.exit); + + expect(Exit.isFailure(exit)).toBe(true); + expect(child.spawned).toHaveLength(0); + expect(dbConfig.poolerFallbacks).toHaveLength(0); + }), + ), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("preserves the original remote pg-meta error when pooler fallback resolution fails", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const child = mockSequentialChildProcessSpawner([ + { + exitCode: 1, + stderr: [ + 'could not translate host name "db.abcdefghijklmnopqrst.supabase.co" to address: No address associated with hostname', + ], + }, + ]); + const { layer } = setup({ + args: ["gen", "types", "--lang", "go", "--project-id", LEGACY_VALID_REF], + childLayer: child.layer, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: `db.${LEGACY_VALID_REF}.supabase.co`, + port, + user: "postgres", + password: "direct-password", + database: "postgres", + }), + ), + poolerFallbackFails: true, + }); + + const exit = await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "go", + }), + ).pipe(Effect.provide(layer), Effect.exit), + ); + + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + expect(String(exit.cause)).toContain("error running container: exit 1"); + expect(String(exit.cause)).not.toContain("pooler fallback failed"); + } + expect(child.spawned).toHaveLength(1); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("uses remote config schemas for explicit project-ref pg-meta typegen", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const workdir = mkdtempSync(join(tmpdir(), "supabase-gen-types-remote-config-")); + writeConfig( + workdir, + [ + 'project_id = "base"', + "", + "[api]", + 'schemas = ["public"]', + "", + "[remotes.staging]", + `project_id = "${LEGACY_VALID_REF}"`, + "", + "[remotes.staging.api]", + 'schemas = ["private"]', + "", + ].join("\n"), + ); + const docker = captureDockerRun(); + const { layer } = setup({ + workdir, + args: ["gen", "types", "--lang", "go", "--project-id", LEGACY_VALID_REF], + childStdout: ["type PrivateMovies struct {}"], + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: "127.0.0.1", + port, + user: "postgres", + password: "direct-password", + database: "postgres", + }), + ), + onSpawn: docker.onSpawn, + }); + + try { + await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "go", + }), + ).pipe(Effect.provide(layer)), + ); + } finally { + rmSync(workdir, { recursive: true, force: true }); + } + + expect(docker.env.has("PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=public,private")).toBe( + true, + ); + expect(docker.env.has("PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=public")).toBe(false); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("uses remote config schemas for linked pg-meta typegen", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const workdir = mkdtempSync(join(tmpdir(), "supabase-gen-types-linked-config-")); + writeConfig( + workdir, + [ + 'project_id = "base"', + "", + "[api]", + 'schemas = ["public"]', + "", + "[remotes.staging]", + `project_id = "${LEGACY_VALID_REF}"`, + "", + "[remotes.staging.api]", + 'schemas = ["private"]', + "", + ].join("\n"), + ); + const docker = captureDockerRun(); + const { layer } = setup({ + workdir, + projectId: Option.some(LEGACY_VALID_REF), + args: ["gen", "types", "--lang", "go", "--linked"], + childStdout: ["type PrivateMovies struct {}"], + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: "127.0.0.1", + port, + user: "postgres", + password: "direct-password", + database: "postgres", + }), + ), + onSpawn: docker.onSpawn, + }); + + try { + await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + linked: true, + lang: "go", + }), + ).pipe(Effect.provide(layer)), + ); + } finally { + rmSync(workdir, { recursive: true, force: true }); + } + + expect(docker.env.has("PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=public,private")).toBe( + true, + ); + expect(docker.env.has("PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS=public")).toBe(false); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("allows pg-meta flags for remote non-TypeScript project refs", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const docker = captureDockerRun(); + const { layer } = setup({ + args: [ + "gen", + "types", + "--lang", + "swift", + "--project-id", + LEGACY_VALID_REF, + "--swift-access-control", + "public", + "--query-timeout", + "20s", + "--postgrest-v9-compat", + ], + childStdout: ["struct PublicMovies: Codable {}"], + dbConfigResolve: () => + Effect.succeed( + remoteResolvedConfig({ + host: "127.0.0.1", + port, + user: "postgres", + password: "postgres", + database: "postgres", + }), + ), + onSpawn: docker.onSpawn, + }); + + await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "swift", + swiftAccessControl: "public", + queryTimeout: "20s", + postgrestV9Compat: true, + }), + ).pipe(Effect.provide(layer)), + ); + + expect(docker.env.has("PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL=public")).toBe(true); + expect(docker.env.has("PG_QUERY_TIMEOUT_SECS=20")).toBe(true); + expect( + docker.env.has("PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS=false"), + ).toBe(true); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("falls back to preview branch config for non-TypeScript project refs", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const docker = captureDockerRun(); + const { layer, api, dbConfig } = setup({ + args: ["gen", "types", "--lang", "python", "--project-id", LEGACY_VALID_REF], + childStdout: ["class PublicMovies(BaseModel):"], + getProject: () => + Effect.fail(statusApiError(404, `{"message":"Preview branch not found"}`)), + getABranchConfig: ({ branch_id_or_ref }) => + Effect.succeed({ + ref: branch_id_or_ref, + postgres_version: "15.1", + postgres_engine: "15", + release_channel: "ga", + status: "ACTIVE_HEALTHY", + db_host: "127.0.0.1", + db_port: port, + db_user: "branch_user", + db_pass: "branch-password", + jwt_secret: "secret", + }), + createLoginRole: ({ ref }) => + Effect.fail(new Error(`unexpected login role creation for ${ref}`)), + onSpawn: docker.onSpawn, + }); + + await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "python", + }), + ).pipe(Effect.provide(layer)), + ); + + expect(api.requests).toContainEqual({ + method: "getProject", + input: { ref: LEGACY_VALID_REF }, + }); + expect(api.requests).toContainEqual({ + method: "getABranchConfig", + input: { branch_id_or_ref: LEGACY_VALID_REF }, + }); + expect(api.requests).not.toContainEqual( + expect.objectContaining({ method: "createLoginRole" }), + ); + expect(dbConfig.resolves).toHaveLength(0); + expect( + docker.env.has( + `PG_META_DB_URL=postgresql://branch_user:branch-password@127.0.0.1:${port}/postgres?connect_timeout=10`, + ), + ).toBe(true); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("retries preview branch pg-meta through the branch IPv4 pooler", () => + Effect.tryPromise({ + try: () => + Effect.runPromise( + Effect.gen(function* () { + const poolerHost = "aws-0-us-east-1.pooler.supabase.com"; + const child = mockSequentialChildProcessSpawner([ + { + exitCode: 1, + stderr: [ + `could not translate host name "db.${LEGACY_VALID_REF}.supabase.co" to address: No address associated with hostname`, + ], + }, + { exitCode: 0, stdout: ["class RetriedViaBranchPooler(BaseModel):"] }, + ]); + const { layer, api } = setup({ + args: ["gen", "types", "--lang", "python", "--project-id", LEGACY_VALID_REF], + childLayer: child.layer, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), + getProject: () => Effect.fail(statusApiError(404, `{"message":"Not found"}`)), + getABranchConfig: ({ branch_id_or_ref }) => + Effect.succeed({ + ref: branch_id_or_ref, + postgres_version: "15.1", + postgres_engine: "15", + release_channel: "ga", + status: "ACTIVE_HEALTHY", + db_host: `db.${branch_id_or_ref}.supabase.co`, + db_port: 5432, + db_user: "branch_user", + db_pass: "branch-password", + jwt_secret: "secret", + }), + getPoolerConfig: ({ ref }) => + Effect.succeed([ + { + identifier: "primary", + database_type: "PRIMARY", + is_using_scram_auth: true, + db_user: "postgres", + db_host: "db.example", + db_port: 5432, + db_name: "postgres", + connection_string: `postgres://postgres.${ref}:[YOUR-PASSWORD]@${poolerHost}:6543/postgres`, + connectionString: `postgres://postgres.${ref}:[YOUR-PASSWORD]@${poolerHost}:6543/postgres`, + default_pool_size: null, + max_client_conn: null, + pool_mode: "transaction", + }, + ]), + }); + + yield* legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "python", + }), + ).pipe(Effect.provide(layer)); + + expect(api.requests).toContainEqual({ + method: "getPoolerConfig", + input: { ref: LEGACY_VALID_REF }, + }); + expect(child.spawned).toHaveLength(2); + expect( + dockerEnv(child.spawned[1]?.args ?? []).has( + `PG_META_DB_URL=postgresql://postgres.${LEGACY_VALID_REF}:branch-password@${poolerHost}:5432/postgres?connect_timeout=10`, + ), + ).toBe(true); + }), + ), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("skips preview branch pooler fallback when the pooler URL fails validation", () => + Effect.tryPromise({ + try: () => + Effect.runPromise( + Effect.gen(function* () { + const child = mockSequentialChildProcessSpawner([ + { + exitCode: 1, + stderr: [ + `could not translate host name "db.${LEGACY_VALID_REF}.supabase.co" to address: No address associated with hostname`, + ], + }, + ]); + const { layer, api } = setup({ + args: ["gen", "types", "--lang", "python", "--project-id", LEGACY_VALID_REF], + childLayer: child.layer, + sslProbeLayer: Layer.succeed(LegacyPgDeltaSslProbe, { + requireSsl: () => Effect.succeed(false), + requireSslForHost: () => Effect.succeed(false), + }), + getProject: () => Effect.fail(statusApiError(404, `{"message":"Not found"}`)), + getABranchConfig: ({ branch_id_or_ref }) => + Effect.succeed({ + ref: branch_id_or_ref, + postgres_version: "15.1", + postgres_engine: "15", + release_channel: "ga", + status: "ACTIVE_HEALTHY", + db_host: `db.${branch_id_or_ref}.supabase.co`, + db_port: 5432, + db_user: "branch_user", + db_pass: "branch-password", + jwt_secret: "secret", + }), + getPoolerConfig: ({ ref }) => + Effect.succeed([ + { + identifier: "primary", + database_type: "PRIMARY", + is_using_scram_auth: true, + db_user: "postgres", + db_host: "db.example", + db_port: 5432, + db_name: "postgres", + connection_string: `postgres://postgres.${ref}:[YOUR-PASSWORD]@pooler.example.com:6543/postgres`, + connectionString: `postgres://postgres.${ref}:[YOUR-PASSWORD]@pooler.example.com:6543/postgres`, + default_pool_size: null, + max_client_conn: null, + pool_mode: "transaction", + }, + ]), + }); + + const exit = yield* legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "python", + }), + ).pipe(Effect.provide(layer), Effect.exit); + + expect(Exit.isFailure(exit)).toBe(true); + expect(api.requests).toContainEqual({ + method: "getPoolerConfig", + input: { ref: LEGACY_VALID_REF }, + }); + expect(child.spawned).toHaveLength(1); + }), + ), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("falls back to preview branch config for any project 404 body", () => + Effect.tryPromise({ + try: () => + withSslProbeServer(async (port) => { + const docker = captureDockerRun(); + const { layer, api, dbConfig } = setup({ + args: ["gen", "types", "--lang", "python", "--project-id", LEGACY_VALID_REF], + childStdout: ["class PublicMovies(BaseModel):"], + // The Management API's 404 wording is not guaranteed; a generic body + // must still route to the branch config endpoint. + getProject: () => Effect.fail(statusApiError(404, `{"message":"Not found"}`)), + getABranchConfig: ({ branch_id_or_ref }) => + Effect.succeed({ + ref: branch_id_or_ref, + postgres_version: "15.1", + postgres_engine: "15", + release_channel: "ga", + status: "ACTIVE_HEALTHY", + db_host: "127.0.0.1", + db_port: port, + db_user: "branch_user", + db_pass: "branch-password", + jwt_secret: "secret", + }), + onSpawn: docker.onSpawn, + }); + + await Effect.runPromise( + legacyGenTypes( + defaultFlags({ + projectId: Option.some(LEGACY_VALID_REF), + lang: "python", + }), + ).pipe(Effect.provide(layer)), + ); + + expect(api.requests).toContainEqual({ + method: "getABranchConfig", + input: { branch_id_or_ref: LEGACY_VALID_REF }, + }); + expect(dbConfig.resolves).toHaveLength(0); + expect( + docker.env.has( + `PG_META_DB_URL=postgresql://branch_user:branch-password@127.0.0.1:${port}/postgres?connect_timeout=10`, + ), + ).toBe(true); + }), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); + + it.live("fails clearly when preview branch config does not include DB credentials", () => { + const { layer } = setup({ + args: ["gen", "types", "--lang", "python", "--project-id", LEGACY_VALID_REF], + getProject: () => Effect.fail(statusApiError(404, `{"message":"Preview branch not found"}`)), + getABranchConfig: ({ branch_id_or_ref }) => + Effect.succeed({ + ref: branch_id_or_ref, + postgres_version: "15.1", + postgres_engine: "15", + release_channel: "ga", + status: "ACTIVE_HEALTHY", + db_host: "127.0.0.1", + db_port: 5432, + jwt_secret: "secret", + }), + }); return Effect.gen(function* () { const exit = yield* legacyGenTypes( defaultFlags({ projectId: Option.some(LEGACY_VALID_REF), - lang: "go", + lang: "python", }), ).pipe(Effect.provide(layer), Effect.exit); expect(Exit.isFailure(exit)).toBe(true); if (Exit.isFailure(exit)) { - expect(String(exit.cause)).toContain("Try using --db-url flag instead."); + expect(String(exit.cause)).toContain("Preview branch database credentials are unavailable"); } }); }); diff --git a/apps/cli/src/legacy/commands/gen/types/types.layers.ts b/apps/cli/src/legacy/commands/gen/types/types.layers.ts index 098328a0d1..1c2039b0f4 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.layers.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.layers.ts @@ -7,7 +7,12 @@ import { legacyCliConfigLayer } from "../../../config/legacy-cli-config.layer.ts import { LegacyCliConfig } from "../../../config/legacy-cli-config.service.ts"; import { legacyProjectRefLayer } from "../../../config/legacy-project-ref.layer.ts"; import { LegacyProjectRefResolver } from "../../../config/legacy-project-ref.service.ts"; +import { legacyDbConfigLayer } from "../../../shared/legacy-db-config.layer.ts"; +import { LegacyDbConfigResolver } from "../../../shared/legacy-db-config.service.ts"; +import { legacyDbConnectionLayer } from "../../../shared/legacy-db-connection.layer.ts"; import { legacyDebugLoggerLayer } from "../../../shared/legacy-debug-logger.layer.ts"; +import { legacyPgDeltaSslProbeLayer } from "../../../shared/legacy-pgdelta-ssl-probe.layer.ts"; +import { LegacyPgDeltaSslProbe } from "../../../shared/legacy-pgdelta-ssl-probe.service.ts"; import { LegacyIdentityStitch, legacyIdentityStitchLayer, @@ -43,8 +48,16 @@ export const legacyGenTypesRuntimeLayer = (() => { Layer.provide(legacyDebugLoggerLayer), Layer.provide(legacyIdentityStitchLayer), ); + const dbConfig = legacyDbConfigLayer.pipe( + Layer.provide(cliConfig), + Layer.provide(legacyDbConnectionLayer), + Layer.provide(legacyDebugLoggerLayer), + Layer.provide(legacyIdentityStitchLayer), + ); const built = Layer.mergeAll( + dbConfig, + legacyDbConnectionLayer, cliConfig, platformApiFactory, legacyProjectRefLayer.pipe(Layer.provide(platformApiFactory), Layer.provide(cliConfig)), @@ -54,6 +67,7 @@ export const legacyGenTypesRuntimeLayer = (() => { Layer.provide(httpClient), Layer.provide(legacyIdentityStitchLayer), ), + legacyPgDeltaSslProbeLayer, legacyTelemetryStateLayer, // The one per-command identity stitcher (Go's single root-context `sync.Once`), // exposed at top level so `withLegacyCommandInstrumentation` can read @@ -77,6 +91,8 @@ type LegacyGenTypesServices = | LegacyPlatformApiFactory | LegacyCliConfig | LegacyProjectRefResolver + | LegacyDbConfigResolver + | LegacyPgDeltaSslProbe | LegacyLinkedProjectCache | LegacyTelemetryState | LegacyIdentityStitch diff --git a/apps/cli/src/legacy/commands/gen/types/types.shared.ts b/apps/cli/src/legacy/commands/gen/types/types.shared.ts index c6a363b84b..2f55067e1f 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.shared.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.shared.ts @@ -1,4 +1,3 @@ -import { connect as connectSocket } from "node:net"; import { DEFAULT_VERSIONS, dockerImageForService } from "@supabase/stack/effect"; import { Effect } from "effect"; import { @@ -154,52 +153,3 @@ export function resolvePgmetaImage(versionOverride?: string) { export function legacyRootCaBundle() { return `${caStaging2021}${caProd2021}${caProd2025}`; } - -export function probeTlsSupport(host: string, port: number): Effect.Effect { - return Effect.tryPromise({ - try: () => - new Promise((resolve, reject) => { - let settled = false; - const socket = connectSocket({ host, port }); - - const finish = (result: boolean | Error) => { - if (settled) return; - settled = true; - socket.destroy(); - if (result instanceof Error) { - reject(result); - return; - } - resolve(result); - }; - - socket.setTimeout(5_000); - socket.once("connect", () => { - const packet = Buffer.alloc(8); - packet.writeInt32BE(8, 0); - packet.writeInt32BE(80877103, 4); - socket.write(packet); - }); - socket.once("data", (chunk) => { - const response = Number(chunk.at(0) ?? 0); - if (response === 0x53) { - finish(true); - return; - } - if (response === 0x4e) { - finish(false); - return; - } - finish(new Error(`unexpected SSL probe response: ${String.fromCharCode(response ?? 0)}`)); - }); - socket.once("timeout", () => finish(new Error("i/o timeout"))); - socket.once("error", (error) => finish(error)); - socket.once("close", () => { - if (!settled) { - finish(new Error("connection closed during SSL probe")); - } - }); - }), - catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), - }); -} diff --git a/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts b/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts index 9034c57309..115022e562 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts @@ -1,4 +1,3 @@ -import { createServer, type Server, type Socket } from "node:net"; import { describe, expect, it } from "@effect/vitest"; import { Effect, Exit } from "effect"; import { legacyGetHostname } from "../../../shared/legacy-hostname.ts"; @@ -12,7 +11,6 @@ import { localNetworkId, parseDatabaseUrl, parseQueryTimeoutSeconds, - probeTlsSupport, resolvePgmetaImage, } from "./types.shared.ts"; @@ -34,29 +32,6 @@ function withEnv(key: string, value: string | undefined, run: () => T): T { } } -async function withTcpServer( - handler: (socket: Socket) => void, - run: (port: number) => Promise, -): Promise { - const server: Server = createServer(handler); - await new Promise((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", () => resolve()); - }); - const address = server.address(); - if (address === null || typeof address === "string") { - server.close(); - throw new Error("failed to bind tcp server"); - } - try { - return await run(address.port); - } finally { - await new Promise((resolve, reject) => - server.close((error) => (error ? reject(error) : resolve())), - ); - } -} - describe("parseQueryTimeoutSeconds", () => { it.effect("parses compound Go durations", () => Effect.gen(function* () { @@ -239,65 +214,3 @@ describe("schema and id helpers", () => { expect(legacyRootCaBundle().length).toBeGreaterThan(0); }); }); - -describe("probeTlsSupport", () => { - it.effect("detects TLS support from an 'S' response", () => - Effect.gen(function* () { - const result = yield* Effect.tryPromise(() => - withTcpServer( - (socket) => - socket.once("data", () => { - socket.write(Buffer.from("S")); - socket.end(); - }), - (port) => Effect.runPromise(probeTlsSupport("127.0.0.1", port)), - ), - ); - expect(result).toBe(true); - }), - ); - - it.effect("detects a refused TLS connection from an 'N' response", () => - Effect.gen(function* () { - const result = yield* Effect.tryPromise(() => - withTcpServer( - (socket) => - socket.once("data", () => { - socket.write(Buffer.from("N")); - socket.end(); - }), - (port) => Effect.runPromise(probeTlsSupport("127.0.0.1", port)), - ), - ); - expect(result).toBe(false); - }), - ); - - it.effect("fails on an unexpected probe response", () => - Effect.gen(function* () { - const exit = yield* Effect.tryPromise(() => - withTcpServer( - (socket) => - socket.once("data", () => { - socket.write(Buffer.from("X")); - socket.end(); - }), - (port) => Effect.runPromise(probeTlsSupport("127.0.0.1", port)), - ), - ).pipe(Effect.exit); - expect(Exit.isFailure(exit)).toBe(true); - }), - ); - - it.effect("fails when the connection closes before responding", () => - Effect.gen(function* () { - const exit = yield* Effect.tryPromise(() => - withTcpServer( - (socket) => socket.destroy(), - (port) => Effect.runPromise(probeTlsSupport("127.0.0.1", port)), - ), - ).pipe(Effect.exit); - expect(Exit.isFailure(exit)).toBe(true); - }), - ); -}); diff --git a/apps/cli/src/legacy/commands/services/services.integration.test.ts b/apps/cli/src/legacy/commands/services/services.integration.test.ts index f3c8ce3e64..7e03294354 100644 --- a/apps/cli/src/legacy/commands/services/services.integration.test.ts +++ b/apps/cli/src/legacy/commands/services/services.integration.test.ts @@ -163,7 +163,7 @@ describe("legacy services", () => { CliOutput.layer(textCliOutputFormatter()), out.layer, analytics.layer, - processEnvLayer({ SUPABASE_HOME: workdir }), + processEnvLayer({ SUPABASE_HOME: workdir, SUPABASE_NO_KEYRING: "1" }), mockRuntimeInfo({ cwd: workdir, homeDir: workdir }), mockTty({ stdinIsTty: false, stdoutIsTty: false }), Stdio.layerTest({ args: Effect.succeed(args) }), diff --git a/apps/cli/src/legacy/shared/legacy-connect-errors.ts b/apps/cli/src/legacy/shared/legacy-connect-errors.ts index 9aa4675915..504deb471e 100644 --- a/apps/cli/src/legacy/shared/legacy-connect-errors.ts +++ b/apps/cli/src/legacy/shared/legacy-connect-errors.ts @@ -5,6 +5,8 @@ * warrants retrying through the IPv4 transaction pooler. */ +import { isIPv6 } from "node:net"; + import { legacyAqua } from "./legacy-colors.ts"; /** @@ -27,6 +29,7 @@ export function legacyIpv6Suggestion(): string { // Go's `ipv6LiteralPattern` (`connect.go:181`): an IPv6 address in brackets // (Go dial form) or parens (libpq form). Run against the original-case message. const IPV6_LITERAL_PATTERN = /(?:\[[0-9a-fA-F:]+\]|\([0-9a-fA-F:]+\))/; +const NODE_ENETUNREACH_PATTERN = /\benetunreach\s+([0-9a-fA-F:]+):\d+(?:\s|$)/i; /** * Port of Go's `isIPv6ConnectivityError` (`connect.go:189-208`). Lower-cases the @@ -40,8 +43,58 @@ export function legacyIsIPv6ConnectivityError(message: string): boolean { if (lower.includes("address family for hostname not supported")) return true; if (lower.includes("no address associated with hostname")) return true; if (lower.includes("network is unreachable")) return true; + const nodeEnetunreachMatch = NODE_ENETUNREACH_PATTERN.exec(message); + if (nodeEnetunreachMatch?.[1] !== undefined) return isIPv6(nodeEnetunreachMatch[1]); if (lower.includes("no route to host") || lower.includes("cannot assign requested address")) { return IPV6_LITERAL_PATTERN.test(message); } return false; } + +function hasStringCode(error: unknown): error is { + readonly code: string; + readonly address?: unknown; +} { + return ( + typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" + ); +} + +/** + * Classifies Node socket/getaddrinfo causes that carry errno-style `code` fields. + * `ENOTFOUND` is intentionally broader than Go's text classifier (it can include + * typo'd hosts); callers must combine this with a direct `db.` host gate. + */ +export function legacyIsIPv6ConnectivityErrorCause(error: unknown): boolean { + if (error instanceof AggregateError) { + return error.errors.some((cause) => legacyIsIPv6ConnectivityErrorCause(cause)); + } + if ( + typeof error === "object" && + error !== null && + "cause" in error && + error.cause !== undefined && + legacyIsIPv6ConnectivityErrorCause(error.cause) + ) { + return true; + } + + if (hasStringCode(error)) { + switch (error.code) { + case "ENETUNREACH": + case "ENOTFOUND": + return true; + case "EHOSTUNREACH": + case "EADDRNOTAVAIL": + return typeof error.address === "string" && isIPv6(error.address); + case "ECONNREFUSED": + case "ENOENT": + case "ETIMEDOUT": + return false; + default: + break; + } + } + + return legacyIsIPv6ConnectivityError(error instanceof Error ? error.message : String(error)); +} diff --git a/apps/cli/src/legacy/shared/legacy-connect-errors.unit.test.ts b/apps/cli/src/legacy/shared/legacy-connect-errors.unit.test.ts index b8edbdfe10..05ff60186c 100644 --- a/apps/cli/src/legacy/shared/legacy-connect-errors.unit.test.ts +++ b/apps/cli/src/legacy/shared/legacy-connect-errors.unit.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from "vitest"; -import { legacyIsIPv6ConnectivityError } from "./legacy-connect-errors.ts"; +import { + legacyIsIPv6ConnectivityError, + legacyIsIPv6ConnectivityErrorCause, +} from "./legacy-connect-errors.ts"; describe("legacyIsIPv6ConnectivityError", () => { it("classifies the getaddrinfo IPv6-only failures (case-insensitive)", () => { @@ -28,8 +31,85 @@ describe("legacyIsIPv6ConnectivityError", () => { expect(legacyIsIPv6ConnectivityError("cannot assign requested address")).toBe(false); }); + it("classifies Node ENETUNREACH stderr for IPv6 literals", () => { + expect( + legacyIsIPv6ConnectivityError("connect ENETUNREACH 2600:1f18::1:5432 - Local (:::0)"), + ).toBe(true); + expect(legacyIsIPv6ConnectivityError("connect ENETUNREACH 10.0.0.1:5432")).toBe(false); + }); + it("does not classify unrelated errors", () => { expect(legacyIsIPv6ConnectivityError("permission denied for schema public")).toBe(false); expect(legacyIsIPv6ConnectivityError("")).toBe(false); }); }); + +describe("legacyIsIPv6ConnectivityErrorCause", () => { + it("classifies Node getaddrinfo and network-unreachable errors", () => { + expect( + legacyIsIPv6ConnectivityErrorCause(Object.assign(new Error(), { code: "ENETUNREACH" })), + ).toBe(true); + expect( + legacyIsIPv6ConnectivityErrorCause(Object.assign(new Error(), { code: "ENOTFOUND" })), + ).toBe(true); + }); + + it("requires an IPv6 literal address for ambiguous Node dial errors", () => { + expect( + legacyIsIPv6ConnectivityErrorCause( + Object.assign(new Error(), { code: "EHOSTUNREACH", address: "2600:1f18::1" }), + ), + ).toBe(true); + expect( + legacyIsIPv6ConnectivityErrorCause( + Object.assign(new Error(), { code: "EADDRNOTAVAIL", address: "2a05:d014::1" }), + ), + ).toBe(true); + expect( + legacyIsIPv6ConnectivityErrorCause( + Object.assign(new Error(), { code: "EHOSTUNREACH", address: "10.0.0.1" }), + ), + ).toBe(false); + }); + + it("recurses through AggregateError causes", () => { + expect( + legacyIsIPv6ConnectivityErrorCause( + new AggregateError([ + Object.assign(new Error(), { code: "ECONNREFUSED" }), + Object.assign(new Error(), { code: "ENETUNREACH" }), + ]), + ), + ).toBe(true); + }); + + it("recurses through wrapped cause fields", () => { + expect( + legacyIsIPv6ConnectivityErrorCause( + Object.assign(new Error("probe failed"), { + cause: Object.assign(new Error(), { code: "ENETUNREACH" }), + }), + ), + ).toBe(true); + }); + + it("does not classify unrelated process and timeout failures", () => { + expect( + legacyIsIPv6ConnectivityErrorCause(Object.assign(new Error(), { code: "ECONNREFUSED" })), + ).toBe(false); + expect(legacyIsIPv6ConnectivityErrorCause(Object.assign(new Error(), { code: "ENOENT" }))).toBe( + false, + ); + expect( + legacyIsIPv6ConnectivityErrorCause(Object.assign(new Error(), { code: "ETIMEDOUT" })), + ).toBe(false); + }); + + it("falls back to the existing message classifier for wrapped libpq wording", () => { + expect( + legacyIsIPv6ConnectivityErrorCause( + new Error("could not translate host name: no address associated with hostname"), + ), + ).toBe(true); + }); +}); diff --git a/apps/cli/src/legacy/shared/legacy-db-config.integration.test.ts b/apps/cli/src/legacy/shared/legacy-db-config.integration.test.ts index b8c0c4d860..78549ac06f 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.integration.test.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.integration.test.ts @@ -12,7 +12,7 @@ import { mockTelemetryRuntime, mockTty, } from "../../../tests/helpers/mocks.ts"; -import { mockLegacyCliConfig } from "../../../tests/helpers/legacy-mocks.ts"; +import { LEGACY_VALID_TOKEN, mockLegacyCliConfig } from "../../../tests/helpers/legacy-mocks.ts"; import { LegacyDebugFlag, LegacyDnsResolverFlag, @@ -25,7 +25,11 @@ import { legacyIdentityStitchLayer } from "./legacy-identity-stitch.ts"; import { legacyDbConfigLayer } from "./legacy-db-config.layer.ts"; import { LegacyDbConfigResolver } from "./legacy-db-config.service.ts"; import type { LegacyDbConfigFlags } from "./legacy-db-config.types.ts"; -import { LegacyDbConnection } from "./legacy-db-connection.service.ts"; +import { + LegacyDbConnection, + type LegacyDbSession, + type LegacyPgConnInput, +} from "./legacy-db-connection.service.ts"; // `--local` / `--db-url` never touch the Management API stack, so the resolver // builds with simple ambient stubs. The `--linked` sub-flow (login-role, @@ -40,10 +44,22 @@ const mockDbConnection = Layer.succeed(LegacyDbConnection, { connect: () => Effect.die("unexpected connect() in --local/--db-url resolver test"), }); -function buildResolver(workdir: string) { +function buildResolver( + workdir: string, + opts: { + readonly projectHost?: string; + readonly poolerHost?: string; + readonly dbConnection?: Layer.Layer; + } = {}, +) { const deps = Layer.mergeAll( - mockLegacyCliConfig({ workdir, projectHost: "supabase.co", projectId: Option.none() }), - mockDbConnection, + mockLegacyCliConfig({ + workdir, + projectHost: opts.projectHost ?? "supabase.co", + poolerHost: opts.poolerHost, + projectId: Option.none(), + }), + opts.dbConnection ?? mockDbConnection, mockDebugLogger, mockOutput().layer, mockAnalytics().layer, @@ -76,11 +92,25 @@ function withWorkdir(toml?: string) { return dir; } -const resolve = (workdir: string, flags: LegacyDbConfigFlags) => +const resolve = ( + workdir: string, + flags: LegacyDbConfigFlags, + opts?: Parameters[1], +) => Effect.gen(function* () { const resolver = yield* LegacyDbConfigResolver; return yield* resolver.resolve(flags); - }).pipe(Effect.provide(buildResolver(workdir))); + }).pipe(Effect.provide(buildResolver(workdir, opts))); + +const resolvePoolerFallback = ( + workdir: string, + flags: LegacyDbConfigFlags, + opts?: Parameters[1], +) => + Effect.gen(function* () { + const resolver = yield* LegacyDbConfigResolver; + return yield* resolver.resolvePoolerFallback(flags); + }).pipe(Effect.provide(buildResolver(workdir, opts))); const localFlags: LegacyDbConfigFlags = { dbUrl: Option.none(), @@ -357,4 +387,425 @@ describe("legacyDbConfigResolver (linked config ordering)", () => { ), ); }); + + it.effect("ad-hoc project refs ignore the linked workdir password and saved pooler URL", () => { + const linkedRef = "abcdefghijklmnopqrst"; + const adHocRef = "qrstabcdefghijklmnop"; + const dir = withWorkdir( + [`project_id = "${linkedRef}"`, "[db]", "major_version = 15", ""].join("\n"), + ); + mkdirSync(join(dir, "supabase", ".temp"), { recursive: true }); + writeFileSync(join(dir, "supabase", ".temp", "project-ref"), linkedRef); + writeFileSync( + join(dir, "supabase", ".temp", "pooler-url"), + `postgres://postgres.${linkedRef}:saved-workdir-password@stale.pooler.supabase.com:6543/postgres`, + ); + + const previousAccessToken = process.env["SUPABASE_ACCESS_TOKEN"]; + const previousPassword = process.env["SUPABASE_DB_PASSWORD"]; + const previousFetch = globalThis.fetch; + const requests: Array<{ readonly method: string; readonly path: string }> = []; + const connections: Array<{ + readonly conn: LegacyPgConnInput; + readonly dnsResolver: "native" | "https"; + readonly isLocal: boolean; + }> = []; + const session: LegacyDbSession = { + exec: () => Effect.void, + query: () => Effect.succeed([]), + extensionExists: () => Effect.succeed(false), + copyToCsv: () => Effect.succeed(new Uint8Array()), + queryRaw: () => Effect.succeed({ fields: [], rows: [], commandTag: "SELECT 1" }), + }; + const dbConnection = Layer.succeed(LegacyDbConnection, { + connect: (conn, options) => + Effect.sync(() => { + connections.push({ conn, ...options }); + return session; + }), + }); + const fetchMock = Object.assign( + async (input: string | URL | Request, init?: RequestInit): Promise => { + const url = new URL(typeof input === "string" || input instanceof URL ? input : input.url); + const method = init?.method ?? (input instanceof Request ? input.method : "GET"); + requests.push({ method, path: url.pathname }); + + if ( + method === "GET" && + url.pathname === `/v1/projects/${adHocRef}/config/database/pooler` + ) { + return new Response( + JSON.stringify([ + { + identifier: "primary", + database_type: "PRIMARY", + is_using_scram_auth: true, + db_user: "postgres", + db_host: "db.example", + db_port: 5432, + db_name: "postgres", + connection_string: `postgres://postgres.${adHocRef}:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres`, + connectionString: `postgres://postgres.${adHocRef}:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres`, + default_pool_size: null, + max_client_conn: null, + pool_mode: "transaction", + }, + ]), + { status: 200, headers: { "content-type": "application/json" } }, + ); + } + + if (method === "POST" && url.pathname === `/v1/projects/${adHocRef}/cli/login-role`) { + return new Response( + JSON.stringify({ + role: "cli_login_role", + password: "temporary-role-password", + ttl_seconds: 3600, + }), + { status: 201, headers: { "content-type": "application/json" } }, + ); + } + + return new Response(JSON.stringify({ message: "unexpected request" }), { + status: 404, + headers: { "content-type": "application/json" }, + }); + }, + { preconnect: previousFetch.preconnect }, + ); + + process.env["SUPABASE_ACCESS_TOKEN"] = LEGACY_VALID_TOKEN; + process.env["SUPABASE_DB_PASSWORD"] = "ambient-linked-password"; + globalThis.fetch = fetchMock; + + return resolve( + dir, + { + ...linkedFlags, + linkedProjectRef: Option.some(adHocRef), + adHocProjectRef: true, + }, + { projectHost: "invalid", dbConnection }, + ).pipe( + Effect.tap((r) => + Effect.sync(() => { + expect(r.conn).toEqual({ + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: `cli_login_role.${adHocRef}`, + password: "temporary-role-password", + database: "postgres", + }); + expect(r.ref).toEqual(Option.some(adHocRef)); + expect(requests).toEqual([ + { + method: "GET", + path: `/v1/projects/${adHocRef}/config/database/pooler`, + }, + { + method: "POST", + path: `/v1/projects/${adHocRef}/cli/login-role`, + }, + ]); + expect(connections).toEqual([ + { + conn: { + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: `cli_login_role.${adHocRef}`, + password: "temporary-role-password", + database: "postgres", + }, + isLocal: false, + dnsResolver: "native", + }, + ]); + }), + ), + Effect.ensuring( + Effect.sync(() => { + globalThis.fetch = previousFetch; + if (previousAccessToken === undefined) delete process.env["SUPABASE_ACCESS_TOKEN"]; + else process.env["SUPABASE_ACCESS_TOKEN"] = previousAccessToken; + if (previousPassword === undefined) delete process.env["SUPABASE_DB_PASSWORD"]; + else process.env["SUPABASE_DB_PASSWORD"] = previousPassword; + rmSync(dir, { recursive: true, force: true }); + }), + ), + ); + }); + + it.effect( + "ad-hoc pooler fallback ignores the linked workdir password and saved pooler URL", + () => { + const linkedRef = "abcdefghijklmnopqrst"; + const adHocRef = "qrstabcdefghijklmnop"; + const dir = withWorkdir( + [`project_id = "${linkedRef}"`, "[db]", "major_version = 15", ""].join("\n"), + ); + mkdirSync(join(dir, "supabase", ".temp"), { recursive: true }); + writeFileSync(join(dir, "supabase", ".temp", "project-ref"), linkedRef); + writeFileSync( + join(dir, "supabase", ".temp", "pooler-url"), + `postgres://postgres.${linkedRef}:saved-workdir-password@stale.pooler.supabase.com:6543/postgres`, + ); + + const previousAccessToken = process.env["SUPABASE_ACCESS_TOKEN"]; + const previousPassword = process.env["SUPABASE_DB_PASSWORD"]; + const previousFetch = globalThis.fetch; + const requests: Array<{ readonly method: string; readonly path: string }> = []; + const connections: Array<{ + readonly conn: LegacyPgConnInput; + readonly dnsResolver: "native" | "https"; + readonly isLocal: boolean; + }> = []; + const session: LegacyDbSession = { + exec: () => Effect.void, + query: () => Effect.succeed([]), + extensionExists: () => Effect.succeed(false), + copyToCsv: () => Effect.succeed(new Uint8Array()), + queryRaw: () => Effect.succeed({ fields: [], rows: [], commandTag: "SELECT 1" }), + }; + const dbConnection = Layer.succeed(LegacyDbConnection, { + connect: (conn, options) => + Effect.sync(() => { + connections.push({ conn, ...options }); + return session; + }), + }); + const fetchMock = Object.assign( + async (input: string | URL | Request, init?: RequestInit): Promise => { + const url = new URL( + typeof input === "string" || input instanceof URL ? input : input.url, + ); + const method = init?.method ?? (input instanceof Request ? input.method : "GET"); + requests.push({ method, path: url.pathname }); + + if ( + method === "GET" && + url.pathname === `/v1/projects/${adHocRef}/config/database/pooler` + ) { + return new Response( + JSON.stringify([ + { + identifier: "primary", + database_type: "PRIMARY", + is_using_scram_auth: true, + db_user: "postgres", + db_host: "db.example", + db_port: 5432, + db_name: "postgres", + connection_string: `postgres://postgres.${adHocRef}:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres`, + connectionString: `postgres://postgres.${adHocRef}:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres`, + default_pool_size: null, + max_client_conn: null, + pool_mode: "transaction", + }, + ]), + { status: 200, headers: { "content-type": "application/json" } }, + ); + } + + if (method === "POST" && url.pathname === `/v1/projects/${adHocRef}/cli/login-role`) { + return new Response( + JSON.stringify({ + role: "cli_login_role", + password: "temporary-role-password", + ttl_seconds: 3600, + }), + { status: 201, headers: { "content-type": "application/json" } }, + ); + } + + return new Response(JSON.stringify({ message: "unexpected request" }), { + status: 404, + headers: { "content-type": "application/json" }, + }); + }, + { preconnect: previousFetch.preconnect }, + ); + + process.env["SUPABASE_ACCESS_TOKEN"] = LEGACY_VALID_TOKEN; + process.env["SUPABASE_DB_PASSWORD"] = "ambient-linked-password"; + globalThis.fetch = fetchMock; + + return resolvePoolerFallback( + dir, + { + ...linkedFlags, + linkedProjectRef: Option.some(adHocRef), + adHocProjectRef: true, + }, + { dbConnection }, + ).pipe( + Effect.tap((connOpt) => + Effect.sync(() => { + expect(Option.isSome(connOpt)).toBe(true); + if (Option.isSome(connOpt)) { + expect(connOpt.value).toEqual({ + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: `cli_login_role.${adHocRef}`, + password: "temporary-role-password", + database: "postgres", + }); + } + expect(requests).toEqual([ + { + method: "GET", + path: `/v1/projects/${adHocRef}/config/database/pooler`, + }, + { + method: "POST", + path: `/v1/projects/${adHocRef}/cli/login-role`, + }, + ]); + expect(connections).toEqual([ + { + conn: { + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: `cli_login_role.${adHocRef}`, + password: "temporary-role-password", + database: "postgres", + }, + isLocal: false, + dnsResolver: "native", + }, + ]); + }), + ), + Effect.ensuring( + Effect.sync(() => { + globalThis.fetch = previousFetch; + if (previousAccessToken === undefined) delete process.env["SUPABASE_ACCESS_TOKEN"]; + else process.env["SUPABASE_ACCESS_TOKEN"] = previousAccessToken; + if (previousPassword === undefined) delete process.env["SUPABASE_DB_PASSWORD"]; + else process.env["SUPABASE_DB_PASSWORD"] = previousPassword; + rmSync(dir, { recursive: true, force: true }); + }), + ), + ); + }, + ); + + it.effect("linked pooler fallback fetches API config when the saved pooler URL is stale", () => { + const linkedRef = "abcdefghijklmnopqrst"; + const dir = withWorkdir( + [`project_id = "${linkedRef}"`, "[db]", "major_version = 15", ""].join("\n"), + ); + mkdirSync(join(dir, "supabase", ".temp"), { recursive: true }); + writeFileSync(join(dir, "supabase", ".temp", "project-ref"), linkedRef); + writeFileSync( + join(dir, "supabase", ".temp", "pooler-url"), + "postgres://postgres.qrstabcdefghijklmnop:saved-workdir-password@aws-0-us-east-1.pooler.supabase.com:6543/postgres", + ); + + const previousAccessToken = process.env["SUPABASE_ACCESS_TOKEN"]; + const previousPassword = process.env["SUPABASE_DB_PASSWORD"]; + const previousFetch = globalThis.fetch; + const requests: Array<{ readonly method: string; readonly path: string }> = []; + const connections: Array<{ + readonly conn: LegacyPgConnInput; + readonly dnsResolver: "native" | "https"; + readonly isLocal: boolean; + }> = []; + const session: LegacyDbSession = { + exec: () => Effect.void, + query: () => Effect.succeed([]), + extensionExists: () => Effect.succeed(false), + copyToCsv: () => Effect.succeed(new Uint8Array()), + queryRaw: () => Effect.succeed({ fields: [], rows: [], commandTag: "SELECT 1" }), + }; + const dbConnection = Layer.succeed(LegacyDbConnection, { + connect: (conn, options) => + Effect.sync(() => { + connections.push({ conn, ...options }); + return session; + }), + }); + const fetchMock = Object.assign( + async (input: string | URL | Request, init?: RequestInit): Promise => { + const url = new URL(typeof input === "string" || input instanceof URL ? input : input.url); + const method = init?.method ?? (input instanceof Request ? input.method : "GET"); + requests.push({ method, path: url.pathname }); + + if ( + method === "GET" && + url.pathname === `/v1/projects/${linkedRef}/config/database/pooler` + ) { + return new Response( + JSON.stringify([ + { + identifier: "primary", + database_type: "PRIMARY", + is_using_scram_auth: true, + db_user: "postgres", + db_host: "db.example", + db_port: 5432, + db_name: "postgres", + connection_string: `postgres://postgres.${linkedRef}:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres`, + connectionString: `postgres://postgres.${linkedRef}:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres`, + default_pool_size: null, + max_client_conn: null, + pool_mode: "transaction", + }, + ]), + { status: 200, headers: { "content-type": "application/json" } }, + ); + } + + return new Response(JSON.stringify({ message: "unexpected request" }), { + status: 404, + headers: { "content-type": "application/json" }, + }); + }, + { preconnect: previousFetch.preconnect }, + ); + + process.env["SUPABASE_ACCESS_TOKEN"] = LEGACY_VALID_TOKEN; + process.env["SUPABASE_DB_PASSWORD"] = "linked-password"; + globalThis.fetch = fetchMock; + + return resolvePoolerFallback( + dir, + { + ...linkedFlags, + linkedProjectRef: Option.some(linkedRef), + }, + { projectHost: "supabase.co", dbConnection }, + ).pipe( + Effect.tap((connOpt) => + Effect.sync(() => { + expect(Option.isSome(connOpt)).toBe(true); + if (Option.isSome(connOpt)) { + expect(connOpt.value).toEqual({ + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: `postgres.${linkedRef}`, + password: "linked-password", + database: "postgres", + }); + } + expect(requests).toEqual([ + { + method: "GET", + path: `/v1/projects/${linkedRef}/config/database/pooler`, + }, + ]); + expect(connections).toEqual([]); + }), + ), + Effect.ensuring( + Effect.sync(() => { + globalThis.fetch = previousFetch; + if (previousAccessToken === undefined) delete process.env["SUPABASE_ACCESS_TOKEN"]; + else process.env["SUPABASE_ACCESS_TOKEN"] = previousAccessToken; + if (previousPassword === undefined) delete process.env["SUPABASE_DB_PASSWORD"]; + else process.env["SUPABASE_DB_PASSWORD"] = previousPassword; + rmSync(dir, { recursive: true, force: true }); + }), + ), + ); + }); }); diff --git a/apps/cli/src/legacy/shared/legacy-db-config.layer.ts b/apps/cli/src/legacy/shared/legacy-db-config.layer.ts index d6d9e355cf..9bc7e00f45 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.layer.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.layer.ts @@ -1,7 +1,6 @@ import * as net from "node:net"; import { BunServices } from "@effect/platform-bun"; import { Duration, Effect, FileSystem, Layer, Option, Path } from "effect"; -import { getDomain } from "tldts"; import { LegacyPlatformApiFactory } from "../auth/legacy-platform-api-factory.service.ts"; import { LegacyCliConfig } from "../config/legacy-cli-config.service.ts"; @@ -29,6 +28,7 @@ import { } from "./legacy-management-api-runtime.layer.ts"; import * as Errors from "./legacy-db-config.errors.ts"; import { + legacyPoolerConfigFromConnectionString, parseLegacyConnectionString, redactLegacyConnectionString, } from "./legacy-db-config.parse.ts"; @@ -245,57 +245,14 @@ export const legacyDbConfigLayer = Layer.effect( connectionString: string, ): Effect.Effect> => Effect.gen(function* () { - const sanitized = connectionString.replaceAll("[YOUR-PASSWORD]", ""); - const parsed = parseLegacyConnectionString(sanitized); - if (parsed === undefined) { - yield* debug.debug("failed to parse pooler URL"); - return Option.none(); - } - // Preserve the libpq `options` startup param (Go keeps it in - // `pgconn.Config.RuntimeParams`): legacy pooler URLs route by tenant via - // `?options=reference=`, so the actual connection must carry it. - const optionsParam = parsed.options ?? ""; - // Username must encode the project ref: either `.` or the - // `?options=reference=` query param. - const dotIndex = parsed.user.indexOf("."); - if (dotIndex === -1) { - for (const option of optionsParam.split(",")) { - const [key, value] = option.split("="); - // Mirror Go's `strings.Cut` `found` guard (connect.go:83): only reject - // when the `reference` option is present *with* a value that mismatches. - // A bare `reference` token (no `=`) or a missing `reference` key is - // accepted, exactly as Go does — do not reject on absence. - if (key === "reference" && value !== undefined && value !== ref) { - yield* debug.debug(`Pooler options does not match project ref: ${ref}`); - return Option.none(); - } - } - } else if (parsed.user.slice(dotIndex + 1) !== ref) { - yield* debug.debug(`Pooler username does not match project ref: ${ref}`); - return Option.none(); - } - // MITM guard: the pooler domain must belong to the active profile. The - // expected host comes from the resolved profile (built-in table or a YAML - // profile's `pooler_host:`), so custom/staging pooler domains are honored. - const expectedPoolerHost = cliConfig.poolerHost; - const domain = getDomain(parsed.host); - if (domain === null) { - yield* debug.debug("failed to parse pooler TLD"); - return Option.none(); - } - if ( - expectedPoolerHost.length > 0 && - expectedPoolerHost.toLowerCase() !== domain.toLowerCase() - ) { - yield* debug.debug(`Pooler domain does not belong to current profile: ${domain}`); - return Option.none(); - } - // Supavisor transaction mode does not support prepared statements; use port 5432. - return Option.some({ - ...parsed, - port: DIRECT_PORT, - ...(optionsParam.length > 0 ? { options: optionsParam } : {}), - }); + const result = legacyPoolerConfigFromConnectionString( + ref, + connectionString, + cliConfig.poolerHost, + ); + if (result._tag === "ok") return Option.some(result.conn); + yield* debug.debug(result.reason); + return Option.none(); }); // Resolve the DB password with viper's precedence: `--password` flag → @@ -326,6 +283,10 @@ export const legacyDbConfigLayer = Layer.effect( // the resolve-time IPv6 path (`NewDbConfigWithPassword` → `GetPoolerConfig`) uses // the saved URL only and errors otherwise, so this defaults off. fetchFromApi = false, + // For an ad-hoc `--project-id` ref the saved `.temp/pooler-url` belongs to the + // (possibly different) linked workdir, so ignore it and resolve the pooler for + // `ref` from the Management API instead. + ignoreSavedUrl = false, ): Effect.Effect< Option.Option, LegacyDbConfigError, @@ -335,8 +296,12 @@ export const legacyDbConfigLayer = Layer.effect( // Linked-path read: merge the `[remotes.]` override (Go's pooler // resolution runs after LoadConfig(ref) already merged), so this matches the // ref-aware read on the main linked branch rather than validating base config. + // For an ad-hoc `--project-id` ref, skip the saved workdir pooler URL because + // it belongs to the linked project, not necessarily the explicit ref. const tomlValues = yield* legacyReadDbToml(fs, path, cliConfig.workdir, ref); - let connectionString = Option.getOrUndefined(tomlValues.poolerConnectionString); + let connectionString = ignoreSavedUrl + ? undefined + : Option.getOrUndefined(tomlValues.poolerConnectionString); if (connectionString === undefined) { if (!fetchFromApi) return Option.none(); // No saved pooler URL → fetch the primary pooler config from the Management @@ -349,7 +314,20 @@ export const legacyDbConfigLayer = Layer.effect( if (primary === undefined) return Option.none(); connectionString = primary.connection_string; } - const pooler = yield* poolerConfigFrom(ref, connectionString); + let pooler = Option.none(); + if (connectionString !== undefined) { + pooler = yield* poolerConfigFrom(ref, connectionString); + } + if (Option.isNone(pooler) && fetchFromApi) { + const api = yield* (yield* LegacyPlatformApiFactory).make; + const configsOpt = yield* api.v1.getPoolerConfig({ ref }).pipe(Effect.option); + if (Option.isSome(configsOpt)) { + const primary = configsOpt.value.find((config) => config.database_type === "PRIMARY"); + if (primary !== undefined) { + pooler = yield* poolerConfigFrom(ref, primary.connection_string); + } + } + } if (Option.isNone(pooler)) return Option.none(); const poolerConn = pooler.value; if (password.length > 0) { @@ -371,11 +349,17 @@ export const legacyDbConfigLayer = Layer.effect( ref: string, dnsResolver: "native" | "https", passwordFlag: Option.Option, + adHocProjectRef = false, ): Effect.Effect => Effect.gen(function* () { // Read lazily (per invocation) rather than at layer build, so tests and - // env-substitution see the current value. - const dbPassword = yield* resolveDbPassword(passwordFlag); + // env-substitution see the current value. For an ad-hoc `--project-id` ref, + // honor only an explicit `--password` flag and ignore the ambient + // `SUPABASE_DB_PASSWORD` (which belongs to the current workdir, not this ref), + // so we always mint a temporary login role instead of leaking it. + const dbPassword = adHocProjectRef + ? (Option.getOrUndefined(passwordFlag) ?? "") + : yield* resolveDbPassword(passwordFlag); const host = `db.${ref}.${cliConfig.projectHost}`; const base: LegacyPgConnInput = { host, @@ -394,8 +378,17 @@ export const legacyDbConfigLayer = Layer.effect( return yield* initLoginRole(ref, base); } - // Direct host unreachable (IPv6-only network) → try the pooler. - const poolerConn = yield* resolvePoolerConn(ref, dnsResolver, base.password); + // Direct host unreachable (IPv6-only network) → try the pooler. For an ad-hoc + // `--project-id` ref the command already holds a Management API token, so fall + // back to the API pooler config (and ignore the workdir's saved pooler URL) + // rather than failing with the IPv6 "run supabase link" suggestion. + const poolerConn = yield* resolvePoolerConn( + ref, + dnsResolver, + base.password, + adHocProjectRef, + adHocProjectRef, + ); if (Option.isNone(poolerConn)) { return yield* Effect.fail( new Errors.LegacyDbConfigIpv6Error({ @@ -477,7 +470,7 @@ export const legacyDbConfigLayer = Layer.effect( // workdir fails with ErrNotLinked, a bad ref with the invalid-ref error, and an // unreadable ref file surfaces the filesystem problem — matching Go for every // caller of this resolver (`test db --linked`, dump, declarative). - const ref = yield* projectRef.loadProjectRef(Option.none()); + const ref = yield* projectRef.loadProjectRef(flags.linkedProjectRef ?? Option.none()); // Go's `ParseDatabaseConfig` runs `LoadProjectRef` → `LoadConfig` → // `NewDbConfigWithPassword` (`internal/utils/flags/db_url.go:81-92`), so // the `[remotes.]`-merged config (e.g. an unsupported remote @@ -492,6 +485,7 @@ export const legacyDbConfigLayer = Layer.effect( ref, flags.dnsResolver, flags.password ?? Option.none(), + flags.adHocProjectRef ?? false, ); // NB: the linked-project telemetry cache (GET /v1/projects/{ref}) is NOT // issued here. Go caches it in `PersistentPostRun` @@ -537,14 +531,17 @@ export const legacyDbConfigLayer = Layer.effect( if (flags.connType !== "linked") return Option.none(); return yield* Effect.gen(function* () { const projectRef = yield* LegacyProjectRefResolver; - const refOpt = yield* projectRef.resolveOptional(Option.none()); + const refOpt = yield* projectRef.resolveOptional(flags.linkedProjectRef ?? Option.none()); if (Option.isNone(refOpt)) return Option.none(); const ref = refOpt.value; if (!PROJECT_REF_PATTERN.test(ref)) return Option.none(); - const password = yield* resolveDbPassword(flags.password ?? Option.none()); + const adHocProjectRef = flags.adHocProjectRef ?? false; + const password = adHocProjectRef + ? (Option.getOrUndefined(flags.password ?? Option.none()) ?? "") + : yield* resolveDbPassword(flags.password ?? Option.none()); // Container-fallback: fetch the primary pooler config from the Management API // when no `.temp/pooler-url` is saved (Go's `ResolvePoolerConfigForFallback`). - return yield* resolvePoolerConn(ref, flags.dnsResolver, password, true); + return yield* resolvePoolerConn(ref, flags.dnsResolver, password, true, adHocProjectRef); }).pipe( Effect.provide( legacyLinkedDbResolverRuntimeLayer(["db", "dump"]).pipe(Layer.provide(ambientLayer)), diff --git a/apps/cli/src/legacy/shared/legacy-db-config.parse.ts b/apps/cli/src/legacy/shared/legacy-db-config.parse.ts index 2b84378b3f..81bb1761ad 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.parse.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.parse.ts @@ -1,6 +1,7 @@ import { existsSync } from "node:fs"; import { homedir, userInfo } from "node:os"; import { join } from "node:path"; +import { getDomain } from "tldts"; import type { LegacyPgConnInput } from "./legacy-db-connection.service.ts"; import { legacyPgpassPassword } from "./legacy-pgpass.ts"; import { legacyServiceSettings } from "./legacy-pgservicefile.ts"; @@ -399,6 +400,63 @@ export function parseLegacyConnectionString( return parseKeywordValueDsn(trimmed, env); } +export type LegacyPoolerConfigResult = + | { readonly _tag: "ok"; readonly conn: LegacyPgConnInput } + | { readonly _tag: "invalid"; readonly reason: string }; + +/** + * Parse + validate a Supabase transaction-pooler URL. Mirrors Go's + * `GetPoolerConfig`: strip the dashboard password placeholder, require the + * project ref in the tenant user/options, verify the pooler domain belongs to + * the active profile, and force transaction-pooler port 5432. + */ +export function legacyPoolerConfigFromConnectionString( + ref: string, + connectionString: string, + expectedPoolerHost: string, +): LegacyPoolerConfigResult { + const sanitized = connectionString.replaceAll("[YOUR-PASSWORD]", ""); + const parsed = parseLegacyConnectionString(sanitized); + if (parsed === undefined) { + return { _tag: "invalid", reason: "failed to parse pooler URL" }; + } + + const optionsParam = parsed.options ?? ""; + const dotIndex = parsed.user.indexOf("."); + if (dotIndex === -1) { + for (const option of optionsParam.split(",")) { + const separatorIndex = option.indexOf("="); + const key = separatorIndex === -1 ? option : option.slice(0, separatorIndex); + const value = separatorIndex === -1 ? undefined : option.slice(separatorIndex + 1); + if (key === "reference" && value !== undefined && value !== ref) { + return { _tag: "invalid", reason: `Pooler options does not match project ref: ${ref}` }; + } + } + } else if (parsed.user.slice(dotIndex + 1) !== ref) { + return { _tag: "invalid", reason: `Pooler username does not match project ref: ${ref}` }; + } + + const domain = getDomain(parsed.host); + if (domain === null) { + return { _tag: "invalid", reason: "failed to parse pooler TLD" }; + } + if (expectedPoolerHost.length > 0 && expectedPoolerHost.toLowerCase() !== domain.toLowerCase()) { + return { + _tag: "invalid", + reason: `Pooler domain does not belong to current profile: ${domain}`, + }; + } + + return { + _tag: "ok", + conn: { + ...parsed, + port: DIRECT_PORT, + ...(optionsParam.length > 0 ? { options: optionsParam } : {}), + }, + }; +} + /** Parse the WHATWG `postgres(ql)://` URL form. */ function parseUrlConnectionString( value: string, diff --git a/apps/cli/src/legacy/shared/legacy-db-config.parse.unit.test.ts b/apps/cli/src/legacy/shared/legacy-db-config.parse.unit.test.ts index 693217f92b..dd706b3326 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.parse.unit.test.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.parse.unit.test.ts @@ -4,6 +4,7 @@ import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { + legacyPoolerConfigFromConnectionString, parseLegacyConnectionString, redactLegacyConnectionString, } from "./legacy-db-config.parse.ts"; @@ -1009,3 +1010,80 @@ describe("redactLegacyConnectionString", () => { expect(redacted).not.toContain("bad"); }); }); + +describe("legacyPoolerConfigFromConnectionString", () => { + it("strips the placeholder password, validates the tenant, preserves options, and rewrites to port 5432", () => { + expect( + legacyPoolerConfigFromConnectionString( + "abcdefghijklmnopqrst", + "postgres://postgres.abcdefghijklmnopqrst:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres?options=reference%3Dabcdefghijklmnopqrst", + "supabase.com", + ), + ).toEqual({ + _tag: "ok", + conn: { + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: "postgres.abcdefghijklmnopqrst", + password: "", + database: "postgres", + options: "reference=abcdefghijklmnopqrst", + }, + }); + }); + + it("rejects a username tenant mismatch", () => { + expect( + legacyPoolerConfigFromConnectionString( + "abcdefghijklmnopqrst", + "postgres://postgres.wrongrefabcdefghijkl@aws-0-us-east-1.pooler.supabase.com:6543/postgres", + "supabase.com", + ), + ).toEqual({ + _tag: "invalid", + reason: "Pooler username does not match project ref: abcdefghijklmnopqrst", + }); + }); + + it("rejects an options reference mismatch when the username has no tenant suffix", () => { + expect( + legacyPoolerConfigFromConnectionString( + "abcdefghijklmnopqrst", + "postgres://postgres@aws-0-us-east-1.pooler.supabase.com:6543/postgres?options=reference%3Dwrongrefabcdefghijkl", + "supabase.com", + ), + ).toEqual({ + _tag: "invalid", + reason: "Pooler options does not match project ref: abcdefghijklmnopqrst", + }); + }); + + it("rejects a pooler host outside the expected profile domain", () => { + expect( + legacyPoolerConfigFromConnectionString( + "abcdefghijklmnopqrst", + "postgres://postgres.abcdefghijklmnopqrst@aws-0-us-east-1.pooler.example.com:6543/postgres", + "supabase.com", + ), + ).toEqual({ + _tag: "invalid", + reason: "Pooler domain does not belong to current profile: example.com", + }); + }); + + it("skips the profile-domain guard when the expected pooler host is empty", () => { + expect( + legacyPoolerConfigFromConnectionString( + "abcdefghijklmnopqrst", + "postgres://postgres.abcdefghijklmnopqrst@aws-0-us-east-1.pooler.example.com:6543/postgres", + "", + ), + ).toMatchObject({ + _tag: "ok", + conn: { + host: "aws-0-us-east-1.pooler.example.com", + port: 5432, + }, + }); + }); +}); diff --git a/apps/cli/src/legacy/shared/legacy-db-config.types.ts b/apps/cli/src/legacy/shared/legacy-db-config.types.ts index 951bc86107..11af4fc5d2 100644 --- a/apps/cli/src/legacy/shared/legacy-db-config.types.ts +++ b/apps/cli/src/legacy/shared/legacy-db-config.types.ts @@ -32,6 +32,29 @@ export interface LegacyDbConfigFlags { * flag (e.g. `test db`) omit it; the resolver then falls back to env only. */ readonly password?: Option.Option; + /** + * Optional explicit linked project ref override. Commands such as + * `gen types --project-id ` need the linked DB resolver's temp-role and + * pooler fallback behavior without requiring the current workdir to be linked. + * Absent for the normal `--linked` path, which still reads `.temp/project-ref`. + */ + readonly linkedProjectRef?: Option.Option; + /** + * Marks `linkedProjectRef` as an ad-hoc remote target supplied explicitly + * (e.g. `gen types --project-id `) rather than the current linked + * workdir. The ref may belong to a different project than the cwd, so the + * resolver must NOT inherit workdir-scoped credentials or cached state: + * - it ignores the ambient `SUPABASE_DB_PASSWORD` (shell / `.env*`) so it + * always mints a temporary login role instead of handing pg-meta an + * unrelated password, and + * - on an IPv4-only network it skips the saved `.temp/pooler-url` (which + * belongs to the linked workdir) and fetches the primary pooler config + * for `ref` from the Management API instead of failing with the IPv6 + * "run supabase link" suggestion. + * Absent / false for the normal `--linked` path, which is the workdir's own + * project and may legitimately reuse those env vars and saved files. + */ + readonly adHocProjectRef?: boolean; } /** diff --git a/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.integration.test.ts b/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.integration.test.ts new file mode 100644 index 0000000000..5f94b66971 --- /dev/null +++ b/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.integration.test.ts @@ -0,0 +1,67 @@ +import { createServer } from "node:net"; +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Exit, Layer } from "effect"; + +import { LegacyDebugFlag } from "../../shared/legacy/global-flags.ts"; +import { legacyPgDeltaSslProbeLayer } from "./legacy-pgdelta-ssl-probe.layer.ts"; +import { + LegacyPgDeltaSslProbe, + LegacyPgDeltaSslProbeError, +} from "./legacy-pgdelta-ssl-probe.service.ts"; + +async function withClosingServer(run: (port: number) => Promise): Promise { + const server = createServer((socket) => { + socket.destroy(); + }); + + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", () => resolve()); + }); + + const address = server.address(); + if (address === null || typeof address === "string") { + server.close(); + throw new Error("failed to bind closing server"); + } + + try { + return await run(address.port); + } finally { + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())), + ); + } +} + +describe("legacyPgDeltaSslProbeLayer", () => { + it.live("fails promptly when the socket closes before an SSL response byte", () => + Effect.tryPromise({ + try: () => + withClosingServer((port) => + Effect.runPromise( + Effect.gen(function* () { + const probe = yield* LegacyPgDeltaSslProbe; + const exit = yield* probe.requireSslForHost("127.0.0.1", port).pipe( + Effect.timeoutOrElse({ + duration: "1 second", + orElse: () => Effect.fail(new Error("probe did not settle after socket close")), + }), + Effect.exit, + ); + + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + expect(String(exit.cause)).toContain(LegacyPgDeltaSslProbeError.name); + expect(String(exit.cause)).toContain("closed before the server responded"); + } + }).pipe( + Effect.provide(legacyPgDeltaSslProbeLayer), + Effect.provide(Layer.succeed(LegacyDebugFlag, false)), + ), + ), + ), + catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))), + }), + ); +}); diff --git a/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.layer.ts b/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.layer.ts index c68555b3a2..4d2e0f02b5 100644 --- a/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.layer.ts +++ b/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.layer.ts @@ -49,6 +49,10 @@ export function legacyParseSslProbeTarget(dbUrl: string): LegacySslProbeTarget { return { host, port, timeoutMs }; } +function legacySslProbeTargetForHost(host: string, port: number): LegacySslProbeTarget { + return { host, port, timeoutMs: DEFAULT_PROBE_TIMEOUT_MS }; +} + /** * Interprets the server's single-byte `SSLRequest` reply: `S` → speaks TLS, * `N` → refused TLS (Go's `"server refused TLS connection"`). Any other byte is a @@ -76,6 +80,64 @@ export const legacyPgDeltaSslProbeLayer = Layer.effect( // Go disables SSL in debug mode (`require := !viper.GetBool("DEBUG")`), so a // server that speaks TLS still reports "not required" under `--debug`. const debug = yield* LegacyDebugFlag; + const probeTarget = (target: LegacySslProbeTarget) => + Effect.gen(function* () { + const outcome = yield* Effect.callback<"tls" | "refused", LegacyPgDeltaSslProbeError>( + (resume) => { + const socket = net.connect({ host: target.host, port: target.port }); + let settled = false; + const settle = ( + effect: Effect.Effect<"tls" | "refused", LegacyPgDeltaSslProbeError>, + ) => { + if (settled) return; + settled = true; + socket.destroy(); + resume(effect); + }; + socket.setTimeout(target.timeoutMs); + socket.once("connect", () => socket.write(SSL_REQUEST_PACKET)); + socket.once("data", (buf: Buffer) => { + try { + settle(Effect.succeed(legacyInterpretSslProbeByte(buf[0]))); + } catch (cause) { + settle( + Effect.fail( + cause instanceof LegacyPgDeltaSslProbeError + ? cause + : new LegacyPgDeltaSslProbeError({ message: String(cause), cause }), + ), + ); + } + }); + socket.once("timeout", () => + settle( + Effect.fail( + new LegacyPgDeltaSslProbeError({ + message: `SSL probe timed out connecting to ${target.host}:${target.port}`, + }), + ), + ), + ); + socket.once("close", () => + settle( + Effect.fail( + new LegacyPgDeltaSslProbeError({ + message: `SSL probe connection to ${target.host}:${target.port} closed before the server responded`, + }), + ), + ), + ); + socket.once("error", (err: Error) => + settle( + Effect.fail(new LegacyPgDeltaSslProbeError({ message: err.message, cause: err })), + ), + ); + return Effect.sync(() => socket.destroy()); + }, + ); + if (outcome === "refused") return false; + return !debug; + }); return LegacyPgDeltaSslProbe.of({ requireSsl: (dbUrl) => Effect.gen(function* () { @@ -88,51 +150,9 @@ export const legacyPgDeltaSslProbeLayer = Layer.effect( }`, }), }); - const outcome = yield* Effect.callback<"tls" | "refused", LegacyPgDeltaSslProbeError>( - (resume) => { - const socket = net.connect({ host: target.host, port: target.port }); - let settled = false; - const settle = ( - effect: Effect.Effect<"tls" | "refused", LegacyPgDeltaSslProbeError>, - ) => { - if (settled) return; - settled = true; - socket.destroy(); - resume(effect); - }; - socket.setTimeout(target.timeoutMs); - socket.once("connect", () => socket.write(SSL_REQUEST_PACKET)); - socket.once("data", (buf: Buffer) => { - try { - settle(Effect.succeed(legacyInterpretSslProbeByte(buf[0]))); - } catch (cause) { - settle( - Effect.fail( - cause instanceof LegacyPgDeltaSslProbeError - ? cause - : new LegacyPgDeltaSslProbeError({ message: String(cause) }), - ), - ); - } - }); - socket.once("timeout", () => - settle( - Effect.fail( - new LegacyPgDeltaSslProbeError({ - message: `SSL probe timed out connecting to ${target.host}:${target.port}`, - }), - ), - ), - ); - socket.once("error", (err: Error) => - settle(Effect.fail(new LegacyPgDeltaSslProbeError({ message: err.message }))), - ); - return Effect.sync(() => socket.destroy()); - }, - ); - if (outcome === "refused") return false; - return !debug; + return yield* probeTarget(target); }), + requireSslForHost: (host, port) => probeTarget(legacySslProbeTargetForHost(host, port)), }); }), ); diff --git a/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.service.ts b/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.service.ts index 808bcd441b..d5dca259f5 100644 --- a/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.service.ts +++ b/apps/cli/src/legacy/shared/legacy-pgdelta-ssl-probe.service.ts @@ -23,11 +23,16 @@ export interface LegacyPgDeltaSslProbeShape { * debug mode). Fails for any other connection error, matching Go's `return false, err`. */ readonly requireSsl: (dbUrl: string) => Effect.Effect; + readonly requireSslForHost: ( + host: string, + port: number, + ) => Effect.Effect; } /** A non-TLS-refusal connection failure during the SSL probe (Go's propagated `err`). */ export class LegacyPgDeltaSslProbeError extends Data.TaggedError("LegacyPgDeltaSslProbeError")<{ readonly message: string; + readonly cause?: unknown; }> {} export class LegacyPgDeltaSslProbe extends Context.Service< diff --git a/apps/cli/src/legacy/shared/legacy-pgdelta-ssl.unit.test.ts b/apps/cli/src/legacy/shared/legacy-pgdelta-ssl.unit.test.ts index d9949c8d06..c91cbc1afe 100644 --- a/apps/cli/src/legacy/shared/legacy-pgdelta-ssl.unit.test.ts +++ b/apps/cli/src/legacy/shared/legacy-pgdelta-ssl.unit.test.ts @@ -69,6 +69,10 @@ const probeLayer = (requireSsl: boolean | "error") => requireSsl === "error" ? Effect.fail(new LegacyPgDeltaSslProbeError({ message: "connection refused" })) : Effect.succeed(requireSsl), + requireSslForHost: () => + requireSsl === "error" + ? Effect.fail(new LegacyPgDeltaSslProbeError({ message: "connection refused" })) + : Effect.succeed(requireSsl), }); const prepare = (cwd: string, ref: string, requireSsl: boolean | "error" = false) => diff --git a/apps/cli/src/legacy/shared/legacy-pooler-fallback.ts b/apps/cli/src/legacy/shared/legacy-pooler-fallback.ts new file mode 100644 index 0000000000..611d6630d5 --- /dev/null +++ b/apps/cli/src/legacy/shared/legacy-pooler-fallback.ts @@ -0,0 +1,71 @@ +import { Effect, Option } from "effect"; + +import { Output } from "../../shared/output/output.service.ts"; +import { legacyYellow } from "./legacy-colors.ts"; +import type { LegacyPgConnInput } from "./legacy-db-connection.service.ts"; + +export function legacyIsDirectDbHost(host: string, projectHost: string): boolean { + return host.startsWith("db.") && host.endsWith(`.${projectHost}`); +} + +export interface LegacyPoolerFallbackOptions { + readonly run: Effect.Effect; + readonly retry: (pooler: LegacyPgConnInput) => Effect.Effect; + readonly directHost: string; + readonly eligible: boolean; + readonly resolveFallback: Effect.Effect, unknown, RF>; + readonly classifyError?: (error: E) => boolean; + readonly classifyResult?: (result: A) => boolean; +} + +/** + * Go's IPv6 pooler-fallback warning (`internal/utils/connect.go:283-289`), to stderr, + * `Yellow`-wrapped, byte-for-byte. Emitted just before the IPv4 pooler retry. + */ +export const legacyEmitPoolerFallbackWarning = (host: string): Effect.Effect => + Effect.gen(function* () { + const output = yield* Output; + yield* output.raw( + `${legacyYellow( + `Warning: Direct connection to ${host} is unavailable because this environment does not support IPv6.\nRetrying via the IPv4 connection pooler.`, + )}\n`, + "stderr", + ); + }); + +export function legacyRunWithPoolerFallback( + options: LegacyPoolerFallbackOptions, +): Effect.Effect { + const resolveFallback = options.resolveFallback.pipe( + Effect.orElseSucceed(() => Option.none()), + ); + + const retryOrReturn = (result: A) => + Effect.gen(function* () { + const pooler = yield* resolveFallback; + if (Option.isNone(pooler)) return result; + yield* legacyEmitPoolerFallbackWarning(options.directHost); + return yield* options.retry(pooler.value); + }); + + const retryOrFail = (error: E) => + Effect.gen(function* () { + const pooler = yield* resolveFallback; + if (Option.isNone(pooler)) return yield* Effect.fail(error); + yield* legacyEmitPoolerFallbackWarning(options.directHost); + return yield* options.retry(pooler.value); + }); + + const shouldRetryResult = (result: A): boolean => + options.eligible && (options.classifyResult?.(result) ?? false); + const shouldRetryError = (error: E): boolean => + options.eligible && (options.classifyError?.(error) ?? false); + + return options.run.pipe( + Effect.matchEffect({ + onFailure: (error) => (shouldRetryError(error) ? retryOrFail(error) : Effect.fail(error)), + onSuccess: (result) => + shouldRetryResult(result) ? retryOrReturn(result) : Effect.succeed(result), + }), + ); +} diff --git a/apps/cli/src/legacy/shared/legacy-pooler-fallback.unit.test.ts b/apps/cli/src/legacy/shared/legacy-pooler-fallback.unit.test.ts new file mode 100644 index 0000000000..313b7db349 --- /dev/null +++ b/apps/cli/src/legacy/shared/legacy-pooler-fallback.unit.test.ts @@ -0,0 +1,177 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Exit, Layer, Option } from "effect"; + +import { Output } from "../../shared/output/output.service.ts"; +import type { LegacyPgConnInput } from "./legacy-db-connection.service.ts"; +import { legacyRunWithPoolerFallback } from "./legacy-pooler-fallback.ts"; + +interface AttemptResult { + readonly exitCode: number; + readonly stderr: string; +} + +const poolerConn: LegacyPgConnInput = { + host: "aws-0-us-east-1.pooler.supabase.com", + port: 5432, + user: "postgres.abcdefghijklmnopqrst", + password: "secret", + database: "postgres", +}; + +function captureOutput() { + const chunks: Array<{ text: string; stream: "stdout" | "stderr" }> = []; + return { + layer: Layer.succeed(Output, { + format: "text", + interactive: true, + intro: () => Effect.void, + outro: () => Effect.void, + info: () => Effect.void, + warn: () => Effect.void, + error: () => Effect.void, + event: () => Effect.void, + task: () => + Effect.succeed({ + message: () => Effect.void, + succeed: () => Effect.void, + fail: () => Effect.void, + info: () => Effect.void, + cancel: () => Effect.void, + clear: () => Effect.void, + }), + promptText: () => Effect.die("unexpected promptText"), + promptPassword: () => Effect.die("unexpected promptPassword"), + promptConfirm: () => Effect.die("unexpected promptConfirm"), + promptSelect: () => Effect.die("unexpected promptSelect"), + promptMultiSelect: () => Effect.die("unexpected promptMultiSelect"), + progress: () => + Effect.succeed({ + start: () => Effect.void, + advance: () => Effect.void, + message: () => Effect.void, + stop: () => Effect.void, + }), + success: () => Effect.void, + fail: () => Effect.void, + raw: (text: string, stream: "stdout" | "stderr" = "stdout") => + Effect.sync(() => { + chunks.push({ text, stream }); + }), + rawBytes: (bytes: Uint8Array, stream: "stdout" | "stderr" = "stdout") => + Effect.sync(() => { + chunks.push({ text: new TextDecoder().decode(bytes), stream }); + }), + }), + get stderrText() { + return chunks + .filter((chunk) => chunk.stream === "stderr") + .map((chunk) => chunk.text) + .join(""); + }, + }; +} + +describe("legacyRunWithPoolerFallback", () => { + it.live("returns the retry outcome verbatim without re-classifying it", () => { + const out = captureOutput(); + let fallbackResolutions = 0; + let retryRuns = 0; + + return Effect.gen(function* () { + const result = yield* legacyRunWithPoolerFallback({ + run: Effect.succeed({ exitCode: 1, stderr: "network is unreachable" }), + retry: () => + Effect.sync(() => { + retryRuns += 1; + return { exitCode: 1, stderr: "network is unreachable" }; + }), + directHost: "db.abcdefghijklmnopqrst.supabase.co", + eligible: true, + resolveFallback: Effect.sync(() => { + fallbackResolutions += 1; + return Option.some(poolerConn); + }), + classifyResult: (result: AttemptResult) => result.exitCode !== 0, + }); + + expect(result).toEqual({ exitCode: 1, stderr: "network is unreachable" }); + expect(fallbackResolutions).toBe(1); + expect(retryRuns).toBe(1); + expect(out.stderrText).toContain( + "Warning: Direct connection to db.abcdefghijklmnopqrst.supabase.co is unavailable", + ); + }).pipe(Effect.provide(out.layer)); + }); + + it.live("propagates a result-path retry failure without retrying a second time", () => { + const out = captureOutput(); + const retryError = new Error("network is unreachable"); + let fallbackResolutions = 0; + let retryRuns = 0; + + return Effect.gen(function* () { + const exit = yield* legacyRunWithPoolerFallback({ + run: Effect.succeed({ exitCode: 1, stderr: "network is unreachable" }), + retry: () => + Effect.sync(() => { + retryRuns += 1; + }).pipe(Effect.andThen(Effect.fail(retryError))), + directHost: "db.abcdefghijklmnopqrst.supabase.co", + eligible: true, + resolveFallback: Effect.sync(() => { + fallbackResolutions += 1; + return Option.some(poolerConn); + }), + classifyResult: (result: AttemptResult) => result.exitCode !== 0, + classifyError: (error: Error) => error === retryError, + }).pipe(Effect.exit); + + expect(Exit.isFailure(exit)).toBe(true); + expect(fallbackResolutions).toBe(1); + expect(retryRuns).toBe(1); + expect(out.stderrText.match(/Retrying via the IPv4 connection pooler/g)).toHaveLength(1); + }).pipe(Effect.provide(out.layer)); + }); + + it.live("does not resolve a fallback when the failure is not eligible", () => { + const out = captureOutput(); + let fallbackResolutions = 0; + + return Effect.gen(function* () { + const result = yield* legacyRunWithPoolerFallback({ + run: Effect.succeed({ exitCode: 1, stderr: "network is unreachable" }), + retry: () => Effect.succeed({ exitCode: 0, stderr: "" }), + directHost: "aws-0-us-east-1.pooler.supabase.com", + eligible: false, + resolveFallback: Effect.sync(() => { + fallbackResolutions += 1; + return Option.some(poolerConn); + }), + classifyResult: (result: AttemptResult) => result.exitCode !== 0, + }); + + expect(result).toEqual({ exitCode: 1, stderr: "network is unreachable" }); + expect(fallbackResolutions).toBe(0); + expect(out.stderrText).toBe(""); + }).pipe(Effect.provide(out.layer)); + }); + + it.live("falls back from the error channel when the caller classifies the cause", () => { + const out = captureOutput(); + const directError = new Error("probe failed"); + + return Effect.gen(function* () { + const result = yield* legacyRunWithPoolerFallback({ + run: Effect.fail(directError), + retry: () => Effect.succeed({ exitCode: 0, stderr: "" }), + directHost: "db.abcdefghijklmnopqrst.supabase.co", + eligible: true, + resolveFallback: Effect.succeed(Option.some(poolerConn)), + classifyError: (error: Error) => error === directError, + }); + + expect(result).toEqual({ exitCode: 0, stderr: "" }); + expect(out.stderrText).toContain("Retrying via the IPv4 connection pooler."); + }).pipe(Effect.provide(out.layer)); + }); +}); From c8c7b1814a5a2d7235299aa622219c57e93a8cb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 00:12:47 +0000 Subject: [PATCH 34/48] chore(ci): bump golangci/golangci-lint-action from 9.2.1 to 9.3.0 in the actions-major group (#5791) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the actions-major group with 1 update: [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action). Updates `golangci/golangci-lint-action` from 9.2.1 to 9.3.0
Release notes

Sourced from golangci/golangci-lint-action's releases.

v9.3.0

What's Changed

Changes

Dependencies

Full Changelog: https://github.com/golangci/golangci-lint-action/compare/v9.2.1...v9.3.0

Commits
  • ba0d7d2 chore: prepare release v9.3.0
  • efd0857 feat: add no-run-logs-group as experimental option (#1403)
  • ed485de build(deps): bump undici from 6.24.0 to 6.27.0
  • 8872e8d build(deps-dev): bump js-yaml from 4.1.1 to 4.2.0 (#1400)
  • b163415 build(deps): bump tmp from 0.2.6 to 0.2.7 (#1399)
  • e52a9f8 build(deps): bump github/codeql-action from 4.35.5 to 4.36.0 in the github-ac...
  • 8182aa3 build(deps): bump tmp from 0.2.5 to 0.2.6 (#1397)
  • 5403a41 build(deps): bump github/codeql-action from 4.35.4 to 4.35.5 in the github-ac...
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golangci/golangci-lint-action&package-manager=github_actions&previous-version=9.2.1&new-version=9.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) 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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cli-go-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cli-go-ci.yml b/.github/workflows/cli-go-ci.yml index 2178446d15..0d497ffc99 100644 --- a/.github/workflows/cli-go-ci.yml +++ b/.github/workflows/cli-go-ci.yml @@ -86,7 +86,7 @@ jobs: go golangci-lint - - uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1 + - uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0 with: args: --timeout 5m --verbose install-mode: none From 9a9a83d41fb64e022e160ffad765a1a33e0a9e33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 00:13:18 +0000 Subject: [PATCH 35/48] fix(deps): bump the npm-major group across 1 directory with 5 updates (#5790) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-major group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [fumadocs-core](https://github.com/fuma-nama/fumadocs) | `16.10.5` | `16.10.6` | | [fumadocs-mdx](https://github.com/fuma-nama/fumadocs) | `15.0.12` | `15.0.13` | | [fumadocs-ui](https://github.com/fuma-nama/fumadocs) | `16.10.5` | `16.10.6` | | [@typescript/native-preview](https://github.com/microsoft/typescript-go) | `7.0.0-dev.20260626.1` | `7.0.0-dev.20260628.1` | | [tldts](https://github.com/remusao/tldts) | `6.1.86` | `7.4.5` | Updates `fumadocs-core` from 16.10.5 to 16.10.6
Commits
  • 593302d Version Packages (#3382)
  • 923e581 docs: add more FAQs
  • 9fae790 add missing changesets
  • 6ab44ad update lock file
  • b038183 fix(local-md): fix deprecation warnings
  • 4bf7f0d fix(tanstack-start-spa): remove getPageMarkdownUrl, unused and identical to s...
  • d0b57c8 fix(api-docs): inline ref-parser
  • 839e3aa feat(core): handle BASE_PATH automatically
  • 88d1a8a fix(tanstack-start): api search for SPA with serve (#3378)
  • 4abdb05 refactor: use cnfast
  • Additional commits viewable in compare view

Updates `fumadocs-mdx` from 15.0.12 to 15.0.13
Release notes

Sourced from fumadocs-mdx's releases.

fumadocs-mdx@15.0.13

Require collection query param at regex matching

Instead of passing through all JSON/YAML files, the meta loader now requires collection query param to be triggered.

Commits
  • 47b8b63 Version Packages (#3384)
  • baacf1a perf(mdx): require collection query param at regex matching
  • 593302d Version Packages (#3382)
  • 923e581 docs: add more FAQs
  • 9fae790 add missing changesets
  • 6ab44ad update lock file
  • b038183 fix(local-md): fix deprecation warnings
  • 4bf7f0d fix(tanstack-start-spa): remove getPageMarkdownUrl, unused and identical to s...
  • d0b57c8 fix(api-docs): inline ref-parser
  • 839e3aa feat(core): handle BASE_PATH automatically
  • Additional commits viewable in compare view

Updates `fumadocs-ui` from 16.10.5 to 16.10.6
Commits
  • 593302d Version Packages (#3382)
  • 923e581 docs: add more FAQs
  • 9fae790 add missing changesets
  • 6ab44ad update lock file
  • b038183 fix(local-md): fix deprecation warnings
  • 4bf7f0d fix(tanstack-start-spa): remove getPageMarkdownUrl, unused and identical to s...
  • d0b57c8 fix(api-docs): inline ref-parser
  • 839e3aa feat(core): handle BASE_PATH automatically
  • 88d1a8a fix(tanstack-start): api search for SPA with serve (#3378)
  • 4abdb05 refactor: use cnfast
  • Additional commits viewable in compare view

Updates `@typescript/native-preview` from 7.0.0-dev.20260626.1 to 7.0.0-dev.20260628.1
Commits

Updates `tldts` from 6.1.86 to 7.4.5
Release notes

Sourced from tldts's releases.

v7.4.5

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

Authors: 1

v7.4.4

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

Authors: 1

v7.4.3

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

:nut_and_bolt: Dependencies

Authors: 2

v7.4.2

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

:nut_and_bolt: Dependencies

Authors: 2

... (truncated)

Changelog

Sourced from tldts's changelog.

v7.4.5 (Sun Jun 28 2026)

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

Authors: 1


v7.4.4 (Tue Jun 23 2026)

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

Authors: 1


v7.4.3 (Mon Jun 15 2026)

:scroll: Update Public Suffix List

  • tldts-experimental, tldts

:nut_and_bolt: Dependencies

Authors: 2


v7.4.2 (Sat May 30 2026)

:scroll: Update Public Suffix List

... (truncated)

Commits
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for tldts since your current version.


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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/docs/package.json | 6 +- pnpm-lock.yaml | 449 ++++++++++++++++++++--------------------- pnpm-workspace.yaml | 4 +- 3 files changed, 222 insertions(+), 237 deletions(-) diff --git a/apps/docs/package.json b/apps/docs/package.json index c0a5f29ec5..898d807b07 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -8,9 +8,9 @@ "build": "bun run generate && next build" }, "dependencies": { - "fumadocs-core": "^16.10.5", - "fumadocs-mdx": "^15.0.12", - "fumadocs-ui": "^16.10.5", + "fumadocs-core": "^16.10.6", + "fumadocs-mdx": "^15.0.13", + "fumadocs-ui": "^16.10.6", "next": "^16.2.9", "react": "^19.2.7", "react-dom": "^19.2.7" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d796396d78..ae8e53cb34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,8 +37,8 @@ catalogs: specifier: ^1.3.14 version: 1.3.14 '@typescript/native-preview': - specifier: 7.0.0-dev.20260626.1 - version: 7.0.0-dev.20260626.1 + specifier: 7.0.0-dev.20260628.1 + version: 7.0.0-dev.20260628.1 '@vitest/coverage-istanbul': specifier: ^4.1.9 version: 4.1.9 @@ -61,7 +61,7 @@ catalogs: specifier: ^0.23.0 version: 0.23.0 tldts: - specifier: ^7.4.4 + specifier: ^7.4.5 version: 7.4.5 vitest: specifier: ^4.1.9 @@ -155,7 +155,7 @@ importers: version: 19.2.17 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260626.1 + version: 7.0.0-dev.20260628.1 '@vercel/detect-agent': specifier: ^1.2.3 version: 1.2.3 @@ -259,7 +259,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260626.1 + version: 7.0.0-dev.20260628.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -282,14 +282,14 @@ importers: apps/docs: dependencies: fumadocs-core: - specifier: ^16.10.5 - version: 16.10.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.22.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + specifier: ^16.10.6 + version: 16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) fumadocs-mdx: - specifier: ^15.0.12 - version: 15.0.12(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.10.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.22.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(rolldown@1.0.2)(vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) + specifier: ^15.0.13 + version: 15.0.13(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(rolldown@1.0.2)(vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) fumadocs-ui: - specifier: ^16.10.5 - version: 16.10.5(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.10.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.22.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: ^16.10.6 + version: 16.10.6(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next: specifier: ^16.2.9 version: 16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -339,7 +339,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260626.1 + version: 7.0.0-dev.20260628.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -381,7 +381,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260626.1 + version: 7.0.0-dev.20260628.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -431,7 +431,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260626.1 + version: 7.0.0-dev.20260628.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -471,7 +471,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260626.1 + version: 7.0.0-dev.20260628.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -523,7 +523,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260626.1 + version: 7.0.0-dev.20260628.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -2119,8 +2119,8 @@ packages: '@radix-ui/primitive@1.1.4': resolution: {integrity: sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==} - '@radix-ui/react-accordion@1.2.14': - resolution: {integrity: sha512-iE8YB9nmTBH8zd73ofBISZ8JCzgMoMkATJr7qDwa6u5F1+7mTM81V6fa71jgZ65rpjVpecDf1vSnwIFP9Ly1zw==} + '@radix-ui/react-accordion@1.2.15': + resolution: {integrity: sha512-24Zz/0SYx8F2bSVThBnQrdJs2VbKelyuJordcFRRdA0fRAhrq/wSegGCqaQz34VQoiWqSMGYCYXEhynLSlyQlg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2132,8 +2132,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-arrow@1.1.10': - resolution: {integrity: sha512-j2VTDz1vgCsmuG0k5lBfOcM8n5JPFqZBcMryasFjHYMhwxYL5SRUV5lMSUpRdNtw3D/Sv8pzJtrlAgkssYSsQQ==} + '@radix-ui/react-arrow@1.1.11': + resolution: {integrity: sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2145,8 +2145,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collapsible@1.1.14': - resolution: {integrity: sha512-9bT+FvifX1FK2Mj6UEsTdyu0cN3JaA3KdfhaBao+ONrYFy/pyOy3TU1TNw7iOk1o+0hOEq67RojlUUmoFGwxyA==} + '@radix-ui/react-collapsible@1.1.15': + resolution: {integrity: sha512-8A1zibu5skAQ+UVbaeNH5hVMibiFCRJzgMuM14LTWGttnTZKQL9jwYnhAbHRuxrtCqPXa4JvvnVUq1pTNgyZYw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2158,8 +2158,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collection@1.1.10': - resolution: {integrity: sha512-IVVz4EvBcKjrzKgof714qDnz/SzQAkLA2Emh5edlHbgcE6fNd3Un6CJLlaYcnm8N4JmAtzQgse4dOKxcD2yc9g==} + '@radix-ui/react-collection@1.1.11': + resolution: {integrity: sha512-djW9+zeg137KQdlPtmE8xnaD+K2rcXXMWFrSg0hsmYZ6HRbdTA7tDHFgpaW9+huWVEu0RCabL+985T4TA0BE7g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2189,8 +2189,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.17': - resolution: {integrity: sha512-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw==} + '@radix-ui/react-dialog@1.1.18': + resolution: {integrity: sha512-apa28mldjMgORmE6g/w3sCcA0Y9UAVeeDVoozN4i7kOw12mLl9RBchfzK3Nn6qxOWjrZhK1Lfy7f07kyzxtnBw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2211,8 +2211,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.13': - resolution: {integrity: sha512-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg==} + '@radix-ui/react-dismissable-layer@1.1.14': + resolution: {integrity: sha512-4lUhWTWAjbDIqFrAPWJ3WqBOpO5YchVZ88X3nh6H9Lu5AFi5nCUeTPj3D8FSDmabmFeRe9ME0BDA4MwKTha5GQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2233,8 +2233,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-focus-scope@1.1.10': - resolution: {integrity: sha512-Fas/lXQqhVvqwAb64s5RFeHiHYElZ6SUQbZaNd6EkfhP/Al7wTIQ9WIR4QVX475tlu5yFCEdDcJH6/UwsZjMWw==} + '@radix-ui/react-focus-scope@1.1.11': + resolution: {integrity: sha512-Mn88Vg2whaRocGJNOH+DKFqYm6ySFPQaiwHNxZPyjn99B52KAEJWWY9NP83+nWdk2HM3rdov+STu9AG471Rt9w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2255,8 +2255,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-navigation-menu@1.2.16': - resolution: {integrity: sha512-nJ0SkrSQgudyYhMiYeHA1ayLVuduEJCFLan1RZZN7c9kqzzCFLaU9kuy81uNtqzweM9YaQPgWzxi9MwQ9jZ04g==} + '@radix-ui/react-navigation-menu@1.2.17': + resolution: {integrity: sha512-fYeYQvbeNn5AQk2RBbpO7koLm2YbS00UYxC/IL2sgLlninEH5UNIv+X3E0KJ1Vy4WIo+dhN9w8GNqSHhbHWCIg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2268,8 +2268,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popover@1.1.17': - resolution: {integrity: sha512-/YSAOdJ7YJvdn7bn5sdSx2egW+SKY+u7O5RyAVs94Ymrg2fg5QTSFPMRkzvhGyFuE4/qsmPBdrwYoZMZh/4f+g==} + '@radix-ui/react-popover@1.1.18': + resolution: {integrity: sha512-qdXDes+eHlnMUGlBAAAe5EG7oOQvqsXuq4mq585diMudg80iB+jHbsSeG3+Q4eWNsogNyhqU2p/3i+Y0iEepqg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2281,8 +2281,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popper@1.3.1': - resolution: {integrity: sha512-bhnq/0DEPTi2lsOD3J5rTL65qUKHbKbhqHsmN9TMiclSXpipi651ooUKPPp6G5lF/WiHBdn1s0Wuqsn+myVAvw==} + '@radix-ui/react-popper@1.3.2': + resolution: {integrity: sha512-3QXNeMkdshed1MR3LNoiCirBywRFPkD8ETJa/HlPuLwSajaQixf2ro+isoDNJlGABg9ug41XuZpINZJIle4XWg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2294,8 +2294,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.12': - resolution: {integrity: sha512-m309havGzsjLHHaIX50G5PlvRs3xkgPCsGk/5PTvYm8D5q33yG0J7w/712PTOhid7NTaFETtnSXjngHQavvhVw==} + '@radix-ui/react-portal@1.1.13': + resolution: {integrity: sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2320,8 +2320,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.1.6': - resolution: {integrity: sha512-wetd0QI77DbvrPpTAvH1SqOxsYF2wZe5TNxqwOd5Ty4XDpV3dpV0s8K/1MGMJBeY5o7lg8ub5VIt1Ub+yVen6g==} + '@radix-ui/react-primitive@2.1.7': + resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2333,8 +2333,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.13': - resolution: {integrity: sha512-9gkwneI0guf8JDmrFxPjJF6Ozzgioyw+/lonYNCwefS9ZHA05er0BVHiXr+LbWGHxUfczvMY6G1oiZZi1VzjRw==} + '@radix-ui/react-roving-focus@1.1.14': + resolution: {integrity: sha512-8Qcnx9447tx/aCBgw6Jenfqg4Skq+vqab9mCBmuGNipIS5YXvL275wbKEu7+ICYHIlAPgCduUMJH1XOYewKF6Q==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2346,8 +2346,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-scroll-area@1.2.12': - resolution: {integrity: sha512-xuafVzQiTCLsyEjakowTdG3OgTXsmO7IdCiO77otIa+z44xoLNs9Do5eg7POFumIOCjtG6djfm6RKUKpUa/csA==} + '@radix-ui/react-scroll-area@1.2.13': + resolution: {integrity: sha512-7tncSubo2G0UY1e8rk+72qe3XRzrGnOLtZQ1PL1KoBfRUNX0NrJT5akb+0kfwSCc3gVR4wdHqyhAQBDpDNOwDw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2368,8 +2368,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-tabs@1.1.15': - resolution: {integrity: sha512-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg==} + '@radix-ui/react-tabs@1.1.16': + resolution: {integrity: sha512-v3Ab2l7z6U7tRB4xA0IyKdq0OsqaO1o9ZjsIEoKKnSZ/l96mZz8aCTX0NCXw+YVHJXr8Km4d+Mn6/Q8YjXa+gw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2408,15 +2408,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.1.2': - resolution: {integrity: sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-layout-effect@1.1.2': resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} peerDependencies: @@ -2453,8 +2444,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-visually-hidden@1.2.6': - resolution: {integrity: sha512-jCE0WljWifTI4niIMCll06kGpsJTAPiZVU9H4WR1N6qW7At9ystHbN7dDB+we2xH535roFHj7qKS+RGj0FMDWQ==} + '@radix-ui/react-visually-hidden@1.2.7': + resolution: {integrity: sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2598,32 +2589,32 @@ packages: peerDependencies: semantic-release: '>=20.1.0' - '@shikijs/core@4.3.0': - resolution: {integrity: sha512-EooU3i9F6IAE8kEu+AnGf9DFZWkQBZ+hJn3tLVbsH+61mtQiva5biai66fAA6nvFPXkLgvrh7BrR7YcJU83xQQ==} + '@shikijs/core@4.3.1': + resolution: {integrity: sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==} engines: {node: '>=20'} - '@shikijs/engine-javascript@4.3.0': - resolution: {integrity: sha512-hTv/KiFf2tpiqlACPiztGGurEARWIutB8YUhcrA1pUC7VzzwKO+g5crUocrLztrZ5ro5Z4hbXg7bYclETn3gSQ==} + '@shikijs/engine-javascript@4.3.1': + resolution: {integrity: sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==} engines: {node: '>=20'} - '@shikijs/engine-oniguruma@4.3.0': - resolution: {integrity: sha512-1vMdN3gHfnKfLYwecUI2ITJI4RhHt96xEaJumVn7Heb0IlJ8WQMIH0Voak+2j22BpSNKdnOfB/pCTPnPm2gq7A==} + '@shikijs/engine-oniguruma@4.3.1': + resolution: {integrity: sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==} engines: {node: '>=20'} - '@shikijs/langs@4.3.0': - resolution: {integrity: sha512-rnlqFbBRSys9bT4gl/5rw9RnS0W/I84ZldXPkO7cvlEMoV85TyF/aU01N7/NbSR776RNLjrJKjfFUXJR6wN1Cg==} + '@shikijs/langs@4.3.1': + resolution: {integrity: sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==} engines: {node: '>=20'} - '@shikijs/primitive@4.3.0': - resolution: {integrity: sha512-CPkz64PTa5diRW1ggzMZH9VM/du4RNChYgVtgqrFcgruvIybmCvySv8GkiHSczUHXYuuR8TdKEwFx+UnZMpgdg==} + '@shikijs/primitive@4.3.1': + resolution: {integrity: sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==} engines: {node: '>=20'} - '@shikijs/themes@4.3.0': - resolution: {integrity: sha512-Avgt05YiT+Y3prjIc9lmQxhJzHBcCfR6cjiFW4OyaMBbt2A6trX5rfjUzx+Vj/mE9qpArYjatnqo9XPjQNW/AQ==} + '@shikijs/themes@4.3.1': + resolution: {integrity: sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==} engines: {node: '>=20'} - '@shikijs/types@4.3.0': - resolution: {integrity: sha512-oc8b9U2SYvofKZk8e/737nIX0qwf6eV2vHFATeObAu7r+mUVpLs8Re0BmVkIjAWAYgkmG/CzLNo7rzuBzRu/wQ==} + '@shikijs/types@4.3.1': + resolution: {integrity: sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==} engines: {node: '>=20'} '@shikijs/vscode-textmate@10.0.2': @@ -2864,50 +2855,50 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260626.1': - resolution: {integrity: sha512-VDPHf8RZRzsBH6cgArK1u9sH8MODMNkvkNczEt1EXpOLfZeplIhcpKld5Zc9Da8/S9YB4768rjfBkdFokxBu+Q==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260628.1': + resolution: {integrity: sha512-eHHDHAZjbZ681sHyW87tg8mH4+xIs+Q7cHKIlrdafqeny1KYWorj3O9Qfnjvcl2Yd2Eq+IzJxffF6Tepy13L4Q==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260626.1': - resolution: {integrity: sha512-bi7iQZe2A90cFJ3EQnigezEI7F0e5vX6E/QUGluQ1mKZmcbbQCdAwDMDQFV8Z6w3xNrk9AYyNQbvq0DtzJX46w==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260628.1': + resolution: {integrity: sha512-xIsJSXa0Fsv0pPfQ0YYa7nUQJ/nGRF/r8p60e0Aa29SexxgOXMsu3YhOnUnJEdbvaPzqlKqa1GqNGpbGnLQcLA==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260626.1': - resolution: {integrity: sha512-/iptuCYiucdY0HK0nE5ydRjIx0KOf3AY7fieRaQHA4X9/s4ey37rc/58aBX3dtx0x2EhlzpXT5f0ikYY65Zynw==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260628.1': + resolution: {integrity: sha512-p3yj2a70vkaFB3OD+Vt4oNUaiE7I30fwiXs6LVNAW6k0GSHNc4ucYcWVlpcp8+cej9RBQgxMnMH5MSVYmNhUvw==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260626.1': - resolution: {integrity: sha512-ilB0Ew5GWLrqMklNVrMdCEyNePypoEMnd4l5aUopnDRebgtXmtu0RE2GDl9LOTZ1BwlbXsuYAkEHt18Ta25hxg==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260628.1': + resolution: {integrity: sha512-BoclteE+MBOnfK0Qh21mQgrvYPy/v2k7CPTPufcNp1g1fsSvsF3Xv6K8I/grEjo3ZjNrgIvVxivoAqaQhSMlGA==} engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260626.1': - resolution: {integrity: sha512-4o80l1+RoJLkR1G4KOZjTYN1yOAGbq0K2CAP0zF35MPnD8O559Tw8OMuYA+XPpEFE0fkb7mmcxL8J9cxM/kAbw==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260628.1': + resolution: {integrity: sha512-LKNKDoTnM8aacpbt1u8kJR1feXpBuLlvKKbVt0RYBL4j1OA148TXKjLtbVu37I0lcVxjqERYuAybpvut2xq31w==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260626.1': - resolution: {integrity: sha512-xHxewRWY74zJnwt4bj+Kdf6Owfs6L0Fbggb37psSa6CvofqcvSI3AyuwiBNjC0T8Eb/d/BMMXttbPJ4XXqMpXQ==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260628.1': + resolution: {integrity: sha512-XUGCYlDAfeA4PIm7ZSZtVHmvffVoMct0LhTA/CoALhSQFnFnJdipOfsZghSyU6TCpTuzBoOhWCjBufrQC23xOQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260626.1': - resolution: {integrity: sha512-Iuf5nqTY4m5kxEvraDpieEf0XS6gDdjBBkw3g74pseznhpMeDFzgRvVCbSaf2pdjBNGugZbiBDVTEOclmD9cjA==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260628.1': + resolution: {integrity: sha512-rJMZ+YaRv9XybOZBYAsJt7x/K2IWmX9bgRatHobl0wwkKmfKd3giNnRXcDwOqYeCaWzunCbUhAirUtuUprRcnQ==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260626.1': - resolution: {integrity: sha512-2RC5omeJPqWKebi5zM3nkMSrTpKpq2zLB3SfceqmFZY3vMCXtIJSE6IpA/AszlKQmNcbT/kWscmgE/PXEvnguQ==} + '@typescript/native-preview@7.0.0-dev.20260628.1': + resolution: {integrity: sha512-359WmBk3vA/bJxfeWyLbFeeejmky7Wssc8HMu0Iabu490WJLj/FqkDC51V65yuDp+anMAEkgeKO43fj6pMb/ZA==} engines: {node: '>=16.20.0'} hasBin: true @@ -3410,6 +3401,10 @@ packages: resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} engines: {node: '>=0.10.0'} + cnfast@0.0.8: + resolution: {integrity: sha512-EjXKMfGfdwtV4AcNSQ6AwQaVzpC1B7IxeiwA3FlhTXz+YFlMKVi4c1JX9tgD2QOlahQXjB8KUXrBaYG+3v871Q==} + hasBin: true + code-excerpt@4.0.0: resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3979,8 +3974,8 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} - framer-motion@12.42.0: - resolution: {integrity: sha512-wp7EJnfWaaEScVygKv3e20udoRz+LbtxScsuTkakAxfXmt+ReC6WyPW2nINRAGvd+hG9odwcjBLyOTPjH5pBRA==} + framer-motion@12.42.2: + resolution: {integrity: sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -4013,8 +4008,8 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - fumadocs-core@16.10.5: - resolution: {integrity: sha512-e/xrZnKvQo8bF/WYMwPuym8PR3OtjZzHy0S/EIOvGwjKRgVq9z6J58zaBpi4LvYtPVZxNGsxdZVlmZXCVWq4FQ==} + fumadocs-core@16.10.6: + resolution: {integrity: sha512-vd/hidAsC1d8ldrfCvr/vb/H46AC/iRm0Zp4SOWWWfa1iNoFw1mFIRGHwaNDB9ETkZPZ7xUwhi3QkhlXPrYbVw==} peerDependencies: '@mdx-js/mdx': '*' '@mixedbread/sdk': 0.x.x @@ -4072,8 +4067,8 @@ packages: zod: optional: true - fumadocs-mdx@15.0.12: - resolution: {integrity: sha512-R4WenrNQxSKi+QU46Q1cscVWi+S90dj3As4jdN+vgChO2o0TVOj+FFIe3onWM7mglhPj53NxZp/upP+t/ryekQ==} + fumadocs-mdx@15.0.13: + resolution: {integrity: sha512-VsGhCiLriXXMzm3WbgrVP7t6LvOthwh1BC+IGSI1ZW63UcSo1jE4aAiuUrTIF0jv1EGQkJG8cPsy0cOnf4sejA==} hasBin: true peerDependencies: '@types/mdast': '*' @@ -4103,13 +4098,13 @@ packages: vite: optional: true - fumadocs-ui@16.10.5: - resolution: {integrity: sha512-vd69ckYx/4a1aoJTCUJ5LBkqNeOFxm3r+8SK9bVYaeHJrY/n8+4W6b0soqxVqgj1UwNmgovoAg0vlsYmSxZBgQ==} + fumadocs-ui@16.10.6: + resolution: {integrity: sha512-Sgp3r1FKNMnuG1AQlLUDsOcuE9kR3GU6RGuIUC5io3PFUNRc+IE4cjwGYv4i8vZi+VPV1Qqwm8oWztHCkwLvsg==} peerDependencies: '@takumi-rs/image-response': '*' '@types/mdx': '*' '@types/react': '*' - fumadocs-core: 16.10.5 + fumadocs-core: 16.10.6 next: 16.x.x react: ^19.2.0 react-dom: ^19.2.0 @@ -4550,14 +4545,14 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true - js-yaml@4.2.0: - resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} - hasBin: true - js-yaml@4.3.0: resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true + js-yaml@5.2.1: + resolution: {integrity: sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==} + hasBin: true + jsbn@0.1.1: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} @@ -4791,8 +4786,8 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lucide-react@1.22.0: - resolution: {integrity: sha512-c9o3l0PiNcgOQDW4F31BEYHudE7kgxVt3o30qMl36ZPwTxXlGB4QnLilhERvVM4uh/pl5MDyY1/gzZSYcHDtBg==} + lucide-react@1.23.0: + resolution: {integrity: sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -5082,14 +5077,14 @@ packages: engines: {node: '>=10'} hasBin: true - motion-dom@12.42.0: - resolution: {integrity: sha512-M63h4n8R+quJdNhBwuLlgxM+OLYa9+I/T2pzDRboB9fLXRdbou+Gw7Zury+SkpaCyACP1JHSjHgZ1EgTkBr30w==} + motion-dom@12.42.2: + resolution: {integrity: sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==} motion-utils@12.39.0: resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} - motion@12.42.0: - resolution: {integrity: sha512-Qhwvu9sVl5/URSq5CNzwMCpSKK8Uhnrwb6VO977kZyj/wOCS7mWebJUnBoHx5cZU1Zv8a9BD5CSICWKAlrLJgA==} + motion@12.42.2: + resolution: {integrity: sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -6043,8 +6038,8 @@ packages: resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} engines: {node: '>= 0.4'} - shiki@4.3.0: - resolution: {integrity: sha512-NKKjWzR6LIGL3sXBrWDw9sDS9cxx42/DkysaNqJEeOWE8Kix5gpak0bc00OfDVEO4oyXSyz8+aRaqKoBD1yo7A==} + shiki@4.3.1: + resolution: {integrity: sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==} engines: {node: '>=20'} side-channel-list@1.0.1: @@ -6271,9 +6266,6 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} - tailwind-merge@3.6.0: - resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} - tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -8016,16 +8008,16 @@ snapshots: '@radix-ui/primitive@1.1.4': {} - '@radix-ui/react-accordion@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-accordion@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collapsible': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-collapsible': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-collection': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -8033,23 +8025,23 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-arrow@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-arrow@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-collapsible@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-collapsible@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 @@ -8058,11 +8050,11 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-collection@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-collection@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -8082,18 +8074,18 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-dialog@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-dialog@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) aria-hidden: 1.2.6 @@ -8110,13 +8102,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-dismissable-layer@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-dismissable-layer@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-escape-keydown': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: @@ -8129,10 +8121,10 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-focus-scope@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-focus-scope@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -8147,41 +8139,41 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-navigation-menu@1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-navigation-menu@1.2.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-collection': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-visually-hidden': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-visually-hidden': 1.2.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-popover@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-popover@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-popper': 1.3.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-popper': 1.3.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) aria-hidden: 1.2.6 @@ -8192,13 +8184,13 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-popper@1.3.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-popper@1.3.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-arrow': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-arrow': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-rect': 1.1.2(@types/react@19.2.17)(react@19.2.7) @@ -8210,9 +8202,9 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-portal@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-portal@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -8229,7 +8221,7 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-primitive@2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-primitive@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 @@ -8238,15 +8230,15 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-roving-focus@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-roving-focus@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-collection': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 @@ -8255,7 +8247,7 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-scroll-area@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-scroll-area@1.2.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/number': 1.1.2 '@radix-ui/primitive': 1.1.4 @@ -8263,7 +8255,7 @@ snapshots: '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 @@ -8279,15 +8271,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-tabs@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-tabs@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.4 '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-roving-focus': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -8316,13 +8308,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-use-escape-keydown@1.1.2(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: react: 19.2.7 @@ -8349,9 +8334,9 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-visually-hidden@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-visually-hidden@1.2.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: @@ -8486,40 +8471,40 @@ snapshots: transitivePeerDependencies: - supports-color - '@shikijs/core@4.3.0': + '@shikijs/core@4.3.1': dependencies: - '@shikijs/primitive': 4.3.0 - '@shikijs/types': 4.3.0 + '@shikijs/primitive': 4.3.1 + '@shikijs/types': 4.3.1 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@4.3.0': + '@shikijs/engine-javascript@4.3.1': dependencies: - '@shikijs/types': 4.3.0 + '@shikijs/types': 4.3.1 '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.6 - '@shikijs/engine-oniguruma@4.3.0': + '@shikijs/engine-oniguruma@4.3.1': dependencies: - '@shikijs/types': 4.3.0 + '@shikijs/types': 4.3.1 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@4.3.0': + '@shikijs/langs@4.3.1': dependencies: - '@shikijs/types': 4.3.0 + '@shikijs/types': 4.3.1 - '@shikijs/primitive@4.3.0': + '@shikijs/primitive@4.3.1': dependencies: - '@shikijs/types': 4.3.0 + '@shikijs/types': 4.3.1 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 - '@shikijs/themes@4.3.0': + '@shikijs/themes@4.3.1': dependencies: - '@shikijs/types': 4.3.0 + '@shikijs/types': 4.3.1 - '@shikijs/types@4.3.0': + '@shikijs/types@4.3.1': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -8746,36 +8731,36 @@ snapshots: dependencies: '@types/node': 26.0.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260626.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260628.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260626.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260628.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260626.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260628.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260626.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260628.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260626.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260628.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260626.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260628.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260626.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260628.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260626.1': + '@typescript/native-preview@7.0.0-dev.20260628.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260626.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260626.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260626.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260626.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260626.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260626.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260626.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260628.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260628.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260628.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260628.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260628.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260628.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260628.1 '@ungap/structured-clone@1.3.2': {} @@ -9363,6 +9348,8 @@ snapshots: cluster-key-slot@1.1.1: {} + cnfast@0.0.8: {} + code-excerpt@4.0.0: dependencies: convert-to-spaces: 2.0.1 @@ -10029,9 +10016,9 @@ snapshots: forwarded@0.2.0: {} - framer-motion@12.42.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + framer-motion@12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - motion-dom: 12.42.0 + motion-dom: 12.42.2 motion-utils: 12.39.0 tslib: 2.8.1 optionalDependencies: @@ -10053,21 +10040,21 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@16.10.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.22.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3): + fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3): dependencies: '@orama/orama': 3.1.18 estree-util-value-to-estree: 3.5.0 github-slugger: 2.0.0 hast-util-to-estree: 3.1.3 hast-util-to-jsx-runtime: 2.3.6 - js-yaml: 4.3.0 + js-yaml: 5.2.1 mdast-util-mdx: 3.0.0 mdast-util-to-markdown: 2.1.2 remark: 15.0.1 remark-gfm: 4.0.1 remark-rehype: 11.1.2 scroll-into-view-if-needed: 3.1.0 - shiki: 4.3.0 + shiki: 4.3.1 tinyglobby: 0.2.17 unified: 11.0.5 unist-util-visit: 5.1.0 @@ -10078,7 +10065,7 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 '@types/react': 19.2.17 - lucide-react: 1.22.0(react@19.2.7) + lucide-react: 1.23.0(react@19.2.7) next: 16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -10086,18 +10073,18 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-mdx@15.0.12(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.10.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.22.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(rolldown@1.0.2)(vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)): + fumadocs-mdx@15.0.13(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(rolldown@1.0.2)(vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 chokidar: 5.0.0 esbuild: 0.28.1 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.10.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.22.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) - js-yaml: 4.2.0 + fumadocs-core: 16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + js-yaml: 5.2.1 mdast-util-mdx: 3.0.0 picocolors: 1.1.1 - picomatch: 4.0.4 + picomatch: 4.0.5 tinyexec: 1.2.4 tinyglobby: 0.2.17 unified: 11.0.5 @@ -10116,32 +10103,32 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-ui@16.10.5(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.10.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.22.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + fumadocs-ui@16.10.6(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@fuma-translate/react': 1.0.2(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@fumadocs/tailwind': 0.0.5 - '@radix-ui/react-accordion': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-collapsible': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-dialog': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-accordion': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-collapsible': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dialog': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-navigation-menu': 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-popover': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-navigation-menu': 1.2.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-popover': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-scroll-area': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-scroll-area': 1.2.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-tabs': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-tabs': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) class-variance-authority: 0.7.1 - fumadocs-core: 16.10.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.22.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) - lucide-react: 1.22.0(react@19.2.7) - motion: 12.42.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + cnfast: 0.0.8 + fumadocs-core: 16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + lucide-react: 1.23.0(react@19.2.7) + motion: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next-themes: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) rehype-raw: 7.0.0 scroll-into-view-if-needed: 3.1.0 - shiki: 4.3.0 - tailwind-merge: 3.6.0 + shiki: 4.3.1 unist-util-visit: 5.1.0 optionalDependencies: '@types/mdx': 2.0.14 @@ -10658,11 +10645,11 @@ snapshots: dependencies: argparse: 2.0.1 - js-yaml@4.2.0: + js-yaml@4.3.0: dependencies: argparse: 2.0.1 - js-yaml@4.3.0: + js-yaml@5.2.1: dependencies: argparse: 2.0.1 @@ -10876,7 +10863,7 @@ snapshots: lru-cache@7.18.3: {} - lucide-react@1.22.0(react@19.2.7): + lucide-react@1.23.0(react@19.2.7): dependencies: react: 19.2.7 @@ -11405,15 +11392,15 @@ snapshots: mkdirp@1.0.4: {} - motion-dom@12.42.0: + motion-dom@12.42.2: dependencies: motion-utils: 12.39.0 motion-utils@12.39.0: {} - motion@12.42.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + motion@12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - framer-motion: 12.42.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + framer-motion: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tslib: 2.8.1 optionalDependencies: react: 19.2.7 @@ -12591,14 +12578,14 @@ snapshots: shell-quote@1.8.4: {} - shiki@4.3.0: + shiki@4.3.1: dependencies: - '@shikijs/core': 4.3.0 - '@shikijs/engine-javascript': 4.3.0 - '@shikijs/engine-oniguruma': 4.3.0 - '@shikijs/langs': 4.3.0 - '@shikijs/themes': 4.3.0 - '@shikijs/types': 4.3.0 + '@shikijs/core': 4.3.1 + '@shikijs/engine-javascript': 4.3.1 + '@shikijs/engine-oniguruma': 4.3.1 + '@shikijs/langs': 4.3.1 + '@shikijs/themes': 4.3.1 + '@shikijs/types': 4.3.1 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -12833,8 +12820,6 @@ snapshots: tagged-tag@1.0.0: {} - tailwind-merge@3.6.0: {} - tar-stream@2.2.0: dependencies: bl: 4.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 85f88b9b51..536d238908 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -22,7 +22,7 @@ catalog: "@swc/core": "^1.15.43" "@tsconfig/bun": "^1.0.10" "@types/bun": "^1.3.14" - "@typescript/native-preview": "7.0.0-dev.20260626.1" + "@typescript/native-preview": "7.0.0-dev.20260628.1" "@vitest/coverage-istanbul": "^4.1.9" "effect": "4.0.0-beta.93" "knip": "^6.17.1" @@ -30,7 +30,7 @@ catalog: "oxfmt": "^0.56.0" "oxlint": "^1.70.0" "oxlint-tsgolint": "^0.23.0" - "tldts": "^7.4.4" + "tldts": "^7.4.5" "vitest": "^4.1.9" blockExoticSubdeps: true From 28cc8449fc9d93a039b6a2fcdbcc3926143cd1a3 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Mon, 6 Jul 2026 10:34:31 +0100 Subject: [PATCH 36/48] fix(cli): wire --experimental gate into 7 ungated native leaves (#5766) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Current Behavior Go gates `postgres-config {get,update,delete}`, `ssl-enforcement {get,update}`, and `network-bans {get,remove}` behind the global `--experimental` flag in the root `PersistentPreRunE` (`apps/cli-go/cmd/root.go:56-93`). The TS port's `legacyRequireExperimental` helper (`shared/legacy-experimental-gate.ts`) was wired only into the storage leaves, so these 7 commands ran unconditionally, without ever requiring `--experimental`. Fixes #CLI-1854 ## Expected Behavior All 7 leaves now fail with `must set the --experimental flag to run this command` unless `--experimental` (or `SUPABASE_EXPERIMENTAL`) is set, matching Go exactly. Wiring this in required more than adding a `yield*` line: `legacyManagementApiRuntimeLayer` (used by all 7 leaves) eagerly resolves an access token as part of its own layer construction. Since `Command.provide(layer)` builds that layer *before* the handler body's first yield ever runs, leaving the gate check inside the handler while the layer stayed on `Command.provide` would let a missing-token error mask the missing-`--experimental` error — verified against the built binary before the fix (`postgres-config get` with no credentials and no `--experimental` showed `LegacyPlatformAuthRequiredError`, not the gate error). Each leaf now moves `legacyManagementApiRuntimeLayer` to an inline `Effect.provide` applied *after* the gate check, matching Go's actual `PersistentPreRunE` order (experimental check, then the `IsManagementAPI` login check — `root.go:91-109`). Re-verified against the built binary post-fix for all 7 leaves. Also corrects the `legacy-experimental-gate.ts` doc comment, which previously had cobra's `PersistentPreRunE`/`ValidateFlagGroups` ordering backwards, and now accurately notes that the 4 pre-existing storage leaves still have the mutex-check-before-gate ordering bug (tracked separately) rather than presenting their ordering as the correct example to copy. --- .../commands/network-bans/get/SIDE_EFFECTS.md | 41 +++--- .../commands/network-bans/get/get.command.ts | 31 +++- ...bans.experimental-gate.integration.test.ts | 121 ++++++++++++++++ .../network-bans/remove/SIDE_EFFECTS.md | 41 +++--- .../network-bans/remove/remove.command.ts | 31 +++- .../postgres-config/delete/SIDE_EFFECTS.md | 47 ++++--- .../postgres-config/delete/delete.command.ts | 31 +++- .../postgres-config/get/SIDE_EFFECTS.md | 41 +++--- .../postgres-config/get/get.command.ts | 31 +++- ...nfig.experimental-gate.integration.test.ts | 132 ++++++++++++++++++ .../postgres-config/update/SIDE_EFFECTS.md | 49 ++++--- .../postgres-config/update/update.command.ts | 31 +++- .../ssl-enforcement/get/SIDE_EFFECTS.md | 40 +++--- .../ssl-enforcement/get/get.command.ts | 31 +++- ...ment.experimental-gate.integration.test.ts | 124 ++++++++++++++++ .../ssl-enforcement/update/SIDE_EFFECTS.md | 45 +++--- .../ssl-enforcement/update/update.command.ts | 31 +++- .../legacy/shared/legacy-experimental-gate.ts | 18 ++- .../legacy-command-instrumentation.ts | 13 +- 19 files changed, 753 insertions(+), 176 deletions(-) create mode 100644 apps/cli/src/legacy/commands/network-bans/network-bans.experimental-gate.integration.test.ts create mode 100644 apps/cli/src/legacy/commands/postgres-config/postgres-config.experimental-gate.integration.test.ts create mode 100644 apps/cli/src/legacy/commands/ssl-enforcement/ssl-enforcement.experimental-gate.integration.test.ts diff --git a/apps/cli/src/legacy/commands/network-bans/get/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/network-bans/get/SIDE_EFFECTS.md index fdb5dbaf68..f9042c22a7 100644 --- a/apps/cli/src/legacy/commands/network-bans/get/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/network-bans/get/SIDE_EFFECTS.md @@ -9,10 +9,10 @@ ## Files Written -| 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 | +| Path | Format | When | +| ------------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | once the `--experimental` gate is open, after ref resolution, via `Effect.ensuring` — on success and failure | +| `~/.supabase/telemetry.json` | JSON | once the `--experimental` gate is open, via `Effect.ensuring` — on success and failure. Not written if the gate is closed. | ## API Routes @@ -24,21 +24,23 @@ The Management API exposes this read operation as `POST .../network-bans/retriev ## 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`) | -| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` → prompt) | +| 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) | +| `SUPABASE_EXPERIMENTAL` | enables `--experimental`-gated commands without the flag | no (pass `--experimental` instead; one of the two is required) | ## Exit Codes -| Code | Condition | -| ---- | --------------------------------------------------------------------------------------- | -| `0` | success — network bans printed to stdout | -| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | -| `1` | API non-2xx (`LegacyNetworkBansGetUnexpectedStatusError`) | -| `1` | transport failure (`LegacyNetworkBansGetNetworkError`) | -| `1` | `--output env` requested (`LegacyNetworkBansEnvNotSupportedError`) | +| Code | Condition | +| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `0` | success — network bans printed to stdout | +| `1` | `--experimental` not passed and `SUPABASE_EXPERIMENTAL` unset (`LegacyExperimentalRequiredError`) — checked before ref resolution/API/telemetry | +| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | +| `1` | API non-2xx (`LegacyNetworkBansGetUnexpectedStatusError`) | +| `1` | transport failure (`LegacyNetworkBansGetNetworkError`) | +| `1` | `--output env` requested (`LegacyNetworkBansEnvNotSupportedError`) | ## Telemetry Events Fired @@ -80,5 +82,10 @@ One `result` event whose `data` is the full response object. - The Go `--output` flag wins over the TS `--output-format` flag when both are provided. - `linked-project.json` is written **after** the project ref is resolved, regardless of whether the subsequent API call succeeds (mirrors Go's `PersistentPostRun`). -- `telemetry.json` is written on every invocation, including failures. +- `telemetry.json` is written on every invocation, including failures, but only once the `--experimental` gate is open. - Network bans are temporary blocks on IPs with abusive traffic patterns (e.g. multiple failed auth attempts). +- `network-bans` is an experimental command (Go `root.go:63`, `bansCmd`): `get` requires + `--experimental` (or `SUPABASE_EXPERIMENTAL`), matching Go's root-level `PersistentPreRunE` + gate (`root.go:91-96`), which runs before the `IsManagementAPI` login check (`root.go:105-109`). + A closed gate exits 1 before project-ref resolution, the API call, the `linked-project.json` + write, the `telemetry.json` write, and the `cli_command_executed` event. diff --git a/apps/cli/src/legacy/commands/network-bans/get/get.command.ts b/apps/cli/src/legacy/commands/network-bans/get/get.command.ts index 0789bcf660..33333bbda8 100644 --- a/apps/cli/src/legacy/commands/network-bans/get/get.command.ts +++ b/apps/cli/src/legacy/commands/network-bans/get/get.command.ts @@ -1,9 +1,15 @@ +import { Effect } 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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts"; +import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts"; import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; -import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; +import { + legacyValidateOutputFormat, + withLegacyCommandInstrumentation, +} from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyNetworkBansGet } from "./get.handler.ts"; const config = { @@ -19,10 +25,23 @@ export const legacyNetworkBansGetCommand = Command.make("get", config).pipe( Command.withDescription("Get the current network bans."), Command.withShortDescription("Get the current network bans"), Command.withHandler((flags) => - legacyNetworkBansGet(flags).pipe( - withLegacyCommandInstrumentation({ flags }), - withJsonErrorHandling, - ), + Effect.gen(function* () { + // Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`) + // — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an + // invalid `-o` value must win over a missing `--experimental` flag. + yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS); + // Go gates `bansCmd` (network-bans) behind `--experimental` in PersistentPreRunE + // (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109). + // `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part + // of building its `LegacyPlatformApi` layer, so it must be provided AFTER + // the gate (inline here) rather than via `Command.provide` on the whole + // command — `Command.provide` would build the layer, and fail on a missing + // token, before this generator's first `yield*` ever runs. + yield* legacyRequireExperimental; + return yield* legacyNetworkBansGet(flags).pipe( + withLegacyCommandInstrumentation({ flags }), + Effect.provide(legacyManagementApiRuntimeLayer(["network-bans", "get"])), + ); + }).pipe(withJsonErrorHandling), ), - Command.provide(legacyManagementApiRuntimeLayer(["network-bans", "get"])), ); diff --git a/apps/cli/src/legacy/commands/network-bans/network-bans.experimental-gate.integration.test.ts b/apps/cli/src/legacy/commands/network-bans/network-bans.experimental-gate.integration.test.ts new file mode 100644 index 0000000000..867514f1df --- /dev/null +++ b/apps/cli/src/legacy/commands/network-bans/network-bans.experimental-gate.integration.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Exit, Layer } from "effect"; +import { CliOutput, Command } from "effect/unstable/cli"; + +import { textCliOutputFormatter } from "../../../shared/output/text-formatter.ts"; +import { LEGACY_GLOBAL_FLAGS } from "../../../shared/legacy/global-flags.ts"; +import { TelemetryRuntime } from "../../../shared/telemetry/runtime.service.ts"; +import { makeTelemetryIdentity } from "../../../shared/telemetry/identity.ts"; +import { mockOutput, mockRuntimeInfo, processEnvLayer } from "../../../../tests/helpers/mocks.ts"; +import { + buildLegacyTestRuntime, + mockLegacyCliConfig, + mockLegacyPlatformApi, + useLegacyTempWorkdir, +} from "../../../../tests/helpers/legacy-mocks.ts"; +import { legacyNetworkBansCommand } from "./network-bans.command.ts"; + +// See postgres-config.experimental-gate.integration.test.ts for the full +// rationale: this proves `--experimental` is wired into the actual +// `.command.ts` handler pipeline AND runs before +// `legacyManagementApiRuntimeLayer`'s eager access-token resolution +// (Go's `IsExperimental` check precedes `IsManagementAPI` in +// `apps/cli-go/cmd/root.go:91-109`). + +const tempRoot = useLegacyTempWorkdir("supabase-network-bans-experimental-int-"); + +const testRoot = Command.make("supabase").pipe( + Command.withGlobalFlags(LEGACY_GLOBAL_FLAGS), + Command.withSubcommands([legacyNetworkBansCommand]), +); + +function setup() { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + response: { status: 200, body: { banned_ipv4_addresses: [] } }, + }); + const runtime = buildLegacyTestRuntime({ + out, + api, + cliConfig: mockLegacyCliConfig({ workdir: tempRoot.current }), + // `RuntimeInfo` is ambient (not provided by `legacyManagementApiRuntimeLayer` + // itself), so the real `legacyCredentialsLayer` built inline inside the + // command for the "gate open" case resolves ITS `RuntimeInfo` from this + // layer. Point homeDir at this test's isolated tempRoot so the layer's + // file-based token fallback (`/.supabase/access-token`) can't pick + // up a stray token left at the shared default `/tmp/supabase-cli-test-home`. + runtimeInfo: mockRuntimeInfo({ homeDir: tempRoot.current }), + }); + const layer = Layer.mergeAll( + runtime, + CliOutput.layer(textCliOutputFormatter()), + // The "gate open" case reaches the real `legacyManagementApiRuntimeLayer` + // (provided inline inside the command, not by this test's mocked runtime), + // which reads credentials/env directly — an ambient SUPABASE_ACCESS_TOKEN, + // SUPABASE_EXPERIMENTAL, or OS keyring entry on the machine running the + // test would make these assertions non-deterministic. Wipe process.env + // down to just this and disable the keyring fallback. + processEnvLayer({ SUPABASE_NO_KEYRING: "1" }), + Layer.succeed( + TelemetryRuntime, + TelemetryRuntime.of({ + configDir: `${tempRoot.current}/.supabase`, + tracesDir: `${tempRoot.current}/.supabase/traces`, + consent: "granted", + showDebug: false, + deviceId: "test-device-id", + sessionId: "test-session-id", + identity: makeTelemetryIdentity(undefined), + isFirstRun: false, + isTty: false, + isCi: false, + os: "linux", + arch: "x64", + cliVersion: "0.1.0", + }), + ), + ); + return { layer, api }; +} + +describe("legacy network-bans experimental gate (Go PersistentPreRunE parity)", () => { + const leaves: ReadonlyArray<{ readonly name: string; readonly args: ReadonlyArray }> = [ + { name: "get", args: ["network-bans", "get"] }, + { name: "remove", args: ["network-bans", "remove"] }, + ]; + + for (const { name, args } of leaves) { + it.live( + `${name} fails with LegacyExperimentalRequiredError when --experimental is unset`, + () => { + const { layer, api } = setup(); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + Command.runWith(testRoot, { version: "0.0.0-test" })(args), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + expect(JSON.stringify(exit.cause)).toContain("LegacyExperimentalRequiredError"); + } + expect(api.requests).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }, + ); + + it.live(`${name} does not fail with the gate error once --experimental is set`, () => { + const { layer, api } = setup(); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + Command.runWith(testRoot, { version: "0.0.0-test" })([...args, "--experimental"]), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const causeText = JSON.stringify(exit.cause); + expect(causeText).not.toContain("LegacyExperimentalRequiredError"); + expect(causeText).toContain("LegacyPlatformAuthRequiredError"); + } + expect(api.requests).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }); + } +}); diff --git a/apps/cli/src/legacy/commands/network-bans/remove/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/network-bans/remove/SIDE_EFFECTS.md index f1252d674b..333bf103a1 100644 --- a/apps/cli/src/legacy/commands/network-bans/remove/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/network-bans/remove/SIDE_EFFECTS.md @@ -9,10 +9,10 @@ ## Files Written -| 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 | +| Path | Format | When | +| ------------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | once the `--experimental` gate is open, after ref resolution, via `Effect.ensuring` — on success and failure | +| `~/.supabase/telemetry.json` | JSON | once the `--experimental` gate is open, via `Effect.ensuring` — on success and failure. Not written if the gate is closed. | ## API Routes @@ -24,21 +24,23 @@ ## 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`) | -| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` → prompt) | +| 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) | +| `SUPABASE_EXPERIMENTAL` | enables `--experimental`-gated commands without the flag | no (pass `--experimental` instead; one of the two is required) | ## Exit Codes -| Code | Condition | -| ---- | --------------------------------------------------------------------------------------- | -| `0` | success — network ban removed | -| `1` | invalid IP supplied via `--db-unban-ip` (`LegacyNetworkBansInvalidIpError`) | -| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | -| `1` | API non-2xx (`LegacyNetworkBansRemoveUnexpectedStatusError`) | -| `1` | transport failure (`LegacyNetworkBansRemoveNetworkError`) | +| Code | Condition | +| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `0` | success — network ban removed | +| `1` | `--experimental` not passed and `SUPABASE_EXPERIMENTAL` unset (`LegacyExperimentalRequiredError`) — checked before ref resolution/API/telemetry | +| `1` | invalid IP supplied via `--db-unban-ip` (`LegacyNetworkBansInvalidIpError`) | +| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | +| `1` | API non-2xx (`LegacyNetworkBansRemoveUnexpectedStatusError`) | +| `1` | transport failure (`LegacyNetworkBansRemoveNetworkError`) | ## Telemetry Events Fired @@ -78,4 +80,9 @@ One `result` event on success when the Go `--output` flag is unset. - Requires `--db-unban-ip` flag to specify IP(s) to unban (repeatable). When omitted, the caller's own IP is unbanned (`requester_ip: true`). - Requires `--project-ref` or a linked project (`.supabase/config.json`). - `linked-project.json` is written **after** the project ref is resolved, regardless of whether the subsequent API call succeeds (mirrors Go's `PersistentPostRun`). -- `telemetry.json` is written on every invocation, including failures. +- `telemetry.json` is written on every invocation, including failures, but only once the `--experimental` gate is open. +- `network-bans` is an experimental command (Go `root.go:63`, `bansCmd`): `remove` requires + `--experimental` (or `SUPABASE_EXPERIMENTAL`), matching Go's root-level `PersistentPreRunE` + gate (`root.go:91-96`), which runs before the `IsManagementAPI` login check (`root.go:105-109`). + A closed gate exits 1 before project-ref resolution, the API call, the `linked-project.json` + write, the `telemetry.json` write, and the `cli_command_executed` event. diff --git a/apps/cli/src/legacy/commands/network-bans/remove/remove.command.ts b/apps/cli/src/legacy/commands/network-bans/remove/remove.command.ts index dde28a344d..02beb81d15 100644 --- a/apps/cli/src/legacy/commands/network-bans/remove/remove.command.ts +++ b/apps/cli/src/legacy/commands/network-bans/remove/remove.command.ts @@ -1,9 +1,15 @@ +import { Effect } 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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts"; +import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts"; import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; -import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; +import { + legacyValidateOutputFormat, + withLegacyCommandInstrumentation, +} from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyNetworkBansRemove } from "./remove.handler.ts"; const config = { @@ -23,10 +29,23 @@ export const legacyNetworkBansRemoveCommand = Command.make("remove", config).pip Command.withDescription("Remove a network ban."), Command.withShortDescription("Remove a network ban"), Command.withHandler((flags) => - legacyNetworkBansRemove(flags).pipe( - withLegacyCommandInstrumentation({ flags }), - withJsonErrorHandling, - ), + Effect.gen(function* () { + // Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`) + // — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an + // invalid `-o` value must win over a missing `--experimental` flag. + yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS); + // Go gates `bansCmd` (network-bans) behind `--experimental` in PersistentPreRunE + // (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109). + // `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part + // of building its `LegacyPlatformApi` layer, so it must be provided AFTER + // the gate (inline here) rather than via `Command.provide` on the whole + // command — `Command.provide` would build the layer, and fail on a missing + // token, before this generator's first `yield*` ever runs. + yield* legacyRequireExperimental; + return yield* legacyNetworkBansRemove(flags).pipe( + withLegacyCommandInstrumentation({ flags }), + Effect.provide(legacyManagementApiRuntimeLayer(["network-bans", "remove"])), + ); + }).pipe(withJsonErrorHandling), ), - Command.provide(legacyManagementApiRuntimeLayer(["network-bans", "remove"])), ); 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 892ae2b29c..69bd23294e 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 @@ -9,10 +9,10 @@ ## Files Written -| 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 | +| Path | Format | When | +| ------------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | once the `--experimental` gate is open, after ref resolution, via `Effect.ensuring` - on success and failure | +| `~/.supabase/telemetry.json` | JSON | once the `--experimental` gate is open, via `Effect.ensuring` - on success and failure. Not written if the gate is closed. | ## API Routes @@ -25,24 +25,26 @@ This command does not call a delete endpoint. It mirrors Go: fetch current confi ## 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`) | -| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` -> prompt) | +| 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) | +| `SUPABASE_EXPERIMENTAL` | enables `--experimental`-gated commands without the flag | no (pass `--experimental` instead; one of the two is required) | ## Exit Codes -| 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`) | +| Code | Condition | +| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `0` | success - Postgres config updated with the deleted keys removed | +| `1` | `--experimental` not passed and `SUPABASE_EXPERIMENTAL` unset (`LegacyExperimentalRequiredError`) - checked before ref resolution/API/telemetry | +| `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 @@ -98,4 +100,9 @@ One `result` event on success. - 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. +- `telemetry.json` is written on every invocation, including failures, but only once the `--experimental` gate is open. +- `postgres-config` is an experimental command (Go `root.go:63`): `delete` requires `--experimental` + (or `SUPABASE_EXPERIMENTAL`), matching Go's root-level `PersistentPreRunE` gate (`root.go:91-96`), + which runs before the `IsManagementAPI` login check (`root.go:105-109`). A closed gate exits 1 + before project-ref resolution, the API calls, the `linked-project.json` write, the + `telemetry.json` write, and the `cli_command_executed` event. 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 903558e9e9..c4dcbc169c 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,10 +1,16 @@ +import { Effect } 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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts"; +import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts"; import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; import { legacyParseStringSliceFlag } from "../../../shared/legacy-string-slice-flag.ts"; -import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; +import { + legacyValidateOutputFormat, + withLegacyCommandInstrumentation, +} from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyPostgresConfigDelete } from "./delete.handler.ts"; export const legacyPostgresConfigDeleteConfigFlag = Flag.string("config").pipe( @@ -33,10 +39,23 @@ export const legacyPostgresConfigDeleteCommand = Command.make("delete", config). Command.withDescription("Delete specific Postgres database config overrides."), Command.withShortDescription("Delete Postgres database config overrides"), Command.withHandler((flags) => - legacyPostgresConfigDelete(flags).pipe( - withLegacyCommandInstrumentation({ flags }), - withJsonErrorHandling, - ), + Effect.gen(function* () { + // Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`) + // — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an + // invalid `-o` value must win over a missing `--experimental` flag. + yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS); + // Go gates `postgresCmd` behind `--experimental` in PersistentPreRunE + // (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109). + // `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part + // of building its `LegacyPlatformApi` layer, so it must be provided AFTER + // the gate (inline here) rather than via `Command.provide` on the whole + // command — `Command.provide` would build the layer, and fail on a missing + // token, before this generator's first `yield*` ever runs. + yield* legacyRequireExperimental; + return yield* legacyPostgresConfigDelete(flags).pipe( + withLegacyCommandInstrumentation({ flags }), + Effect.provide(legacyManagementApiRuntimeLayer(["postgres-config", "delete"])), + ); + }).pipe(withJsonErrorHandling), ), - Command.provide(legacyManagementApiRuntimeLayer(["postgres-config", "delete"])), ); 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 94e0953515..6582161bd1 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 @@ -9,10 +9,10 @@ ## Files Written -| 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 | +| Path | Format | When | +| ------------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | once the `--experimental` gate is open, after ref resolution, via `Effect.ensuring` - on success and failure | +| `~/.supabase/telemetry.json` | JSON | once the `--experimental` gate is open, via `Effect.ensuring` - on success and failure. Not written if the gate is closed. | ## API Routes @@ -22,21 +22,23 @@ ## 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`) | -| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` -> prompt) | +| 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) | +| `SUPABASE_EXPERIMENTAL` | enables `--experimental`-gated commands without the flag | no (pass `--experimental` instead; one of the two is required) | ## Exit Codes -| 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`) | +| Code | Condition | +| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `0` | success - Postgres config printed | +| `1` | `--experimental` not passed and `SUPABASE_EXPERIMENTAL` unset (`LegacyExperimentalRequiredError`) - checked before ref resolution/API/telemetry | +| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | +| `1` | API non-2xx (`LegacyPostgresConfigGetUnexpectedStatusError`) | +| `1` | transport failure (`LegacyPostgresConfigGetNetworkError`) | +| `1` | invalid JSON response (`LegacyPostgresConfigGetUnmarshalError`) | ## Telemetry Events Fired @@ -89,4 +91,9 @@ One `result` event on success. - 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. +- `telemetry.json` is written on every invocation, including failures, but only once the `--experimental` gate is open. +- `postgres-config` is an experimental command (Go `root.go:63`): `get` requires `--experimental` + (or `SUPABASE_EXPERIMENTAL`), matching Go's root-level `PersistentPreRunE` gate (`root.go:91-96`), + which runs before the `IsManagementAPI` login check (`root.go:105-109`). A closed gate exits 1 + before project-ref resolution, the API call, the `linked-project.json` write, the + `telemetry.json` write, and the `cli_command_executed` event. 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 845f0641bb..3606f50e2a 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,9 +1,15 @@ +import { Effect } 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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts"; +import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts"; import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; -import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; +import { + legacyValidateOutputFormat, + withLegacyCommandInstrumentation, +} from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyPostgresConfigGet } from "./get.handler.ts"; const config = { @@ -19,10 +25,23 @@ export const legacyPostgresConfigGetCommand = Command.make("get", config).pipe( Command.withDescription("Get the current Postgres database config overrides."), Command.withShortDescription("Get Postgres database config"), Command.withHandler((flags) => - legacyPostgresConfigGet(flags).pipe( - withLegacyCommandInstrumentation({ flags }), - withJsonErrorHandling, - ), + Effect.gen(function* () { + // Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`) + // — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an + // invalid `-o` value must win over a missing `--experimental` flag. + yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS); + // Go gates `postgresCmd` behind `--experimental` in PersistentPreRunE + // (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109). + // `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part + // of building its `LegacyPlatformApi` layer, so it must be provided AFTER + // the gate (inline here) rather than via `Command.provide` on the whole + // command — `Command.provide` would build the layer, and fail on a missing + // token, before this generator's first `yield*` ever runs. + yield* legacyRequireExperimental; + return yield* legacyPostgresConfigGet(flags).pipe( + withLegacyCommandInstrumentation({ flags }), + Effect.provide(legacyManagementApiRuntimeLayer(["postgres-config", "get"])), + ); + }).pipe(withJsonErrorHandling), ), - Command.provide(legacyManagementApiRuntimeLayer(["postgres-config", "get"])), ); diff --git a/apps/cli/src/legacy/commands/postgres-config/postgres-config.experimental-gate.integration.test.ts b/apps/cli/src/legacy/commands/postgres-config/postgres-config.experimental-gate.integration.test.ts new file mode 100644 index 0000000000..3b9c42e048 --- /dev/null +++ b/apps/cli/src/legacy/commands/postgres-config/postgres-config.experimental-gate.integration.test.ts @@ -0,0 +1,132 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Exit, Layer } from "effect"; +import { CliOutput, Command } from "effect/unstable/cli"; + +import { textCliOutputFormatter } from "../../../shared/output/text-formatter.ts"; +import { LEGACY_GLOBAL_FLAGS } from "../../../shared/legacy/global-flags.ts"; +import { TelemetryRuntime } from "../../../shared/telemetry/runtime.service.ts"; +import { makeTelemetryIdentity } from "../../../shared/telemetry/identity.ts"; +import { mockOutput, mockRuntimeInfo, processEnvLayer } from "../../../../tests/helpers/mocks.ts"; +import { + buildLegacyTestRuntime, + mockLegacyCliConfig, + mockLegacyPlatformApi, + useLegacyTempWorkdir, +} from "../../../../tests/helpers/legacy-mocks.ts"; +import { legacyPostgresConfigCommand } from "./postgres-config.command.ts"; + +// This suite proves the `--experimental` gate is wired into the actual +// `.command.ts` handler pipeline (not just the shared helper in isolation), +// and — critically — that it runs BEFORE `legacyManagementApiRuntimeLayer` +// resolves an access token. Go's root `PersistentPreRunE` checks +// `IsExperimental` before the `IsManagementAPI` login check +// (`apps/cli-go/cmd/root.go:91-109`); `legacyManagementApiRuntimeLayer` +// eagerly fails on a missing token as part of its own layer construction, so +// wiring the gate anywhere except immediately before that layer is attached +// would let a missing-token error mask the missing-`--experimental` error. + +const tempRoot = useLegacyTempWorkdir("supabase-postgres-config-experimental-int-"); + +const testRoot = Command.make("supabase").pipe( + Command.withGlobalFlags(LEGACY_GLOBAL_FLAGS), + Command.withSubcommands([legacyPostgresConfigCommand]), +); + +function setup() { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + response: { status: 200, body: { max_connections: 100 } }, + }); + const runtime = buildLegacyTestRuntime({ + out, + api, + cliConfig: mockLegacyCliConfig({ workdir: tempRoot.current }), + // `RuntimeInfo` is ambient (not provided by `legacyManagementApiRuntimeLayer` + // itself), so the real `legacyCredentialsLayer` built inline inside the + // command for the "gate open" case resolves ITS `RuntimeInfo` from this + // layer. Point homeDir at this test's isolated tempRoot so the layer's + // file-based token fallback (`/.supabase/access-token`) can't pick + // up a stray token left at the shared default `/tmp/supabase-cli-test-home`. + runtimeInfo: mockRuntimeInfo({ homeDir: tempRoot.current }), + }); + const layer = Layer.mergeAll( + runtime, + CliOutput.layer(textCliOutputFormatter()), + // The "gate open" case reaches the real `legacyManagementApiRuntimeLayer` + // (provided inline inside the command, not by this test's mocked runtime), + // which reads credentials/env directly — an ambient SUPABASE_ACCESS_TOKEN, + // SUPABASE_EXPERIMENTAL, or OS keyring entry on the machine running the + // test would make these assertions non-deterministic. Wipe process.env + // down to just this and disable the keyring fallback. + processEnvLayer({ SUPABASE_NO_KEYRING: "1" }), + Layer.succeed( + TelemetryRuntime, + TelemetryRuntime.of({ + configDir: `${tempRoot.current}/.supabase`, + tracesDir: `${tempRoot.current}/.supabase/traces`, + consent: "granted", + showDebug: false, + deviceId: "test-device-id", + sessionId: "test-session-id", + identity: makeTelemetryIdentity(undefined), + isFirstRun: false, + isTty: false, + isCi: false, + os: "linux", + arch: "x64", + cliVersion: "0.1.0", + }), + ), + ); + return { layer, api }; +} + +describe("legacy postgres-config experimental gate (Go PersistentPreRunE parity)", () => { + const leaves: ReadonlyArray<{ readonly name: string; readonly args: ReadonlyArray }> = [ + { name: "get", args: ["postgres-config", "get"] }, + { name: "update", args: ["postgres-config", "update"] }, + { name: "delete", args: ["postgres-config", "delete"] }, + ]; + + for (const { name, args } of leaves) { + it.live( + `${name} fails with LegacyExperimentalRequiredError when --experimental is unset`, + () => { + const { layer, api } = setup(); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + Command.runWith(testRoot, { version: "0.0.0-test" })(args), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + expect(JSON.stringify(exit.cause)).toContain("LegacyExperimentalRequiredError"); + } + // The gate must run before any API call (and before the eager + // access-token resolution inside `legacyManagementApiRuntimeLayer`) — + // a closed gate makes zero network requests. + expect(api.requests).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }, + ); + + it.live(`${name} does not fail with the gate error once --experimental is set`, () => { + const { layer, api } = setup(); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + Command.runWith(testRoot, { version: "0.0.0-test" })([...args, "--experimental"]), + ); + // No real access token is configured in this test environment, so the + // command still fails — but past the gate, at the auth-resolution step + // that `legacyManagementApiRuntimeLayer` performs, never with the + // experimental gate error once the flag is on. + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const causeText = JSON.stringify(exit.cause); + expect(causeText).not.toContain("LegacyExperimentalRequiredError"); + expect(causeText).toContain("LegacyPlatformAuthRequiredError"); + } + expect(api.requests).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }); + } +}); 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 030a6a322d..edd0de4c4a 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 @@ -9,10 +9,10 @@ ## Files Written -| 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 | +| Path | Format | When | +| ------------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | once the `--experimental` gate is open, after ref resolution, via `Effect.ensuring` - on success and failure | +| `~/.supabase/telemetry.json` | JSON | once the `--experimental` gate is open, via `Effect.ensuring` - on success and failure. Not written if the gate is closed. | ## API Routes @@ -25,25 +25,27 @@ The initial `GET` is skipped when `--replace-existing-overrides` is set. Otherwi ## 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`) | -| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` -> prompt) | +| 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) | +| `SUPABASE_EXPERIMENTAL` | enables `--experimental`-gated commands without the flag | no (pass `--experimental` instead; one of the two is required) | ## Exit Codes -| 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`) | +| Code | Condition | +| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `0` | success - Postgres config updated | +| `1` | `--experimental` not passed and `SUPABASE_EXPERIMENTAL` unset (`LegacyExperimentalRequiredError`) - checked before ref resolution/API/telemetry | +| `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 @@ -100,4 +102,9 @@ One `result` event on success. - 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. +- `telemetry.json` is written on every invocation, including failures, but only once the `--experimental` gate is open. +- `postgres-config` is an experimental command (Go `root.go:63`): `update` requires `--experimental` + (or `SUPABASE_EXPERIMENTAL`), matching Go's root-level `PersistentPreRunE` gate (`root.go:91-96`), + which runs before the `IsManagementAPI` login check (`root.go:105-109`). A closed gate exits 1 + before project-ref resolution, the API calls, the `linked-project.json` write, the + `telemetry.json` write, and the `cli_command_executed` event. 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 1ab52d9021..2f4afb50fe 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,10 +1,16 @@ +import { Effect } 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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts"; +import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts"; import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; import { legacyParseStringSliceFlag } from "../../../shared/legacy-string-slice-flag.ts"; -import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; +import { + legacyValidateOutputFormat, + withLegacyCommandInstrumentation, +} from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacyPostgresConfigUpdate } from "./update.handler.ts"; export const legacyPostgresConfigUpdateConfigFlag = Flag.string("config").pipe( @@ -38,10 +44,23 @@ export const legacyPostgresConfigUpdateCommand = Command.make("update", config). Command.withDescription("Update Postgres database config."), Command.withShortDescription("Update Postgres database config"), Command.withHandler((flags) => - legacyPostgresConfigUpdate(flags).pipe( - withLegacyCommandInstrumentation({ flags }), - withJsonErrorHandling, - ), + Effect.gen(function* () { + // Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`) + // — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an + // invalid `-o` value must win over a missing `--experimental` flag. + yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS); + // Go gates `postgresCmd` behind `--experimental` in PersistentPreRunE + // (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109). + // `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part + // of building its `LegacyPlatformApi` layer, so it must be provided AFTER + // the gate (inline here) rather than via `Command.provide` on the whole + // command — `Command.provide` would build the layer, and fail on a missing + // token, before this generator's first `yield*` ever runs. + yield* legacyRequireExperimental; + return yield* legacyPostgresConfigUpdate(flags).pipe( + withLegacyCommandInstrumentation({ flags }), + Effect.provide(legacyManagementApiRuntimeLayer(["postgres-config", "update"])), + ); + }).pipe(withJsonErrorHandling), ), - Command.provide(legacyManagementApiRuntimeLayer(["postgres-config", "update"])), ); diff --git a/apps/cli/src/legacy/commands/ssl-enforcement/get/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/ssl-enforcement/get/SIDE_EFFECTS.md index d06a2d9671..ccf0e03d43 100644 --- a/apps/cli/src/legacy/commands/ssl-enforcement/get/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/ssl-enforcement/get/SIDE_EFFECTS.md @@ -9,10 +9,10 @@ ## Files Written -| 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 | +| Path | Format | When | +| ------------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | once the `--experimental` gate is open, after ref resolution, via `Effect.ensuring` — on success and failure | +| `~/.supabase/telemetry.json` | JSON | once the `--experimental` gate is open, via `Effect.ensuring` — on success and failure. Not written if the gate is closed. | ## API Routes @@ -22,20 +22,22 @@ ## 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`) | -| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` → prompt) | +| 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) | +| `SUPABASE_EXPERIMENTAL` | enables `--experimental`-gated commands without the flag | no (pass `--experimental` instead; one of the two is required) | ## Exit Codes -| Code | Condition | -| ---- | --------------------------------------------------------------------------------------- | -| `0` | success — SSL enforcement status printed to stdout | -| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | -| `1` | API non-200 (`LegacySslEnforcementGetUnexpectedStatusError`) | -| `1` | transport failure (`LegacySslEnforcementGetNetworkError`) | +| Code | Condition | +| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `0` | success — SSL enforcement status printed to stdout | +| `1` | `--experimental` not passed and `SUPABASE_EXPERIMENTAL` unset (`LegacyExperimentalRequiredError`) — checked before ref resolution/API/telemetry | +| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | +| `1` | API non-200 (`LegacySslEnforcementGetUnexpectedStatusError`) | +| `1` | transport failure (`LegacySslEnforcementGetNetworkError`) | ## Telemetry Events Fired @@ -99,4 +101,10 @@ One `result` event: - The Go `--output` flag wins over the TS `--output-format` flag when both are provided. - `linked-project.json` is written **after** the project ref is resolved, regardless of whether the subsequent API call succeeds (mirrors Go's `PersistentPostRun`). -- `telemetry.json` is written on every invocation, including failures. +- `telemetry.json` is written on every invocation, including failures, but only once the + `--experimental` gate is open. +- `ssl-enforcement` is an experimental command (Go `root.go:63`): `get` requires `--experimental` + (or `SUPABASE_EXPERIMENTAL`), matching Go's root-level `PersistentPreRunE` gate (`root.go:91-96`), + which runs before the `IsManagementAPI` login check (`root.go:105-109`). A closed gate exits 1 + before project-ref resolution, the API call, the `linked-project.json` write, the + `telemetry.json` write, and the `cli_command_executed` event. diff --git a/apps/cli/src/legacy/commands/ssl-enforcement/get/get.command.ts b/apps/cli/src/legacy/commands/ssl-enforcement/get/get.command.ts index 2b402215de..cafd98327f 100644 --- a/apps/cli/src/legacy/commands/ssl-enforcement/get/get.command.ts +++ b/apps/cli/src/legacy/commands/ssl-enforcement/get/get.command.ts @@ -1,9 +1,15 @@ +import { Effect } 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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts"; +import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts"; import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; -import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; +import { + legacyValidateOutputFormat, + withLegacyCommandInstrumentation, +} from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacySslEnforcementGet } from "./get.handler.ts"; const config = { @@ -19,10 +25,23 @@ export const legacySslEnforcementGetCommand = Command.make("get", config).pipe( Command.withDescription("Get the current SSL enforcement configuration."), Command.withShortDescription("Get SSL enforcement configuration"), Command.withHandler((flags) => - legacySslEnforcementGet(flags).pipe( - withLegacyCommandInstrumentation({ flags }), - withJsonErrorHandling, - ), + Effect.gen(function* () { + // Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`) + // — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an + // invalid `-o` value must win over a missing `--experimental` flag. + yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS); + // Go gates `sslEnforcementCmd` behind `--experimental` in PersistentPreRunE + // (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109). + // `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part + // of building its `LegacyPlatformApi` layer, so it must be provided AFTER + // the gate (inline here) rather than via `Command.provide` on the whole + // command — `Command.provide` would build the layer, and fail on a missing + // token, before this generator's first `yield*` ever runs. + yield* legacyRequireExperimental; + return yield* legacySslEnforcementGet(flags).pipe( + withLegacyCommandInstrumentation({ flags }), + Effect.provide(legacyManagementApiRuntimeLayer(["ssl-enforcement", "get"])), + ); + }).pipe(withJsonErrorHandling), ), - Command.provide(legacyManagementApiRuntimeLayer(["ssl-enforcement", "get"])), ); diff --git a/apps/cli/src/legacy/commands/ssl-enforcement/ssl-enforcement.experimental-gate.integration.test.ts b/apps/cli/src/legacy/commands/ssl-enforcement/ssl-enforcement.experimental-gate.integration.test.ts new file mode 100644 index 0000000000..68a32afe5d --- /dev/null +++ b/apps/cli/src/legacy/commands/ssl-enforcement/ssl-enforcement.experimental-gate.integration.test.ts @@ -0,0 +1,124 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Exit, Layer } from "effect"; +import { CliOutput, Command } from "effect/unstable/cli"; + +import { textCliOutputFormatter } from "../../../shared/output/text-formatter.ts"; +import { LEGACY_GLOBAL_FLAGS } from "../../../shared/legacy/global-flags.ts"; +import { TelemetryRuntime } from "../../../shared/telemetry/runtime.service.ts"; +import { makeTelemetryIdentity } from "../../../shared/telemetry/identity.ts"; +import { mockOutput, mockRuntimeInfo, processEnvLayer } from "../../../../tests/helpers/mocks.ts"; +import { + buildLegacyTestRuntime, + mockLegacyCliConfig, + mockLegacyPlatformApi, + useLegacyTempWorkdir, +} from "../../../../tests/helpers/legacy-mocks.ts"; +import { legacySslEnforcementCommand } from "./ssl-enforcement.command.ts"; + +// See postgres-config.experimental-gate.integration.test.ts for the full +// rationale: this proves `--experimental` is wired into the actual +// `.command.ts` handler pipeline AND runs before +// `legacyManagementApiRuntimeLayer`'s eager access-token resolution +// (Go's `IsExperimental` check precedes `IsManagementAPI` in +// `apps/cli-go/cmd/root.go:91-109`). + +const tempRoot = useLegacyTempWorkdir("supabase-ssl-enforcement-experimental-int-"); + +const testRoot = Command.make("supabase").pipe( + Command.withGlobalFlags(LEGACY_GLOBAL_FLAGS), + Command.withSubcommands([legacySslEnforcementCommand]), +); + +function setup() { + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ + response: { + status: 200, + body: { currentConfig: { database: true }, appliedSuccessfully: true }, + }, + }); + const runtime = buildLegacyTestRuntime({ + out, + api, + cliConfig: mockLegacyCliConfig({ workdir: tempRoot.current }), + // `RuntimeInfo` is ambient (not provided by `legacyManagementApiRuntimeLayer` + // itself), so the real `legacyCredentialsLayer` built inline inside the + // command for the "gate open" case resolves ITS `RuntimeInfo` from this + // layer. Point homeDir at this test's isolated tempRoot so the layer's + // file-based token fallback (`/.supabase/access-token`) can't pick + // up a stray token left at the shared default `/tmp/supabase-cli-test-home`. + runtimeInfo: mockRuntimeInfo({ homeDir: tempRoot.current }), + }); + const layer = Layer.mergeAll( + runtime, + CliOutput.layer(textCliOutputFormatter()), + // The "gate open" case reaches the real `legacyManagementApiRuntimeLayer` + // (provided inline inside the command, not by this test's mocked runtime), + // which reads credentials/env directly — an ambient SUPABASE_ACCESS_TOKEN, + // SUPABASE_EXPERIMENTAL, or OS keyring entry on the machine running the + // test would make these assertions non-deterministic. Wipe process.env + // down to just this and disable the keyring fallback. + processEnvLayer({ SUPABASE_NO_KEYRING: "1" }), + Layer.succeed( + TelemetryRuntime, + TelemetryRuntime.of({ + configDir: `${tempRoot.current}/.supabase`, + tracesDir: `${tempRoot.current}/.supabase/traces`, + consent: "granted", + showDebug: false, + deviceId: "test-device-id", + sessionId: "test-session-id", + identity: makeTelemetryIdentity(undefined), + isFirstRun: false, + isTty: false, + isCi: false, + os: "linux", + arch: "x64", + cliVersion: "0.1.0", + }), + ), + ); + return { layer, api }; +} + +describe("legacy ssl-enforcement experimental gate (Go PersistentPreRunE parity)", () => { + const leaves: ReadonlyArray<{ readonly name: string; readonly args: ReadonlyArray }> = [ + { name: "get", args: ["ssl-enforcement", "get"] }, + { name: "update", args: ["ssl-enforcement", "update", "--enable-db-ssl-enforcement"] }, + ]; + + for (const { name, args } of leaves) { + it.live( + `${name} fails with LegacyExperimentalRequiredError when --experimental is unset`, + () => { + const { layer, api } = setup(); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + Command.runWith(testRoot, { version: "0.0.0-test" })(args), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + expect(JSON.stringify(exit.cause)).toContain("LegacyExperimentalRequiredError"); + } + expect(api.requests).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }, + ); + + it.live(`${name} does not fail with the gate error once --experimental is set`, () => { + const { layer, api } = setup(); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + Command.runWith(testRoot, { version: "0.0.0-test" })([...args, "--experimental"]), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const causeText = JSON.stringify(exit.cause); + expect(causeText).not.toContain("LegacyExperimentalRequiredError"); + expect(causeText).toContain("LegacyPlatformAuthRequiredError"); + } + expect(api.requests).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }); + } +}); diff --git a/apps/cli/src/legacy/commands/ssl-enforcement/update/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/ssl-enforcement/update/SIDE_EFFECTS.md index f6caab93da..6f067ad6b5 100644 --- a/apps/cli/src/legacy/commands/ssl-enforcement/update/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/ssl-enforcement/update/SIDE_EFFECTS.md @@ -9,10 +9,10 @@ ## Files Written -| Path | Format | When | -| ------------------------------------------------ | ------ | ----------------------------------------------------------------------------------------- | -| `~/.supabase//linked-project.json` | JSON | after the project ref is resolved (only if flag validation passes), via `Effect.ensuring` | -| `~/.supabase/telemetry.json` | JSON | always, via `Effect.ensuring` — including flag-validation failures | +| Path | Format | When | +| ------------------------------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------- | +| `~/.supabase//linked-project.json` | JSON | once the `--experimental` gate is open, after the project ref is resolved (only if flag validation passes), via `Effect.ensuring` | +| `~/.supabase/telemetry.json` | JSON | once the `--experimental` gate is open, via `Effect.ensuring` — including flag-validation failures. Not written if the gate is closed. | ## API Routes @@ -22,22 +22,24 @@ ## 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`) | -| `PROJECT_ID` | project ref fallback when `--project-ref` is unset | no (falls back to `supabase/.temp/project-ref` → prompt) | +| 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) | +| `SUPABASE_EXPERIMENTAL` | enables `--experimental`-gated commands without the flag | no (pass `--experimental` instead; one of the two is required) | ## Exit Codes -| Code | Condition | -| ---- | ----------------------------------------------------------------------------------------------------------------------------- | -| `0` | success — SSL enforcement status (post-update) printed to stdout | -| `1` | neither `--enable-db-ssl-enforcement` nor `--disable-db-ssl-enforcement` set (`LegacySslEnforcementNoEnableDisableFlagError`) | -| `1` | both `--enable-db-ssl-enforcement` and `--disable-db-ssl-enforcement` set (`LegacySslEnforcementMutuallyExclusiveFlagsError`) | -| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | -| `1` | API non-200 (`LegacySslEnforcementUpdateUnexpectedStatusError`) | -| `1` | transport failure (`LegacySslEnforcementUpdateNetworkError`) | +| Code | Condition | +| ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `0` | success — SSL enforcement status (post-update) printed to stdout | +| `1` | `--experimental` not passed and `SUPABASE_EXPERIMENTAL` unset (`LegacyExperimentalRequiredError`) — checked in `.command.ts`, before the handler (and its flag validation/telemetry) ever runs | +| `1` | neither `--enable-db-ssl-enforcement` nor `--disable-db-ssl-enforcement` set (`LegacySslEnforcementNoEnableDisableFlagError`) | +| `1` | both `--enable-db-ssl-enforcement` and `--disable-db-ssl-enforcement` set (`LegacySslEnforcementMutuallyExclusiveFlagsError`) | +| `1` | project ref unresolved (`LegacyProjectNotLinkedError` / `LegacyInvalidProjectRefError`) | +| `1` | API non-200 (`LegacySslEnforcementUpdateUnexpectedStatusError`) | +| `1` | transport failure (`LegacySslEnforcementUpdateNetworkError`) | ## Telemetry Events Fired @@ -96,5 +98,12 @@ One `result` event: `--disable-db-ssl-enforcement` is the user-facing way to send `database: false`. - `linked-project.json` is **not** written if flag validation fails (no ref is resolved). `telemetry.json` is written regardless, matching Go's - `PersistentPostRun` semantics. + `PersistentPostRun` semantics — but only once the `--experimental` gate is open, since + the flag-validation/telemetry-writing handler never runs at all when the gate is closed. - The Go `--output` flag wins over the TS `--output-format` flag when both are provided. +- `ssl-enforcement` is an experimental command (Go `root.go:63`): `update` requires + `--experimental` (or `SUPABASE_EXPERIMENTAL`), matching Go's root-level `PersistentPreRunE` + gate (`root.go:91-96`), which runs before the `IsManagementAPI` login check + (`root.go:105-109`). A closed gate exits 1 before the enable/disable mutex check, project-ref + resolution, the API call, the `linked-project.json` write, the `telemetry.json` write, and the + `cli_command_executed` event. diff --git a/apps/cli/src/legacy/commands/ssl-enforcement/update/update.command.ts b/apps/cli/src/legacy/commands/ssl-enforcement/update/update.command.ts index c7a907223c..c9bdff5164 100644 --- a/apps/cli/src/legacy/commands/ssl-enforcement/update/update.command.ts +++ b/apps/cli/src/legacy/commands/ssl-enforcement/update/update.command.ts @@ -1,9 +1,15 @@ +import { Effect } 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 { legacyRequireExperimental } from "../../../shared/legacy-experimental-gate.ts"; +import { LEGACY_RESOURCE_OUTPUT_FORMATS } from "../../../shared/legacy-go-output-flag.ts"; import { legacyManagementApiRuntimeLayer } from "../../../shared/legacy-management-api-runtime.layer.ts"; -import { withLegacyCommandInstrumentation } from "../../../telemetry/legacy-command-instrumentation.ts"; +import { + legacyValidateOutputFormat, + withLegacyCommandInstrumentation, +} from "../../../telemetry/legacy-command-instrumentation.ts"; import { legacySslEnforcementUpdate } from "./update.handler.ts"; const config = { @@ -29,10 +35,23 @@ export const legacySslEnforcementUpdateCommand = Command.make("update", config). Command.withDescription("Update SSL enforcement configuration."), Command.withShortDescription("Update SSL enforcement configuration"), Command.withHandler((flags) => - legacySslEnforcementUpdate(flags).pipe( - withLegacyCommandInstrumentation({ flags }), - withJsonErrorHandling, - ), + Effect.gen(function* () { + // Cobra parses flags — rejecting an out-of-enum `-o` (`internal/utils/enum.go:21-27`) + // — before `PersistentPreRunE` ever runs (`cobra@v1.10.2/command.go:919,985`), so an + // invalid `-o` value must win over a missing `--experimental` flag. + yield* legacyValidateOutputFormat(LEGACY_RESOURCE_OUTPUT_FORMATS); + // Go gates `sslEnforcementCmd` behind `--experimental` in PersistentPreRunE + // (root.go:91-96) BEFORE the `IsManagementAPI` login check (root.go:105-109). + // `legacyManagementApiRuntimeLayer` eagerly resolves an access token as part + // of building its `LegacyPlatformApi` layer, so it must be provided AFTER + // the gate (inline here) rather than via `Command.provide` on the whole + // command — `Command.provide` would build the layer, and fail on a missing + // token, before this generator's first `yield*` ever runs. + yield* legacyRequireExperimental; + return yield* legacySslEnforcementUpdate(flags).pipe( + withLegacyCommandInstrumentation({ flags }), + Effect.provide(legacyManagementApiRuntimeLayer(["ssl-enforcement", "update"])), + ); + }).pipe(withJsonErrorHandling), ), - Command.provide(legacyManagementApiRuntimeLayer(["ssl-enforcement", "update"])), ); diff --git a/apps/cli/src/legacy/shared/legacy-experimental-gate.ts b/apps/cli/src/legacy/shared/legacy-experimental-gate.ts index 9d2973fcef..e9b9289dcf 100644 --- a/apps/cli/src/legacy/shared/legacy-experimental-gate.ts +++ b/apps/cli/src/legacy/shared/legacy-experimental-gate.ts @@ -13,12 +13,18 @@ import { legacyResolveExperimental } from "../../shared/legacy/global-flags.ts"; * ``` * * `IsExperimental` is true for the commands registered in the `experimental` - * slice and their direct children (`root.go:56-74`), which includes `storageCmd`. - * Go enforces this in `PersistentPreRunE` — after cobra's arg + flag-group - * validation but before `RunE`/`PersistentPostRun` — so a closed gate must NOT - * emit `cli_command_executed` or write the telemetry/linked-project files. Each - * native experimental leaf therefore calls this in its `.command.ts` AFTER the - * mutual-exclusivity check and BEFORE `withLegacyCommandInstrumentation`. + * slice and their direct children (`root.go:56-74`). + * Go enforces this in `PersistentPreRunE`, which cobra runs BEFORE + * `ValidateFlagGroups()` (mutual-exclusivity checks) and `RunE`/`PersistentPostRun` + * (`cobra@v1.10.2/command.go:985,1010,1014`) — so a closed gate must NOT run + * mutual-exclusivity checks, emit `cli_command_executed`, or write the + * telemetry/linked-project files. (Cobra's positional-argument count/type + * validation, `ValidateArgs`, runs even earlier, at `command.go:968` — the gate + * does not preempt that.) Each native experimental leaf therefore calls this in + * its `.command.ts` before any mutual-exclusivity check and before + * `withLegacyCommandInstrumentation` — the 4 pre-existing storage leaves + * (`storage/{cp,ls,mv,rm}`) currently get this ordering wrong (mutex check + * before the gate); do not copy them until that's fixed. * * The message byte-matches Go's `errors.New(...)`; the value is resolved with the * `SUPABASE_EXPERIMENTAL` viper fallback (see {@link legacyResolveExperimental}). diff --git a/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.ts b/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.ts index 205749e172..04deca03cd 100644 --- a/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.ts +++ b/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.ts @@ -55,8 +55,17 @@ interface LegacyCommandInstrumentationOptions) => +export const legacyValidateOutputFormat = (allowed: ReadonlyArray) => Effect.gen(function* () { const flag = yield* Effect.serviceOption(LegacyOutputFlag); if (Option.isNone(flag) || Option.isNone(flag.value)) return; @@ -347,5 +356,5 @@ export function withLegacyCommandInstrumentation Date: Mon, 6 Jul 2026 10:34:38 +0100 Subject: [PATCH 37/48] fix(cli): resolve network-bans project ref before validating --db-unban-ip (#5769) ## Current Behavior `supabase network-bans remove --project-ref --db-unban-ip ` surfaces the invalid-IP error first, because the TS handler validated `--db-unban-ip` before resolving the project ref. ## Expected Behavior Go resolves the project ref in `PersistentPreRunE`, which cobra always runs before `RunE`'s IP validation (`cmd/root.go:108-114` vs `internal/bans/update/update.go:12-25`). So an invalid ref must surface before an invalid IP, matching Go. The handler now resolves the ref first, and the IP validation loop runs inside the same `Effect.ensuring(linkedProjectCache.cache(ref))` scope that Go's `ensureProjectGroupsCached` mirrors. Added regression tests covering both the unresolved-ref (no linked project) and invalid-ref (`--project-ref BADREF`) cases combined with an invalid `--db-unban-ip`, asserting the ref error wins and no API call is made. Fixes CLI-1856 --- .../network-bans/remove/remove.handler.ts | 15 +++--- .../remove/remove.integration.test.ts | 54 +++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/apps/cli/src/legacy/commands/network-bans/remove/remove.handler.ts b/apps/cli/src/legacy/commands/network-bans/remove/remove.handler.ts index 4825dcfc89..cf8a743dd7 100644 --- a/apps/cli/src/legacy/commands/network-bans/remove/remove.handler.ts +++ b/apps/cli/src/legacy/commands/network-bans/remove/remove.handler.ts @@ -33,15 +33,18 @@ export const legacyNetworkBansRemove = Effect.fn("legacy.network-bans.remove")(f const telemetryState = yield* LegacyTelemetryState; yield* Effect.gen(function* () { - for (const ip of flags.dbUnbanIp) { - if (isIP(ip) === 0) { - return yield* new LegacyNetworkBansInvalidIpError({ input: ip }); - } - } - + // Go resolves the project ref in `PersistentPreRunE` (`cmd/root.go:108-114`), + // before `RunE`'s `--db-unban-ip` validation (`internal/bans/update/update.go:12-25`) + // ever runs — so a bad ref must surface before a bad IP, not after. const ref = yield* resolver.resolve(flags.projectRef); yield* Effect.gen(function* () { + for (const ip of flags.dbUnbanIp) { + if (isIP(ip) === 0) { + return yield* new LegacyNetworkBansInvalidIpError({ input: ip }); + } + } + yield* api.v1 .deleteNetworkBans({ ref, diff --git a/apps/cli/src/legacy/commands/network-bans/remove/remove.integration.test.ts b/apps/cli/src/legacy/commands/network-bans/remove/remove.integration.test.ts index f3f426de20..47e9bf7185 100644 --- a/apps/cli/src/legacy/commands/network-bans/remove/remove.integration.test.ts +++ b/apps/cli/src/legacy/commands/network-bans/remove/remove.integration.test.ts @@ -165,6 +165,60 @@ describe("legacy network-bans remove integration", () => { }).pipe(Effect.provide(layer)); }); + it.live( + "surfaces the unresolved-ref error, not the invalid-IP error, when both are wrong", + () => { + // Go resolves the project ref in PersistentPreRunE, before RunE's IP + // validation ever runs (cmd/root.go:108-114 vs internal/bans/update/update.go:12-25), + // so a bad ref must win over a bad IP — this is the regression CLI-1856 guards. + const out = mockOutput({ format: "text" }); + const api = mockLegacyPlatformApi({ response: { status: 200, body: null } }); + const cliConfig = mockLegacyCliConfig({ + workdir: tempRoot.current, + projectId: Option.none(), + }); + const layer = buildLegacyTestRuntime({ out, api, cliConfig }); + + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyNetworkBansRemove({ + projectRef: Option.none(), + dbUnbanIp: ["12.3.4"], + }), + ); + expect(Exit.isFailure(exit)).toBe(true); + expect(api.requests).toHaveLength(0); + if (Exit.isFailure(exit)) { + const errJson = JSON.stringify(exit.cause); + expect(errJson).toContain("LegacyProjectNotLinkedError"); + expect(errJson).not.toContain("LegacyNetworkBansInvalidIpError"); + } + }).pipe(Effect.provide(layer)); + }, + ); + + it.live( + "surfaces the invalid-project-ref error, not the invalid-IP error, when both are wrong", + () => { + const { layer, api } = setup(); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + legacyNetworkBansRemove({ + projectRef: Option.some("BADREF"), + dbUnbanIp: ["12.3.4"], + }), + ); + expect(Exit.isFailure(exit)).toBe(true); + expect(api.requests).toHaveLength(0); + if (Exit.isFailure(exit)) { + const errJson = JSON.stringify(exit.cause); + expect(errJson).toContain("LegacyInvalidProjectRefError"); + expect(errJson).not.toContain("LegacyNetworkBansInvalidIpError"); + } + }).pipe(Effect.provide(layer)); + }, + ); + it.live("fails with LegacyNetworkBansRemoveUnexpectedStatusError on HTTP 503", () => { const { layer } = setup({ status: 503 }); return Effect.gen(function* () { From 4efa0df232268956dbbf5a86af0e2f5c145b24fa Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Mon, 6 Jul 2026 10:37:07 +0100 Subject: [PATCH 38/48] fix(cli): add --no-descriptions flag to completion subcommands (#5771) ## Current Behavior cobra auto-registers a `--no-descriptions` flag on the `bash`/`zsh`/`fish`/`powershell` completion subcommands whenever descriptions are enabled, which is the Go CLI's default. The TS proxy commands declared no flags at all, so `supabase completion zsh --no-descriptions` (and the other three shells) failed with an UnrecognizedOption error in TS before ever reaching the Go binary. ## Expected Behavior Each of the four completion leaves now declares `--no-descriptions` (name, default, and description matching cobra's `compCmdNoDescFlagName`/`compCmdNoDescFlagDefault`/`compCmdNoDescFlagDesc` constants exactly) and forwards it to the Go binary proxy. Since the flag is identical across all four leaves in the same command family, it's hoisted into `completion.flags.ts` rather than redeclared four times, following the same pattern already used for `--linked`/`--local` in `storage.flags.ts`. Fixes CLI-1858 --- apps/cli/docs/go-cli-porting-status.md | 14 ++++----- .../src/legacy/cli/complete-passthrough.ts | 12 +++++--- .../cli/complete-passthrough.unit.test.ts | 11 +++++++ .../commands/completion/SIDE_EFFECTS.md | 11 +++++-- .../commands/completion/bash/bash.command.ts | 5 +++- .../commands/completion/bash/bash.handler.ts | 6 ++-- .../completion/bash/bash.integration.test.ts | 27 ++++++++++++++++- .../completion/completion.e2e.test.ts | 25 ++++++++++++++++ .../commands/completion/completion.flags.ts | 17 +++++++++++ .../commands/completion/fish/fish.command.ts | 5 +++- .../commands/completion/fish/fish.handler.ts | 6 ++-- .../completion/fish/fish.integration.test.ts | 27 ++++++++++++++++- .../powershell/powershell.command.ts | 5 +++- .../powershell/powershell.handler.ts | 6 ++-- .../powershell/powershell.integration.test.ts | 29 ++++++++++++++++++- .../commands/completion/zsh/zsh.command.ts | 5 +++- .../commands/completion/zsh/zsh.handler.ts | 6 ++-- .../completion/zsh/zsh.integration.test.ts | 27 ++++++++++++++++- 18 files changed, 215 insertions(+), 29 deletions(-) create mode 100644 apps/cli/src/legacy/commands/completion/completion.e2e.test.ts create mode 100644 apps/cli/src/legacy/commands/completion/completion.flags.ts diff --git a/apps/cli/docs/go-cli-porting-status.md b/apps/cli/docs/go-cli-porting-status.md index e6138adacd..42d783057c 100644 --- a/apps/cli/docs/go-cli-porting-status.md +++ b/apps/cli/docs/go-cli-porting-status.md @@ -193,13 +193,13 @@ These route-first equivalents are intentionally lower-level than the old Go comm ## Additional Commands -| Old command | TS status | TS command path or `missing` | Missing flags/params | Extra TS flags/params | Notes | -| ----------------------- | --------- | -------------------------------- | --------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------ | -| `completion bash` | `ported` | `supabase completion bash` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). | -| `completion fish` | `ported` | `supabase completion fish` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). | -| `completion powershell` | `ported` | `supabase completion powershell` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). | -| `completion zsh` | `ported` | `supabase completion zsh` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). | -| `help` | `partial` | `supabase --help` | Go-style top-level `help` command shape | `-` | Feature parity exists via the framework-provided global `--help` flag instead of a dedicated `help` command. | +| Old command | TS status | TS command path or `missing` | Missing flags/params | Extra TS flags/params | Notes | +| ----------------------- | --------- | -------------------------------- | --------------------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `completion bash` | `ported` | `supabase completion bash` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). `--no-descriptions` added to match cobra's auto-registered flag (CLI-1858). | +| `completion fish` | `ported` | `supabase completion fish` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). `--no-descriptions` added to match cobra's auto-registered flag (CLI-1858). | +| `completion powershell` | `ported` | `supabase completion powershell` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). `--no-descriptions` added to match cobra's auto-registered flag (CLI-1858). | +| `completion zsh` | `ported` | `supabase completion zsh` | `-` | `-` | Proxies verbatim to the Go binary so the emitted script is byte-identical to Cobra's output (CLI-1532). `--no-descriptions` added to match cobra's auto-registered flag (CLI-1858). | +| `help` | `partial` | `supabase --help` | Go-style top-level `help` command shape | `-` | Feature parity exists via the framework-provided global `--help` flag instead of a dedicated `help` command. | ## Legacy Shell Wrapping Status diff --git a/apps/cli/src/legacy/cli/complete-passthrough.ts b/apps/cli/src/legacy/cli/complete-passthrough.ts index 59d4d27947..313868bc2c 100644 --- a/apps/cli/src/legacy/cli/complete-passthrough.ts +++ b/apps/cli/src/legacy/cli/complete-passthrough.ts @@ -16,9 +16,13 @@ export interface CompletePassthroughDeps { /** * Cobra-generated completion scripts (`supabase completion {bash,zsh,fish,powershell}`) - * call back into `supabase __complete ` on every tab press. The args may - * include partial flag tokens (e.g. `--de` while the user is mid-completion of a - * flag name) that Effect's structured parser would reject. Bypass Effect entirely + * call back into `supabase __complete ` on every tab press — or + * `supabase __completeNoDesc ` when the script was generated with + * `--no-descriptions` (`__completeNoDesc` is cobra's alias for the same hidden + * command, `ShellCompNoDescRequestCmd` in `spf13/cobra@v1.10.2/completions.go`, + * baked into the generated script at generation time). The args may include + * partial flag tokens (e.g. `--de` while the user is mid-completion of a flag + * name) that Effect's structured parser would reject. Bypass Effect entirely * for this code path and proxy the raw argv to the bundled Go binary, which is * the authority on completion behavior for the legacy shell. * @@ -26,7 +30,7 @@ export interface CompletePassthroughDeps { * otherwise. */ export function tryCompletePassthrough(deps: CompletePassthroughDeps): boolean { - if (deps.argv[0] !== "__complete") return false; + if (deps.argv[0] !== "__complete" && deps.argv[0] !== "__completeNoDesc") return false; const resolved = deps.resolveBinary(); if (!("found" in resolved)) { diff --git a/apps/cli/src/legacy/cli/complete-passthrough.unit.test.ts b/apps/cli/src/legacy/cli/complete-passthrough.unit.test.ts index 0e5f7c4801..09f578a801 100644 --- a/apps/cli/src/legacy/cli/complete-passthrough.unit.test.ts +++ b/apps/cli/src/legacy/cli/complete-passthrough.unit.test.ts @@ -68,6 +68,17 @@ describe("tryCompletePassthrough", () => { expect(exits).toEqual([0]); }); + it("forwards verbatim argv to the Go binary on __completeNoDesc (scripts generated with --no-descriptions)", () => { + const { deps, spawnCalls, exits } = makeDeps({ + argv: ["__completeNoDesc", "migration", "li"], + }); + expect(tryCompletePassthrough(deps)).toBe(true); + expect(spawnCalls).toEqual([ + { cmd: "/path/to/supabase-go", args: ["__completeNoDesc", "migration", "li"] }, + ]); + expect(exits).toEqual([0]); + }); + it("propagates the child's non-zero exit code", () => { const spawn = vi.fn(() => spawnResult(7)); const { deps, exits } = makeDeps({ spawn }); diff --git a/apps/cli/src/legacy/commands/completion/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/completion/SIDE_EFFECTS.md index 9246628751..f25912c9c5 100644 --- a/apps/cli/src/legacy/commands/completion/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/completion/SIDE_EFFECTS.md @@ -44,8 +44,11 @@ brew-managed `_supabase` files in their `fpath`, or analogous bash/fish/powershe artifacts. Drift would break tab completion for those users. The generated scripts call back to `supabase __complete ` on every tab press to -fetch dynamic completion candidates — see `apps/cli/src/legacy/commands/__complete/`, -which provides the matching hidden command. +fetch dynamic completion candidates, or `supabase __completeNoDesc ` when the +script was generated with `--no-descriptions` (cobra's alias for the same hidden +command) — see `apps/cli/src/legacy/cli/complete-passthrough.ts`, which intercepts +both `__complete` and `__completeNoDesc` before Effect's argv parser and proxies them +straight to the Go binary. ## Notes @@ -58,3 +61,7 @@ which provides the matching hidden command. - The Go CLI exits non-zero when called without a shell subcommand (e.g. `supabase completion`). Effect CLI surfaces the same condition through its usual "missing subcommand" help-with-exit-1 behavior. +- Each of `bash`/`zsh`/`fish`/`powershell` declares `--no-descriptions` (cobra's + auto-registered flag, `completions.go` in `spf13/cobra`) and forwards it to the + Go binary, so the emitted script omits completion descriptions exactly as it + would with the Go CLI. diff --git a/apps/cli/src/legacy/commands/completion/bash/bash.command.ts b/apps/cli/src/legacy/commands/completion/bash/bash.command.ts index 5e8c1d9a10..a2810b19b7 100644 --- a/apps/cli/src/legacy/commands/completion/bash/bash.command.ts +++ b/apps/cli/src/legacy/commands/completion/bash/bash.command.ts @@ -1,8 +1,11 @@ import { Command } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; +import { LegacyCompletionNoDescriptionsFlagDef } from "../completion.flags.ts"; import { legacyCompletionBash } from "./bash.handler.ts"; -const config = {}; +const config = { + noDescriptions: LegacyCompletionNoDescriptionsFlagDef, +} as const; export type LegacyCompletionBashFlags = CliCommand.Command.Config.Infer; export const legacyCompletionBashCommand = Command.make("bash", config).pipe( diff --git a/apps/cli/src/legacy/commands/completion/bash/bash.handler.ts b/apps/cli/src/legacy/commands/completion/bash/bash.handler.ts index dce9331b4c..9ff371dd44 100644 --- a/apps/cli/src/legacy/commands/completion/bash/bash.handler.ts +++ b/apps/cli/src/legacy/commands/completion/bash/bash.handler.ts @@ -3,8 +3,10 @@ import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; import type { LegacyCompletionBashFlags } from "./bash.command.ts"; export const legacyCompletionBash = Effect.fn("legacy.completion.bash")(function* ( - _flags: LegacyCompletionBashFlags, + flags: LegacyCompletionBashFlags, ) { const proxy = yield* LegacyGoProxy; - yield* proxy.exec(["completion", "bash"]); + const args: string[] = ["completion", "bash"]; + if (flags.noDescriptions) args.push("--no-descriptions"); + yield* proxy.exec(args); }); diff --git a/apps/cli/src/legacy/commands/completion/bash/bash.integration.test.ts b/apps/cli/src/legacy/commands/completion/bash/bash.integration.test.ts index 3506609072..3bb8f31137 100644 --- a/apps/cli/src/legacy/commands/completion/bash/bash.integration.test.ts +++ b/apps/cli/src/legacy/commands/completion/bash/bash.integration.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from "@effect/vitest"; import { Effect, Layer } from "effect"; +import { Command } from "effect/unstable/cli"; import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; +import { legacyCompletionBashCommand } from "./bash.command.ts"; import { legacyCompletionBash } from "./bash.handler.ts"; function setupLegacyCompletionBash() { @@ -15,12 +17,35 @@ function setupLegacyCompletionBash() { return { layer, calls }; } +function legacyTestRoot() { + return Command.make("supabase").pipe(Command.withSubcommands([legacyCompletionBashCommand])); +} + describe("legacy completion bash", () => { it.live("forwards `completion bash` to the Go binary", () => { const { layer, calls } = setupLegacyCompletionBash(); return Effect.gen(function* () { - yield* legacyCompletionBash({}); + yield* legacyCompletionBash({ noDescriptions: false }); expect(calls).toEqual([["completion", "bash"]]); }).pipe(Effect.provide(layer)); }); + + it.live("forwards --no-descriptions when set", () => { + const { layer, calls } = setupLegacyCompletionBash(); + return Effect.gen(function* () { + yield* legacyCompletionBash({ noDescriptions: true }); + expect(calls).toEqual([["completion", "bash", "--no-descriptions"]]); + }).pipe(Effect.provide(layer)); + }); + + it.live("accepts --no-descriptions from real argv via the command parser", () => { + const { layer, calls } = setupLegacyCompletionBash(); + return Effect.gen(function* () { + yield* Command.runWith(legacyTestRoot(), { version: "0.0.0-test" })([ + "bash", + "--no-descriptions", + ]); + expect(calls).toEqual([["completion", "bash", "--no-descriptions"]]); + }).pipe(Effect.provide(layer)) as Effect.Effect; + }); }); diff --git a/apps/cli/src/legacy/commands/completion/completion.e2e.test.ts b/apps/cli/src/legacy/commands/completion/completion.e2e.test.ts new file mode 100644 index 0000000000..56c721e5f3 --- /dev/null +++ b/apps/cli/src/legacy/commands/completion/completion.e2e.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, test } from "vitest"; +import { runSupabase } from "../../../../tests/helpers/cli.ts"; + +const E2E_TIMEOUT_MS = 30_000; + +describe("supabase completion (legacy)", () => { + // Golden-path e2e for CLI-1858: `--no-descriptions` used to be rejected by + // Effect's argv parser (`UnrecognizedOption`) before the request ever + // reached the Go binary, because the flag wasn't declared on the TS leaf + // command. Only a real subprocess run proves both halves of the fix: the + // TS parser accepts the flag, and the Go binary actually receives it — it + // switches the generated script's completion callback from `__complete` to + // `__completeNoDesc` only when the flag is forwarded. + test( + "bash --no-descriptions is accepted and forwarded to the Go binary", + { timeout: E2E_TIMEOUT_MS }, + async () => { + const { exitCode, stdout } = await runSupabase(["completion", "bash", "--no-descriptions"], { + entrypoint: "legacy", + }); + expect(exitCode).toBe(0); + expect(stdout).toContain("__completeNoDesc"); + }, + ); +}); diff --git a/apps/cli/src/legacy/commands/completion/completion.flags.ts b/apps/cli/src/legacy/commands/completion/completion.flags.ts new file mode 100644 index 0000000000..29e1193022 --- /dev/null +++ b/apps/cli/src/legacy/commands/completion/completion.flags.ts @@ -0,0 +1,17 @@ +import { Flag } from "effect/unstable/cli"; + +/** + * cobra auto-registers `--no-descriptions` on the bash/zsh/fish/powershell + * completion subcommands whenever descriptions are enabled (the default) — + * `compCmdNoDescFlagName`/`compCmdNoDescFlagDefault`/`compCmdNoDescFlagDesc` + * in `spf13/cobra@v1.10.2/completions.go:101-103`. Shared across all four + * leaves rather than redeclared per-file. + * + * `Flag.boolean` auto-derives a `--no-` negation, so this flag name + * produces `--no-no-descriptions` as a working (if odd) way to re-enable + * descriptions. Harmless — it resolves to the same `false` default — and + * not something cobra does, so there's no parity requirement to remove it. + */ +export const LegacyCompletionNoDescriptionsFlagDef = Flag.boolean("no-descriptions").pipe( + Flag.withDescription("disable completion descriptions"), +); diff --git a/apps/cli/src/legacy/commands/completion/fish/fish.command.ts b/apps/cli/src/legacy/commands/completion/fish/fish.command.ts index 2d22d4cd6e..33ef664d5f 100644 --- a/apps/cli/src/legacy/commands/completion/fish/fish.command.ts +++ b/apps/cli/src/legacy/commands/completion/fish/fish.command.ts @@ -1,8 +1,11 @@ import { Command } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; +import { LegacyCompletionNoDescriptionsFlagDef } from "../completion.flags.ts"; import { legacyCompletionFish } from "./fish.handler.ts"; -const config = {}; +const config = { + noDescriptions: LegacyCompletionNoDescriptionsFlagDef, +} as const; export type LegacyCompletionFishFlags = CliCommand.Command.Config.Infer; export const legacyCompletionFishCommand = Command.make("fish", config).pipe( diff --git a/apps/cli/src/legacy/commands/completion/fish/fish.handler.ts b/apps/cli/src/legacy/commands/completion/fish/fish.handler.ts index c293d2e3b4..0157d9cc6a 100644 --- a/apps/cli/src/legacy/commands/completion/fish/fish.handler.ts +++ b/apps/cli/src/legacy/commands/completion/fish/fish.handler.ts @@ -3,8 +3,10 @@ import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; import type { LegacyCompletionFishFlags } from "./fish.command.ts"; export const legacyCompletionFish = Effect.fn("legacy.completion.fish")(function* ( - _flags: LegacyCompletionFishFlags, + flags: LegacyCompletionFishFlags, ) { const proxy = yield* LegacyGoProxy; - yield* proxy.exec(["completion", "fish"]); + const args: string[] = ["completion", "fish"]; + if (flags.noDescriptions) args.push("--no-descriptions"); + yield* proxy.exec(args); }); diff --git a/apps/cli/src/legacy/commands/completion/fish/fish.integration.test.ts b/apps/cli/src/legacy/commands/completion/fish/fish.integration.test.ts index 2f441b4fbb..ba22430863 100644 --- a/apps/cli/src/legacy/commands/completion/fish/fish.integration.test.ts +++ b/apps/cli/src/legacy/commands/completion/fish/fish.integration.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from "@effect/vitest"; import { Effect, Layer } from "effect"; +import { Command } from "effect/unstable/cli"; import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; +import { legacyCompletionFishCommand } from "./fish.command.ts"; import { legacyCompletionFish } from "./fish.handler.ts"; function setupLegacyCompletionFish() { @@ -15,12 +17,35 @@ function setupLegacyCompletionFish() { return { layer, calls }; } +function legacyTestRoot() { + return Command.make("supabase").pipe(Command.withSubcommands([legacyCompletionFishCommand])); +} + describe("legacy completion fish", () => { it.live("forwards `completion fish` to the Go binary", () => { const { layer, calls } = setupLegacyCompletionFish(); return Effect.gen(function* () { - yield* legacyCompletionFish({}); + yield* legacyCompletionFish({ noDescriptions: false }); expect(calls).toEqual([["completion", "fish"]]); }).pipe(Effect.provide(layer)); }); + + it.live("forwards --no-descriptions when set", () => { + const { layer, calls } = setupLegacyCompletionFish(); + return Effect.gen(function* () { + yield* legacyCompletionFish({ noDescriptions: true }); + expect(calls).toEqual([["completion", "fish", "--no-descriptions"]]); + }).pipe(Effect.provide(layer)); + }); + + it.live("accepts --no-descriptions from real argv via the command parser", () => { + const { layer, calls } = setupLegacyCompletionFish(); + return Effect.gen(function* () { + yield* Command.runWith(legacyTestRoot(), { version: "0.0.0-test" })([ + "fish", + "--no-descriptions", + ]); + expect(calls).toEqual([["completion", "fish", "--no-descriptions"]]); + }).pipe(Effect.provide(layer)) as Effect.Effect; + }); }); diff --git a/apps/cli/src/legacy/commands/completion/powershell/powershell.command.ts b/apps/cli/src/legacy/commands/completion/powershell/powershell.command.ts index 2034dc3f80..72dd479f3f 100644 --- a/apps/cli/src/legacy/commands/completion/powershell/powershell.command.ts +++ b/apps/cli/src/legacy/commands/completion/powershell/powershell.command.ts @@ -1,8 +1,11 @@ import { Command } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; +import { LegacyCompletionNoDescriptionsFlagDef } from "../completion.flags.ts"; import { legacyCompletionPowershell } from "./powershell.handler.ts"; -const config = {}; +const config = { + noDescriptions: LegacyCompletionNoDescriptionsFlagDef, +} as const; export type LegacyCompletionPowershellFlags = CliCommand.Command.Config.Infer; export const legacyCompletionPowershellCommand = Command.make("powershell", config).pipe( diff --git a/apps/cli/src/legacy/commands/completion/powershell/powershell.handler.ts b/apps/cli/src/legacy/commands/completion/powershell/powershell.handler.ts index 9410dccccb..8b1d056431 100644 --- a/apps/cli/src/legacy/commands/completion/powershell/powershell.handler.ts +++ b/apps/cli/src/legacy/commands/completion/powershell/powershell.handler.ts @@ -3,8 +3,10 @@ import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; import type { LegacyCompletionPowershellFlags } from "./powershell.command.ts"; export const legacyCompletionPowershell = Effect.fn("legacy.completion.powershell")(function* ( - _flags: LegacyCompletionPowershellFlags, + flags: LegacyCompletionPowershellFlags, ) { const proxy = yield* LegacyGoProxy; - yield* proxy.exec(["completion", "powershell"]); + const args: string[] = ["completion", "powershell"]; + if (flags.noDescriptions) args.push("--no-descriptions"); + yield* proxy.exec(args); }); diff --git a/apps/cli/src/legacy/commands/completion/powershell/powershell.integration.test.ts b/apps/cli/src/legacy/commands/completion/powershell/powershell.integration.test.ts index 056c856dcb..da678deeb2 100644 --- a/apps/cli/src/legacy/commands/completion/powershell/powershell.integration.test.ts +++ b/apps/cli/src/legacy/commands/completion/powershell/powershell.integration.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from "@effect/vitest"; import { Effect, Layer } from "effect"; +import { Command } from "effect/unstable/cli"; import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; +import { legacyCompletionPowershellCommand } from "./powershell.command.ts"; import { legacyCompletionPowershell } from "./powershell.handler.ts"; function setupLegacyCompletionPowershell() { @@ -15,12 +17,37 @@ function setupLegacyCompletionPowershell() { return { layer, calls }; } +function legacyTestRoot() { + return Command.make("supabase").pipe( + Command.withSubcommands([legacyCompletionPowershellCommand]), + ); +} + describe("legacy completion powershell", () => { it.live("forwards `completion powershell` to the Go binary", () => { const { layer, calls } = setupLegacyCompletionPowershell(); return Effect.gen(function* () { - yield* legacyCompletionPowershell({}); + yield* legacyCompletionPowershell({ noDescriptions: false }); expect(calls).toEqual([["completion", "powershell"]]); }).pipe(Effect.provide(layer)); }); + + it.live("forwards --no-descriptions when set", () => { + const { layer, calls } = setupLegacyCompletionPowershell(); + return Effect.gen(function* () { + yield* legacyCompletionPowershell({ noDescriptions: true }); + expect(calls).toEqual([["completion", "powershell", "--no-descriptions"]]); + }).pipe(Effect.provide(layer)); + }); + + it.live("accepts --no-descriptions from real argv via the command parser", () => { + const { layer, calls } = setupLegacyCompletionPowershell(); + return Effect.gen(function* () { + yield* Command.runWith(legacyTestRoot(), { version: "0.0.0-test" })([ + "powershell", + "--no-descriptions", + ]); + expect(calls).toEqual([["completion", "powershell", "--no-descriptions"]]); + }).pipe(Effect.provide(layer)) as Effect.Effect; + }); }); diff --git a/apps/cli/src/legacy/commands/completion/zsh/zsh.command.ts b/apps/cli/src/legacy/commands/completion/zsh/zsh.command.ts index fdda3df9e1..8951d805f6 100644 --- a/apps/cli/src/legacy/commands/completion/zsh/zsh.command.ts +++ b/apps/cli/src/legacy/commands/completion/zsh/zsh.command.ts @@ -1,8 +1,11 @@ import { Command } from "effect/unstable/cli"; import type * as CliCommand from "effect/unstable/cli/Command"; +import { LegacyCompletionNoDescriptionsFlagDef } from "../completion.flags.ts"; import { legacyCompletionZsh } from "./zsh.handler.ts"; -const config = {}; +const config = { + noDescriptions: LegacyCompletionNoDescriptionsFlagDef, +} as const; export type LegacyCompletionZshFlags = CliCommand.Command.Config.Infer; export const legacyCompletionZshCommand = Command.make("zsh", config).pipe( diff --git a/apps/cli/src/legacy/commands/completion/zsh/zsh.handler.ts b/apps/cli/src/legacy/commands/completion/zsh/zsh.handler.ts index 10ca64ae3d..472f1918bc 100644 --- a/apps/cli/src/legacy/commands/completion/zsh/zsh.handler.ts +++ b/apps/cli/src/legacy/commands/completion/zsh/zsh.handler.ts @@ -3,8 +3,10 @@ import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; import type { LegacyCompletionZshFlags } from "./zsh.command.ts"; export const legacyCompletionZsh = Effect.fn("legacy.completion.zsh")(function* ( - _flags: LegacyCompletionZshFlags, + flags: LegacyCompletionZshFlags, ) { const proxy = yield* LegacyGoProxy; - yield* proxy.exec(["completion", "zsh"]); + const args: string[] = ["completion", "zsh"]; + if (flags.noDescriptions) args.push("--no-descriptions"); + yield* proxy.exec(args); }); diff --git a/apps/cli/src/legacy/commands/completion/zsh/zsh.integration.test.ts b/apps/cli/src/legacy/commands/completion/zsh/zsh.integration.test.ts index f15c1d9a41..9745c1a1e4 100644 --- a/apps/cli/src/legacy/commands/completion/zsh/zsh.integration.test.ts +++ b/apps/cli/src/legacy/commands/completion/zsh/zsh.integration.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from "@effect/vitest"; import { Effect, Layer } from "effect"; +import { Command } from "effect/unstable/cli"; import { LegacyGoProxy } from "../../../../shared/legacy/go-proxy.service.ts"; +import { legacyCompletionZshCommand } from "./zsh.command.ts"; import { legacyCompletionZsh } from "./zsh.handler.ts"; function setupLegacyCompletionZsh() { @@ -15,12 +17,35 @@ function setupLegacyCompletionZsh() { return { layer, calls }; } +function legacyTestRoot() { + return Command.make("supabase").pipe(Command.withSubcommands([legacyCompletionZshCommand])); +} + describe("legacy completion zsh", () => { it.live("forwards `completion zsh` to the Go binary", () => { const { layer, calls } = setupLegacyCompletionZsh(); return Effect.gen(function* () { - yield* legacyCompletionZsh({}); + yield* legacyCompletionZsh({ noDescriptions: false }); expect(calls).toEqual([["completion", "zsh"]]); }).pipe(Effect.provide(layer)); }); + + it.live("forwards --no-descriptions when set", () => { + const { layer, calls } = setupLegacyCompletionZsh(); + return Effect.gen(function* () { + yield* legacyCompletionZsh({ noDescriptions: true }); + expect(calls).toEqual([["completion", "zsh", "--no-descriptions"]]); + }).pipe(Effect.provide(layer)); + }); + + it.live("accepts --no-descriptions from real argv via the command parser", () => { + const { layer, calls } = setupLegacyCompletionZsh(); + return Effect.gen(function* () { + yield* Command.runWith(legacyTestRoot(), { version: "0.0.0-test" })([ + "zsh", + "--no-descriptions", + ]); + expect(calls).toEqual([["completion", "zsh", "--no-descriptions"]]); + }).pipe(Effect.provide(layer)) as Effect.Effect; + }); }); From f28efedf251513d8ba068a81bd2b2ae5cd909ee0 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Mon, 6 Jul 2026 11:05:15 +0100 Subject: [PATCH 39/48] fix(cli): run --experimental gate before storage's mutex check (#5768) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Current Behavior Go's cobra runs `PersistentPreRunE` (which includes the `--experimental` gate, `apps/cli-go/cmd/root.go:91-96`) before `ValidateFlagGroups()` (mutual-exclusivity checks, `cobra@v1.10.2/command.go:985,1010`). The TS port's `storage ls/cp/mv/rm` had this backwards — checking `--linked`/`--local` exclusivity before the `--experimental` gate — so `supabase storage ls --linked --local` (without `--experimental`) surfaced the mutex error in TS instead of Go's "must set the --experimental flag" error. Fixes CLI-1855 ## Expected Behavior All 4 storage leaves now run the experimental gate first, matching Go's precedence exactly (verified against the built binary). Also corrects the inline comments in those files and the shared `legacy-experimental-gate.ts` doc, which claimed Go gates experimental "after flag-group validation" — this was backwards per cobra's actual execution order. An existing e2e test (`storage.e2e.test.ts`) asserted the old (wrong) precedence for `--linked --local` without `--experimental`; fixed it to add `--experimental` so it keeps testing the mutex message it originally intended, at the real subprocess boundary. New ordering coverage for all 4 leaves lives in a new integration suite (`storage.experimental-gate.integration.test.ts`) rather than duplicating it in e2e, per this repo's e2e scope policy. Out of scope: the same gate-vs-mutex-check ordering bug also exists in `db schema declarative generate`/`sync` (verified against Go source), unrelated command family — filing as a separate follow-up rather than expanding this PR. --- .../legacy/commands/storage/cp/cp.command.ts | 6 +- .../legacy/commands/storage/ls/ls.command.ts | 9 +- .../legacy/commands/storage/mv/mv.command.ts | 6 +- .../legacy/commands/storage/rm/rm.command.ts | 6 +- .../commands/storage/storage.e2e.test.ts | 8 +- ...rage.experimental-gate.integration.test.ts | 121 ++++++++++++++++++ .../legacy/shared/legacy-experimental-gate.ts | 6 +- 7 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 apps/cli/src/legacy/commands/storage/storage.experimental-gate.integration.test.ts diff --git a/apps/cli/src/legacy/commands/storage/cp/cp.command.ts b/apps/cli/src/legacy/commands/storage/cp/cp.command.ts index 019c658cdf..d16c06060a 100644 --- a/apps/cli/src/legacy/commands/storage/cp/cp.command.ts +++ b/apps/cli/src/legacy/commands/storage/cp/cp.command.ts @@ -66,11 +66,11 @@ export const legacyStorageCpCommand = Command.make("cp", config).pipe( ]), Command.withHandler((flags) => Effect.gen(function* () { + // Gate before the mutex check below — order matters; see + // legacyRequireExperimental's doc comment for why. + yield* legacyRequireExperimental; const cliArgs = yield* CliArgs; yield* legacyAssertStorageTargetsExclusive(cliArgs.args); - // Go gates `storageCmd` behind `--experimental` in PersistentPreRunE - // (root.go:91-96), after flag-group validation and before RunE/PostRun. - yield* legacyRequireExperimental; const telemetryFlags = { recursive: flags.recursive, cacheControl: flags.cacheControl, diff --git a/apps/cli/src/legacy/commands/storage/ls/ls.command.ts b/apps/cli/src/legacy/commands/storage/ls/ls.command.ts index 4fe760a559..42abfa7da6 100644 --- a/apps/cli/src/legacy/commands/storage/ls/ls.command.ts +++ b/apps/cli/src/legacy/commands/storage/ls/ls.command.ts @@ -40,14 +40,11 @@ export const legacyStorageLsCommand = Command.make("ls", config).pipe( ]), Command.withHandler((flags) => Effect.gen(function* () { - // Enforce --linked/--local mutual exclusivity BEFORE instrumentation, so a - // flag-validation rejection doesn't emit `cli_command_executed` (Go rejects - // it at cobra flag validation, before RunE/PostRun). + // Gate before the mutex check below — order matters; see + // legacyRequireExperimental's doc comment for why. + yield* legacyRequireExperimental; const cliArgs = yield* CliArgs; yield* legacyAssertStorageTargetsExclusive(cliArgs.args); - // Go gates `storageCmd` behind `--experimental` in PersistentPreRunE - // (root.go:91-96), after flag-group validation and before RunE/PostRun. - yield* legacyRequireExperimental; const telemetryFlags = { recursive: flags.recursive, linked: flags.linked, diff --git a/apps/cli/src/legacy/commands/storage/mv/mv.command.ts b/apps/cli/src/legacy/commands/storage/mv/mv.command.ts index b6eca4193d..25fefced7d 100644 --- a/apps/cli/src/legacy/commands/storage/mv/mv.command.ts +++ b/apps/cli/src/legacy/commands/storage/mv/mv.command.ts @@ -38,11 +38,11 @@ export const legacyStorageMvCommand = Command.make("mv", config).pipe( ]), Command.withHandler((flags) => Effect.gen(function* () { + // Gate before the mutex check below — order matters; see + // legacyRequireExperimental's doc comment for why. + yield* legacyRequireExperimental; const cliArgs = yield* CliArgs; yield* legacyAssertStorageTargetsExclusive(cliArgs.args); - // Go gates `storageCmd` behind `--experimental` in PersistentPreRunE - // (root.go:91-96), after flag-group validation and before RunE/PostRun. - yield* legacyRequireExperimental; const telemetryFlags = { recursive: flags.recursive, linked: flags.linked, diff --git a/apps/cli/src/legacy/commands/storage/rm/rm.command.ts b/apps/cli/src/legacy/commands/storage/rm/rm.command.ts index af97d7f907..9b29eb1afb 100644 --- a/apps/cli/src/legacy/commands/storage/rm/rm.command.ts +++ b/apps/cli/src/legacy/commands/storage/rm/rm.command.ts @@ -42,11 +42,11 @@ export const legacyStorageRmCommand = Command.make("rm", config).pipe( ]), Command.withHandler((flags) => Effect.gen(function* () { + // Gate before the mutex check below — order matters; see + // legacyRequireExperimental's doc comment for why. + yield* legacyRequireExperimental; const cliArgs = yield* CliArgs; yield* legacyAssertStorageTargetsExclusive(cliArgs.args); - // Go gates `storageCmd` behind `--experimental` in PersistentPreRunE - // (root.go:91-96), after flag-group validation and before RunE/PostRun. - yield* legacyRequireExperimental; const telemetryFlags = { recursive: flags.recursive, linked: flags.linked, diff --git a/apps/cli/src/legacy/commands/storage/storage.e2e.test.ts b/apps/cli/src/legacy/commands/storage/storage.e2e.test.ts index 39fabd1937..6827ede843 100644 --- a/apps/cli/src/legacy/commands/storage/storage.e2e.test.ts +++ b/apps/cli/src/legacy/commands/storage/storage.e2e.test.ts @@ -39,10 +39,12 @@ describe("supabase storage (legacy)", () => { }); test("rejects passing both --local and --linked", { timeout: E2E_TIMEOUT_MS }, async () => { - // Go validates flag groups (mutual exclusivity) BEFORE the experimental gate - // in PersistentPreRunE, so this fails on the mutex even without --experimental. + // Go's PersistentPreRunE runs the experimental gate BEFORE cobra's + // ValidateFlagGroups() mutex check (cobra@v1.10.2/command.go:985,1010), so + // --experimental must be set here to reach the mutex check at all — + // otherwise the experimental-gate error wins (see the next test). const { exitCode, stdout, stderr } = await runSupabase( - ["storage", "ls", "--local", "--linked", "ss:///"], + ["storage", "ls", "--local", "--linked", "ss:///", "--experimental"], { entrypoint: "legacy", cwd: projectDir }, ); expect(exitCode).toBe(1); diff --git a/apps/cli/src/legacy/commands/storage/storage.experimental-gate.integration.test.ts b/apps/cli/src/legacy/commands/storage/storage.experimental-gate.integration.test.ts new file mode 100644 index 0000000000..4e455a5c76 --- /dev/null +++ b/apps/cli/src/legacy/commands/storage/storage.experimental-gate.integration.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, it } from "@effect/vitest"; +import { BunServices } from "@effect/platform-bun"; +import { Cause, Effect, Exit, Layer, Option } from "effect"; +import { CliOutput, Command } from "effect/unstable/cli"; + +import { CliArgs } from "../../../shared/cli/cli-args.service.ts"; +import { textCliOutputFormatter } from "../../../shared/output/text-formatter.ts"; +import { LEGACY_GLOBAL_FLAGS } from "../../../shared/legacy/global-flags.ts"; +import { + mockAnalytics, + mockOutput, + mockProcessControl, + mockRuntimeInfo, + mockTty, + processEnvLayer, +} from "../../../../tests/helpers/mocks.ts"; +import { makeTelemetryIdentity } from "../../../shared/telemetry/identity.ts"; +import { TelemetryRuntime } from "../../../shared/telemetry/runtime.service.ts"; +import { LegacyExperimentalRequiredError } from "../../shared/legacy-experimental-gate.ts"; +import { legacyStorageCommand } from "./storage.command.ts"; +import { LegacyStorageMutuallyExclusiveFlagsError } from "./storage.errors.ts"; + +// Go gates `storageCmd` behind `--experimental` in `PersistentPreRunE` +// (`apps/cli-go/cmd/root.go:91-96`), which cobra runs BEFORE +// `ValidateFlagGroups()` (mutual-exclusivity checks, `cobra@v1.10.2/command.go:985,1010`). +// So `supabase storage ls --linked --local` without `--experimental` must +// surface the experimental-gate error in Go, not the mutex error — this suite +// proves that ordering is wired into the actual `.command.ts` handler +// pipeline for all four leaves, not just the shared helper in isolation. + +const testRoot = Command.make("supabase").pipe( + Command.withGlobalFlags(LEGACY_GLOBAL_FLAGS), + Command.withSubcommands([legacyStorageCommand]), +); + +function setup(args: ReadonlyArray) { + const out = mockOutput({ format: "text" }); + const layer = Layer.mergeAll( + BunServices.layer, + CliOutput.layer(textCliOutputFormatter()), + out.layer, + Layer.succeed(CliArgs, { args }), + // `legacyStorageGatewayRuntimeLayer`'s cliConfig/credentials layers read + // real env/files when built. Neither check under test ever reaches that + // lazy factory, but isolate ambient env defensively anyway. + processEnvLayer({ SUPABASE_NO_KEYRING: "1" }), + mockRuntimeInfo(), + mockProcessControl().layer, + mockTty({ stdinIsTty: false, stdoutIsTty: false }), + mockAnalytics().layer, + Layer.succeed( + TelemetryRuntime, + TelemetryRuntime.of({ + configDir: "/tmp/supabase-storage-experimental-gate-test/.supabase", + tracesDir: "/tmp/supabase-storage-experimental-gate-test/.supabase/traces", + consent: "granted", + showDebug: false, + deviceId: "test-device-id", + sessionId: "test-session-id", + identity: makeTelemetryIdentity(undefined), + isFirstRun: false, + isTty: false, + isCi: false, + os: "linux", + arch: "x64", + cliVersion: "0.1.0", + }), + ), + ); + return { layer }; +} + +describe("legacy storage experimental gate vs mutual-exclusivity ordering (Go PersistentPreRunE parity)", () => { + const leaves: ReadonlyArray<{ readonly name: string; readonly args: ReadonlyArray }> = [ + { name: "ls", args: ["storage", "ls", "ss:///bucket"] }, + { name: "cp", args: ["storage", "cp", "ss:///bucket/a", "ss:///bucket/b"] }, + { name: "mv", args: ["storage", "mv", "ss:///bucket/a", "ss:///bucket/b"] }, + { name: "rm", args: ["storage", "rm", "ss:///bucket/a"] }, + ]; + + for (const { name, args } of leaves) { + const conflictingArgs = [...args, "--linked", "--local"]; + + it.live( + `${name} --linked --local without --experimental fails with the gate error, not the mutex error`, + () => { + const { layer } = setup(conflictingArgs); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + Command.runWith(testRoot, { version: "0.0.0-test" })(conflictingArgs), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const failure = Cause.findErrorOption(exit.cause); + expect( + Option.isSome(failure) && failure.value instanceof LegacyExperimentalRequiredError, + ).toBe(true); + } + }).pipe(Effect.provide(layer)); + }, + ); + + it.live(`${name} --linked --local with --experimental fails with the mutex error`, () => { + const withExperimental = [...conflictingArgs, "--experimental"]; + const { layer } = setup(withExperimental); + return Effect.gen(function* () { + const exit = yield* Effect.exit( + Command.runWith(testRoot, { version: "0.0.0-test" })(withExperimental), + ); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const failure = Cause.findErrorOption(exit.cause); + expect( + Option.isSome(failure) && + failure.value instanceof LegacyStorageMutuallyExclusiveFlagsError, + ).toBe(true); + } + }).pipe(Effect.provide(layer)); + }); + } +}); diff --git a/apps/cli/src/legacy/shared/legacy-experimental-gate.ts b/apps/cli/src/legacy/shared/legacy-experimental-gate.ts index e9b9289dcf..325f2e96bb 100644 --- a/apps/cli/src/legacy/shared/legacy-experimental-gate.ts +++ b/apps/cli/src/legacy/shared/legacy-experimental-gate.ts @@ -13,7 +13,7 @@ import { legacyResolveExperimental } from "../../shared/legacy/global-flags.ts"; * ``` * * `IsExperimental` is true for the commands registered in the `experimental` - * slice and their direct children (`root.go:56-74`). + * slice and their direct children (`root.go:56-74`), which includes `storageCmd`. * Go enforces this in `PersistentPreRunE`, which cobra runs BEFORE * `ValidateFlagGroups()` (mutual-exclusivity checks) and `RunE`/`PersistentPostRun` * (`cobra@v1.10.2/command.go:985,1010,1014`) — so a closed gate must NOT run @@ -22,9 +22,7 @@ import { legacyResolveExperimental } from "../../shared/legacy/global-flags.ts"; * validation, `ValidateArgs`, runs even earlier, at `command.go:968` — the gate * does not preempt that.) Each native experimental leaf therefore calls this in * its `.command.ts` before any mutual-exclusivity check and before - * `withLegacyCommandInstrumentation` — the 4 pre-existing storage leaves - * (`storage/{cp,ls,mv,rm}`) currently get this ordering wrong (mutex check - * before the gate); do not copy them until that's fixed. + * `withLegacyCommandInstrumentation`. * * The message byte-matches Go's `errors.New(...)`; the value is resolved with the * `SUPABASE_EXPERIMENTAL` viper fallback (see {@link legacyResolveExperimental}). From 28b17f6f4dc71c2a5cafd20ee6b571dd2114ac9b Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Mon, 6 Jul 2026 11:31:05 +0100 Subject: [PATCH 40/48] fix(cli): add hidden --exclude and --ignore-health-check flags to status (#5770) ## Current Behavior The TS `status` command only declares `--override-name`. Go's `status` also registers two hidden flags, `--exclude` and `--ignore-health-check` (`cmd/status.go:38-42`). Because the Effect CLI parser rejects undeclared flags before the command proxies to the Go binary, `supabase status --exclude db` and `supabase status --ignore-health-check` fail with an UnrecognizedOption error in TS while working fine in Go. ## Expected Behavior Both flags are now declared as hidden (matching Go's `MarkHidden`) and forwarded verbatim to the Go binary proxy, following the same pattern already used for `--override-name` and for `start`'s hidden flags. `SIDE_EFFECTS.md` documents the new flags with references to the Go source lines they mirror. Fixes CLI-1857 --- .../legacy/commands/status/SIDE_EFFECTS.md | 2 + .../legacy/commands/status/status.command.ts | 10 ++++ .../legacy/commands/status/status.handler.ts | 2 + .../status/status.integration.test.ts | 58 +++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 apps/cli/src/legacy/commands/status/status.integration.test.ts diff --git a/apps/cli/src/legacy/commands/status/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/status/SIDE_EFFECTS.md index e27d895e35..089b2340c3 100644 --- a/apps/cli/src/legacy/commands/status/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/status/SIDE_EFFECTS.md @@ -80,3 +80,5 @@ Not applicable. - `-o env` output format uses KEY=VALUE pairs. - `-o json` output format uses a JSON object. - `-o pretty` (default) uses the human-readable table format. +- `--exclude` (hidden, repeatable) omits named containers from the output; matches Go's `cmd/status.go:39-40`. +- `--ignore-health-check` (hidden, boolean) exits `0` even when a service is unhealthy; matches Go's `cmd/status.go:41-42`. diff --git a/apps/cli/src/legacy/commands/status/status.command.ts b/apps/cli/src/legacy/commands/status/status.command.ts index f10c14fafe..201bce98f0 100644 --- a/apps/cli/src/legacy/commands/status/status.command.ts +++ b/apps/cli/src/legacy/commands/status/status.command.ts @@ -8,6 +8,16 @@ const config = { Flag.withDescription("Override specific variable names."), Flag.withDefault([] as ReadonlyArray), ), + exclude: Flag.string("exclude").pipe( + Flag.atLeast(0), + Flag.withDescription("Names of containers to omit from output."), + Flag.withDefault([] as ReadonlyArray), + Flag.withHidden, + ), + ignoreHealthCheck: Flag.boolean("ignore-health-check").pipe( + Flag.withDescription("Ignore unhealthy services and exit 0"), + Flag.withHidden, + ), } as const; export type LegacyStatusFlags = CliCommand.Command.Config.Infer; diff --git a/apps/cli/src/legacy/commands/status/status.handler.ts b/apps/cli/src/legacy/commands/status/status.handler.ts index da50886773..566427260c 100644 --- a/apps/cli/src/legacy/commands/status/status.handler.ts +++ b/apps/cli/src/legacy/commands/status/status.handler.ts @@ -6,5 +6,7 @@ export const legacyStatus = Effect.fn("legacy.status")(function* (flags: LegacyS const proxy = yield* LegacyGoProxy; const args: string[] = ["status"]; for (const override of flags.overrideName) args.push("--override-name", override); + for (const name of flags.exclude) args.push("--exclude", name); + if (flags.ignoreHealthCheck) args.push("--ignore-health-check"); yield* proxy.exec(args); }); diff --git a/apps/cli/src/legacy/commands/status/status.integration.test.ts b/apps/cli/src/legacy/commands/status/status.integration.test.ts new file mode 100644 index 0000000000..1c87049978 --- /dev/null +++ b/apps/cli/src/legacy/commands/status/status.integration.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Layer } from "effect"; +import { LegacyGoProxy } from "../../../shared/legacy/go-proxy.service.ts"; +import { legacyStatus } from "./status.handler.ts"; +import type { LegacyStatusFlags } from "./status.command.ts"; + +function setupLegacyStatus() { + const calls: Array> = []; + const layer = Layer.succeed(LegacyGoProxy, { + exec: (args) => + Effect.sync(() => { + calls.push(args); + }), + execCapture: () => Effect.succeed(""), + }); + return { layer, calls }; +} + +const baseFlags: LegacyStatusFlags = { + overrideName: [], + exclude: [], + ignoreHealthCheck: false, +}; + +describe("legacy status", () => { + it.live("forwards no extra flags when defaults are used", () => { + const { layer, calls } = setupLegacyStatus(); + return Effect.gen(function* () { + yield* legacyStatus(baseFlags); + expect(calls).toEqual([["status"]]); + }).pipe(Effect.provide(layer)); + }); + + it.live("forwards --override-name for each provided override", () => { + const { layer, calls } = setupLegacyStatus(); + return Effect.gen(function* () { + yield* legacyStatus({ + ...baseFlags, + overrideName: ["api.url=NEXT_PUBLIC_SUPABASE_URL"], + }); + expect(calls).toEqual([["status", "--override-name", "api.url=NEXT_PUBLIC_SUPABASE_URL"]]); + }).pipe(Effect.provide(layer)); + }); + + it.live("forwards the hidden --exclude and --ignore-health-check flags", () => { + const { layer, calls } = setupLegacyStatus(); + return Effect.gen(function* () { + yield* legacyStatus({ + overrideName: [], + exclude: ["db", "kong"], + ignoreHealthCheck: true, + }); + expect(calls).toEqual([ + ["status", "--exclude", "db", "--exclude", "kong", "--ignore-health-check"], + ]); + }).pipe(Effect.provide(layer)); + }); +}); From cb38353da34069be51a042d675cf398c82fbf781 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Mon, 6 Jul 2026 12:38:58 +0200 Subject: [PATCH 41/48] test: trim low-value unit coverage (#5792) Trims unit tests that only asserted exported symbols, object shape, or redundant constant properties, while keeping package export contracts that guard real behavior. Removes low-value literal default-version assertions from the stack unit tests without adding new CI surface area. Strengthens JWT generation coverage by checking the decoded header and payload plus the HMAC signature instead of only checking that the token has three segments. --- packages/config/src/tls.unit.test.ts | 1 - packages/stack/src/JwtGenerator.unit.test.ts | 30 +++++++++++++--- packages/stack/src/entrypoints.unit.test.ts | 37 -------------------- packages/stack/src/versions.unit.test.ts | 34 ++---------------- 4 files changed, 29 insertions(+), 73 deletions(-) diff --git a/packages/config/src/tls.unit.test.ts b/packages/config/src/tls.unit.test.ts index 773671e588..1f266f5d80 100644 --- a/packages/config/src/tls.unit.test.ts +++ b/packages/config/src/tls.unit.test.ts @@ -6,6 +6,5 @@ describe("KONG_LOCAL_CA_CERT", () => { it("is a non-empty PEM certificate", () => { expect(KONG_LOCAL_CA_CERT).toContain("BEGIN CERTIFICATE"); expect(KONG_LOCAL_CA_CERT).toContain("END CERTIFICATE"); - expect(KONG_LOCAL_CA_CERT.length).toBeGreaterThan(0); }); }); diff --git a/packages/stack/src/JwtGenerator.unit.test.ts b/packages/stack/src/JwtGenerator.unit.test.ts index 1bc3b8c21a..9d03590bb1 100644 --- a/packages/stack/src/JwtGenerator.unit.test.ts +++ b/packages/stack/src/JwtGenerator.unit.test.ts @@ -1,14 +1,36 @@ +import { createHmac } from "node:crypto"; import { describe, expect, it } from "vitest"; import { defaultJwtSecret, generateJwks, generateJwt } from "./JwtGenerator.ts"; describe("JwtGenerator", () => { it("generates HS256 JWTs", () => { - const jwt = generateJwt(defaultJwtSecret, "anon"); + const before = Math.floor(Date.now() / 1000); + const role = "anon"; + const jwt = generateJwt(defaultJwtSecret, role); + const after = Math.floor(Date.now() / 1000); const [header, payload, signature] = jwt.split("."); - expect(header).toBeDefined(); - expect(payload).toBeDefined(); - expect(signature).toBeDefined(); + expect(JSON.parse(Buffer.from(header!, "base64url").toString("utf8"))).toEqual({ + alg: "HS256", + typ: "JWT", + }); + expect(JSON.parse(Buffer.from(payload!, "base64url").toString("utf8"))).toEqual({ + role, + iss: "supabase", + iat: expect.any(Number), + exp: expect.any(Number), + }); + + const decodedPayload = JSON.parse(Buffer.from(payload!, "base64url").toString("utf8")) as { + readonly iat: number; + readonly exp: number; + }; + expect(decodedPayload.iat).toBeGreaterThanOrEqual(before); + expect(decodedPayload.iat).toBeLessThanOrEqual(after); + expect(decodedPayload.exp).toBe(decodedPayload.iat + 60 * 60 * 24 * 365 * 10); + expect(signature).toBe( + createHmac("sha256", defaultJwtSecret).update(`${header}.${payload}`).digest("base64url"), + ); }); it("generates an oct JWKS from the local JWT secret", () => { diff --git a/packages/stack/src/entrypoints.unit.test.ts b/packages/stack/src/entrypoints.unit.test.ts index d7f0af2d5f..61d8f0f41b 100644 --- a/packages/stack/src/entrypoints.unit.test.ts +++ b/packages/stack/src/entrypoints.unit.test.ts @@ -4,44 +4,7 @@ import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; -import { - daemonEntryPoint as bunDaemonEntryPoint, - createStack as createBunStack, - unixHttpClientLayer as bunUnixHttpClientLayer, -} from "./bun.ts"; -import { - DEFAULT_VERSIONS, - Stack, - StackError, - StackServiceState, - connectLayer, - projectDaemonLayer, -} from "./effect.ts"; -import { - daemonEntryPoint as nodeDaemonEntryPoint, - createStack as createNodeStack, - unixHttpClientLayer as nodeUnixHttpClientLayer, -} from "./node.ts"; - describe("@supabase/stack entrypoints", () => { - it("exports runtime-specific stack builders", () => { - expect(typeof createBunStack).toBe("function"); - expect(typeof createNodeStack).toBe("function"); - expect(typeof bunDaemonEntryPoint).toBe("string"); - expect(typeof nodeDaemonEntryPoint).toBe("string"); - expect(bunUnixHttpClientLayer).toBeDefined(); - expect(nodeUnixHttpClientLayer).toBeDefined(); - }); - - it("consolidates advanced and internal APIs under effect", () => { - expect(Stack).toBeDefined(); - expect(typeof connectLayer).toBe("function"); - expect(typeof projectDaemonLayer).toBe("function"); - expect(StackServiceState).toBeDefined(); - expect(StackError).toBeDefined(); - expect(DEFAULT_VERSIONS.postgres).toBeDefined(); - }); - it("ships conditional root exports and keeps only the effect subpath", () => { const srcDir = dirname(fileURLToPath(import.meta.url)); const packageJson = JSON.parse(readFileSync(join(srcDir, "../package.json"), "utf8")) as { diff --git a/packages/stack/src/versions.unit.test.ts b/packages/stack/src/versions.unit.test.ts index a8440db663..7fbb4933a8 100644 --- a/packages/stack/src/versions.unit.test.ts +++ b/packages/stack/src/versions.unit.test.ts @@ -1,4 +1,3 @@ -import { readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; import { readVersionManifestFromDockerfile, @@ -14,11 +13,6 @@ import { type VersionManifest, } from "./versions.ts"; -const dockerfile = readFileSync( - new URL("../../../apps/cli-go/pkg/config/templates/Dockerfile", import.meta.url), - "utf8", -); - const sampleDockerfile = ` FROM supabase/postgres:17.0.0.1 AS pg FROM library/kong:2.8.1 AS kong @@ -37,30 +31,6 @@ FROM supabase/logflare:1.40.0 AS logflare FROM supabase/migra:3.0.1663481299 AS migra `; -describe("DEFAULT_VERSIONS", () => { - it("has all required services", () => { - expect(DEFAULT_VERSIONS).toHaveProperty("postgres"); - expect(DEFAULT_VERSIONS).toHaveProperty("postgrest"); - expect(DEFAULT_VERSIONS).toHaveProperty("auth"); - expect(DEFAULT_VERSIONS).toHaveProperty("edge-runtime"); - }); - - it("versions are non-empty strings", () => { - expect(typeof DEFAULT_VERSIONS.postgres).toBe("string"); - expect(DEFAULT_VERSIONS.postgres.length).toBeGreaterThan(0); - expect(typeof DEFAULT_VERSIONS.postgrest).toBe("string"); - expect(DEFAULT_VERSIONS.postgrest.length).toBeGreaterThan(0); - expect(typeof DEFAULT_VERSIONS.auth).toBe("string"); - expect(DEFAULT_VERSIONS.auth.length).toBeGreaterThan(0); - expect(typeof DEFAULT_VERSIONS["edge-runtime"]).toBe("string"); - expect(DEFAULT_VERSIONS["edge-runtime"].length).toBeGreaterThan(0); - }); - - it("matches the Dockerfile manifest exposed to Dependabot", () => { - expect(readVersionManifestFromDockerfile(dockerfile)).toEqual(DEFAULT_VERSIONS); - }); -}); - describe("syncDefaultVersionsSource", () => { it("rewrites the DEFAULT_VERSIONS block from Dockerfile versions", () => { const source = `before @@ -111,7 +81,9 @@ after`; it("fails when the Dockerfile contains an unexpected image alias", () => { expect(() => - readVersionManifestFromDockerfile(`${dockerfile}\nFROM supabase/example:1.0.0 AS example\n`), + readVersionManifestFromDockerfile( + `${sampleDockerfile}\nFROM supabase/example:1.0.0 AS example\n`, + ), ).toThrow("Unknown Dockerfile image alias 'example'."); }); }); From 143968079c08a4b2df9d1d157cfa159658b9dc96 Mon Sep 17 00:00:00 2001 From: swiflyme Date: Mon, 6 Jul 2026 13:03:41 +0200 Subject: [PATCH 42/48] fix(cli): honor SUPABASE_INTERNAL_IMAGE_REGISTRY in gen types (#5786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What Make `supabase gen types --local` honor `SUPABASE_INTERNAL_IMAGE_REGISTRY` for **any** registry when pulling the `postgres-meta` image, instead of only special-casing `docker.io`. Closes #5785. ## Why On GitHub-hosted runners (shared egress IPs), `public.ecr.aws` is frequently rate-limited. `supabase/setup-cli` exports `SUPABASE_INTERNAL_IMAGE_REGISTRY=ghcr.io` precisely to avoid this (`core.exportVariable(CLI_CONFIG_REGISTRY, "ghcr.io")`). `supabase start` honors it, but `gen types` did not: it kept running `docker run public.ecr.aws/supabase/postgres-meta:` and failed with ``` docker: toomanyrequests: Rate exceeded ``` even with `SUPABASE_INTERNAL_IMAGE_REGISTRY=ghcr.io` set in the step env. ### Root cause - `apps/cli/src/legacy/commands/gen/types/types.handler.ts` — `runPgMeta` spawns the container with `resolvePgmetaImage(...)`. - `apps/cli/src/legacy/commands/gen/types/types.shared.ts` — `resolvePgmetaImage` only special-cased `SUPABASE_INTERNAL_IMAGE_REGISTRY === "docker.io"`; any other value fell through to `dockerImageForService`. - `packages/stack/src/versions.ts` — `dockerImageForService` hardcodes `DEFAULT_REGISTRY = "public.ecr.aws/supabase"` and does not read the env var. The Go CLI honored the override (with multi-registry fallback) via `GetRegistryImageUrl` in `apps/cli-go/internal/utils/docker.go`, so this is a regression in the TypeScript rewrite. ## How Resolve the image through the existing `legacyGetRegistryImageUrl` helper (`apps/cli/src/legacy/shared/legacy-docker-registry.ts`) — the documented 1:1 port of Go's `GetRegistryImageUrl`, already used by `supabase start` and `test db` (`test/db/db.handler.ts` for `pg_prove`). It honors `SUPABASE_INTERNAL_IMAGE_REGISTRY` for any registry, returns Docker Hub images unchanged for `docker.io`, and defaults to the ECR mirror when unset. Output is unchanged in the two pre-existing cases: | `SUPABASE_INTERNAL_IMAGE_REGISTRY` | before | after | | --- | --- | --- | | _(unset)_ | `public.ecr.aws/supabase/postgres-meta:v` | `public.ecr.aws/supabase/postgres-meta:v` | | `docker.io` | `supabase/postgres-meta:v` | `supabase/postgres-meta:v` | | `ghcr.io` | `public.ecr.aws/supabase/postgres-meta:v` ❌ | `ghcr.io/supabase/postgres-meta:v` ✅ | | `my.registry` | `public.ecr.aws/supabase/postgres-meta:v` ❌ | `my.registry/supabase/postgres-meta:v` ✅ | ## Tests Added regression guards in `types.unit.test.ts` for the default (ECR), `ghcr.io`, and an arbitrary mirror. The `ghcr.io` case fails against the old code. - `types.unit.test.ts`: 26/26 pass - `types.integration.test.ts`: 71/71 pass - `types:check` / `lint:check` / `fmt:check` / `knip:check`: clean --------- Co-authored-by: swiflyme <252834199+swiflyme@users.noreply.github.com> Co-authored-by: Claude Fable 5 Co-authored-by: Julien Goux --- .../legacy/commands/gen/types/SIDE_EFFECTS.md | 16 ++++---- .../commands/gen/types/types.e2e.test.ts | 5 ++- .../legacy/commands/gen/types/types.shared.ts | 25 ++++++++----- .../commands/gen/types/types.unit.test.ts | 23 ++++++++++++ .../legacy/shared/legacy-docker-registry.ts | 37 +++++++++++-------- .../legacy-docker-registry.unit.test.ts | 11 ++++++ 6 files changed, 83 insertions(+), 34 deletions(-) diff --git a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md index 511b89d5f3..1bc6151b77 100644 --- a/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md +++ b/apps/cli/src/legacy/commands/gen/types/SIDE_EFFECTS.md @@ -56,14 +56,14 @@ default 10s pg-delta probe timeout. ## Environment Variables -| Variable | Purpose | Required? | -| ---------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | -| `SUPABASE_ACCESS_TOKEN` | auth token for linked/project-id mode | no (falls back to keyring → `~/.supabase/access-token`) | -| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | -| `SUPABASE_DB_PASSWORD` | database password for `--local` and the `--linked` workdir project | no (defaults to `postgres`; **ignored** for ad-hoc `--project-id`, which always mints a temporary login role) | -| `SUPABASE_SERVICES_HOSTNAME` | host used for the local TLS probe | no (defaults to `127.0.0.1`) | -| `SUPABASE_INTERNAL_IMAGE_REGISTRY` | pg-meta image registry override (`docker.io` → Docker Hub) | no (defaults to the ECR registry) | -| `SUPABASE_CA_SKIP_VERIFY` | when `true`, prints a TLS-verification-disabled warning to stderr | no | +| Variable | Purpose | Required? | +| ---------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | +| `SUPABASE_ACCESS_TOKEN` | auth token for linked/project-id mode | no (falls back to keyring → `~/.supabase/access-token`) | +| `SUPABASE_API_URL` | override Management API base URL | no (defaults to `https://api.supabase.com`) | +| `SUPABASE_DB_PASSWORD` | database password for `--local` and the `--linked` workdir project | no (defaults to `postgres`; **ignored** for ad-hoc `--project-id`, which always mints a temporary login role) | +| `SUPABASE_SERVICES_HOSTNAME` | host used for the local TLS probe | no (defaults to `127.0.0.1`) | +| `SUPABASE_INTERNAL_IMAGE_REGISTRY` | pg-meta image registry override (`docker.io` → Docker Hub; any other value → that registry) | no (defaults to the ECR registry) | +| `SUPABASE_CA_SKIP_VERIFY` | when `true`, prints a TLS-verification-disabled warning to stderr | no | ## Exit Codes diff --git a/apps/cli/src/legacy/commands/gen/types/types.e2e.test.ts b/apps/cli/src/legacy/commands/gen/types/types.e2e.test.ts index 6d9a4032fc..8fb849a120 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.e2e.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.e2e.test.ts @@ -1,19 +1,20 @@ import { spawn } from "node:child_process"; import { mkdir, writeFile } from "node:fs/promises"; import { join } from "node:path"; -import { DEFAULT_VERSIONS, dockerImageForService } from "@supabase/stack/effect"; import { describe, expect, test } from "vitest"; import { makeTempHome, makeTempStackProject, runSupabase, } from "../../../../../tests/helpers/cli.ts"; +import { dockerfileServiceImage } from "../../../../shared/services/dockerfile-images.ts"; import { localDbContainerId, localNetworkId } from "../../../shared/legacy-docker-ids.ts"; +import { legacyGetRegistryImageUrl } from "../../../shared/legacy-docker-registry.ts"; const TYPEGEN_LANGS = ["typescript", "go", "swift", "python"] as const; type TypegenLang = (typeof TYPEGEN_LANGS)[number]; -const LOCAL_POSTGRES_IMAGE = dockerImageForService("postgres", DEFAULT_VERSIONS.postgres); +const LOCAL_POSTGRES_IMAGE = legacyGetRegistryImageUrl(dockerfileServiceImage("pg")); const LOCAL_POSTGRES_TIMEOUT_MS = 120_000; const TYPEGEN_TIMEOUT_MS = 90_000; const REMOTE_E2E_FLAG = "SUPABASE_TYPEGEN_E2E_REMOTE"; diff --git a/apps/cli/src/legacy/commands/gen/types/types.shared.ts b/apps/cli/src/legacy/commands/gen/types/types.shared.ts index 2f55067e1f..4480a03ada 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.shared.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.shared.ts @@ -1,5 +1,6 @@ -import { DEFAULT_VERSIONS, dockerImageForService } from "@supabase/stack/effect"; import { Effect } from "effect"; +import { dockerfileServiceImage } from "../../../../shared/services/dockerfile-images.ts"; +import { legacyGetRegistryImageUrl } from "../../../shared/legacy-docker-registry.ts"; import { LegacyInvalidGenTypesDatabaseUrlError, LegacyInvalidGenTypesDurationError, @@ -139,17 +140,23 @@ export function buildPostgresUrl(input: { } export function resolvePgmetaImage(versionOverride?: string) { - const version = - versionOverride && versionOverride.trim().length > 0 - ? versionOverride.trim().replace(/^v/i, "") - : DEFAULT_VERSIONS.pgmeta; - const registry = process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"]?.toLowerCase(); - if (registry === "docker.io") { - return `supabase/postgres-meta:v${version}`; + const defaultImage = dockerfileServiceImage("pgmeta"); + if (versionOverride === undefined || versionOverride.trim().length === 0) { + return legacyGetRegistryImageUrl(defaultImage); } - return dockerImageForService("pgmeta", version); + return legacyGetRegistryImageUrl( + replaceImageTag(defaultImage, `v${versionOverride.trim().replace(/^v/i, "")}`), + ); } export function legacyRootCaBundle() { return `${caStaging2021}${caProd2021}${caProd2025}`; } + +function replaceImageTag(image: string, tag: string): string { + const tagSeparator = image.lastIndexOf(":"); + if (tagSeparator === -1) { + return image; + } + return `${image.slice(0, tagSeparator + 1)}${tag}`; +} diff --git a/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts b/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts index 115022e562..44bf4d9598 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.unit.test.ts @@ -157,6 +157,29 @@ describe("resolvePgmetaImage", () => { expect(image).not.toBe("supabase/postgres-meta:v1.2.3"); expect(image).toContain("postgres-meta:v1.2.3"); }); + + it("defaults to the ECR mirror when no registry override is set", () => { + const image = withEnv("SUPABASE_INTERNAL_IMAGE_REGISTRY", undefined, () => + resolvePgmetaImage("1.2.3"), + ); + expect(image).toBe("public.ecr.aws/supabase/postgres-meta:v1.2.3"); + }); + + it("honors SUPABASE_INTERNAL_IMAGE_REGISTRY for a non docker.io registry (e.g. ghcr.io)", () => { + // Regression: setup-cli exports `ghcr.io` on shared CI runners to dodge ECR + // rate limits, but gen types used to ignore it and still pull from ECR. + const image = withEnv("SUPABASE_INTERNAL_IMAGE_REGISTRY", "ghcr.io", () => + resolvePgmetaImage("1.2.3"), + ); + expect(image).toBe("ghcr.io/supabase/postgres-meta:v1.2.3"); + }); + + it("rewrites to an arbitrary configured mirror registry", () => { + const image = withEnv("SUPABASE_INTERNAL_IMAGE_REGISTRY", "my.registry.example", () => + resolvePgmetaImage("1.2.3"), + ); + expect(image).toBe("my.registry.example/supabase/postgres-meta:v1.2.3"); + }); }); describe("schema and id helpers", () => { diff --git a/apps/cli/src/legacy/shared/legacy-docker-registry.ts b/apps/cli/src/legacy/shared/legacy-docker-registry.ts index b18bf522f7..2534067128 100644 --- a/apps/cli/src/legacy/shared/legacy-docker-registry.ts +++ b/apps/cli/src/legacy/shared/legacy-docker-registry.ts @@ -13,11 +13,24 @@ * use `legacyGetRegistryImageUrlCandidates`: ECR stays the fast default, with * GHCR and the source image as fallbacks for transient registry throttling. */ +const LEGACY_INTERNAL_IMAGE_REGISTRY_ENV = "SUPABASE_INTERNAL_IMAGE_REGISTRY"; const DEFAULT_REGISTRY = "public.ecr.aws"; +const DEFAULT_SUPABASE_REGISTRY = `${DEFAULT_REGISTRY}/supabase`; const GHCR_REGISTRY = "ghcr.io"; +const GHCR_SUPABASE_REGISTRY = `${GHCR_REGISTRY}/supabase`; +const DOCKER_HUB_REGISTRY = "docker.io"; + +function dedupe(values: ReadonlyArray): ReadonlyArray { + return [...new Set(values)]; +} + +function getLastImageSegment(imageName: string): string { + const parts = imageName.split("/"); + return parts[parts.length - 1] ?? imageName; +} function legacyGetRegistryOverride(): string | undefined { - const registry = process.env["SUPABASE_INTERNAL_IMAGE_REGISTRY"]; + const registry = process.env[LEGACY_INTERNAL_IMAGE_REGISTRY_ENV]?.trim(); return registry === undefined || registry.length === 0 ? undefined : registry.toLowerCase(); } @@ -27,35 +40,29 @@ function legacyGetRegistry(): string { export function legacyGetRegistryImageUrl(imageName: string): string { const registry = legacyGetRegistry(); - if (registry === "docker.io") { + if (registry === DOCKER_HUB_REGISTRY) { return imageName; } - const parts = imageName.split("/"); - const lastPart = parts[parts.length - 1] ?? imageName; - return `${registry}/supabase/${lastPart}`; + return `${registry}/supabase/${getLastImageSegment(imageName)}`; } export function legacyGetRegistryImageUrlCandidates(imageName: string): ReadonlyArray { if (legacyGetRegistryOverride() !== undefined) { return [legacyGetRegistryImageUrl(imageName)]; } - const parts = imageName.split("/"); - const lastPart = parts[parts.length - 1] ?? imageName; + + const lastPart = getLastImageSegment(imageName); return dedupe([ - `${DEFAULT_REGISTRY}/supabase/${lastPart}`, - `${GHCR_REGISTRY}/supabase/${lastPart}`, + legacyGetRegistryImageUrl(imageName), + `${GHCR_SUPABASE_REGISTRY}/${lastPart}`, dockerHubFallbackImage(imageName, lastPart), ]); } -function dedupe(values: ReadonlyArray): ReadonlyArray { - return [...new Set(values)]; -} - function dockerHubFallbackImage(imageName: string, lastPart: string): string { if ( - imageName.startsWith(`${DEFAULT_REGISTRY}/supabase/`) || - imageName.startsWith(`${GHCR_REGISTRY}/supabase/`) + imageName.startsWith(`${DEFAULT_SUPABASE_REGISTRY}/`) || + imageName.startsWith(`${GHCR_SUPABASE_REGISTRY}/`) ) { return `supabase/${lastPart}`; } diff --git a/apps/cli/src/legacy/shared/legacy-docker-registry.unit.test.ts b/apps/cli/src/legacy/shared/legacy-docker-registry.unit.test.ts index 5c38fddc55..21ef80a438 100644 --- a/apps/cli/src/legacy/shared/legacy-docker-registry.unit.test.ts +++ b/apps/cli/src/legacy/shared/legacy-docker-registry.unit.test.ts @@ -24,6 +24,12 @@ describe("legacyGetRegistryImageUrl", () => { ); }); + it("treats a blank registry override as unset", () => { + expect(withRegistry(" ", () => legacyGetRegistryImageUrl("supabase/pg_prove:3.36"))).toBe( + "public.ecr.aws/supabase/pg_prove:3.36", + ); + }); + it("returns the image unchanged for docker.io (case-insensitive)", () => { expect( withRegistry("docker.io", () => legacyGetRegistryImageUrl("supabase/pg_prove:3.36")), @@ -74,5 +80,10 @@ describe("legacyGetRegistryImageUrl", () => { legacyGetRegistryImageUrlCandidates("supabase/postgres:17.6.1.138"), ), ).toEqual(["supabase/postgres:17.6.1.138"]); + expect( + withRegistry("my.mirror.example", () => + legacyGetRegistryImageUrlCandidates("supabase/postgres:17.6.1.138"), + ), + ).toEqual(["my.mirror.example/supabase/postgres:17.6.1.138"]); }); }); From 12b7ed9d65cace9fb685c7044689b7cce548462b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 11:10:17 +0000 Subject: [PATCH 43/48] fix(docker): bump the docker-minor group in /apps/cli-go/pkg/config/templates with 2 updates (#5781) Bumps the docker-minor group in /apps/cli-go/pkg/config/templates with 2 updates: supabase/realtime and supabase/storage-api. Updates `supabase/realtime` from v2.112.3 to v2.112.4 Updates `supabase/storage-api` from v1.61.11 to v1.61.13 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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Julien Goux --- apps/cli-go/pkg/config/templates/Dockerfile | 4 ++-- packages/stack/src/versions.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index 7b376de5eb..977948cb20 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -11,8 +11,8 @@ FROM supabase/edge-runtime:v1.74.2 AS edgeruntime FROM timberio/vector:0.53.0-alpine AS vector FROM supabase/supavisor:2.9.7 AS supavisor FROM supabase/gotrue:v2.192.0 AS gotrue -FROM supabase/realtime:v2.112.3 AS realtime -FROM supabase/storage-api:v1.61.11 AS storage +FROM supabase/realtime:v2.112.4 AS realtime +FROM supabase/storage-api:v1.61.13 AS storage FROM supabase/logflare:1.46.0 AS logflare # Append to JobImages when adding new dependencies below FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ diff --git a/packages/stack/src/versions.ts b/packages/stack/src/versions.ts index e13a2d4c71..14666bfcc9 100644 --- a/packages/stack/src/versions.ts +++ b/packages/stack/src/versions.ts @@ -50,8 +50,8 @@ export const DEFAULT_VERSIONS: VersionManifest = { postgrest: "14.14", auth: "2.192.0", "edge-runtime": "1.74.2", - realtime: "2.112.3", - storage: "1.61.11", + realtime: "2.112.4", + storage: "1.61.13", imgproxy: "v3.8.0", mailpit: "v1.30.2", pgmeta: "0.96.6", From 78acec56864fde021ad8e1b4f18196e233ac5fb5 Mon Sep 17 00:00:00 2001 From: "supabase-cli-releaser[bot]" <246109035+supabase-cli-releaser[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:42:22 +0000 Subject: [PATCH 44/48] chore: sync API types from infrastructure (#5795) This PR was automatically created to sync API types from the infrastructure repository. Changes were detected in the generated API code after syncing with the latest spec from infrastructure. Co-authored-by: supabase-cli-releaser[bot] <246109035+supabase-cli-releaser[bot]@users.noreply.github.com> --- apps/cli-go/pkg/api/types.gen.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/cli-go/pkg/api/types.gen.go b/apps/cli-go/pkg/api/types.gen.go index f0785b4a4b..743af52366 100644 --- a/apps/cli-go/pkg/api/types.gen.go +++ b/apps/cli-go/pkg/api/types.gen.go @@ -3459,6 +3459,7 @@ type PostgresConfigResponse struct { MaintenanceWorkMem *string `json:"maintenance_work_mem,omitempty"` MaxConnections *int `json:"max_connections,omitempty"` MaxLocksPerTransaction *int `json:"max_locks_per_transaction,omitempty"` + MaxLogicalReplicationWorkers *int `json:"max_logical_replication_workers,omitempty"` MaxParallelMaintenanceWorkers *int `json:"max_parallel_maintenance_workers,omitempty"` MaxParallelWorkers *int `json:"max_parallel_workers,omitempty"` MaxParallelWorkersPerGather *int `json:"max_parallel_workers_per_gather,omitempty"` @@ -3466,6 +3467,7 @@ type PostgresConfigResponse struct { MaxSlotWalKeepSize *string `json:"max_slot_wal_keep_size,omitempty"` MaxStandbyArchiveDelay *string `json:"max_standby_archive_delay,omitempty"` MaxStandbyStreamingDelay *string `json:"max_standby_streaming_delay,omitempty"` + MaxSyncWorkersPerSubscription *int `json:"max_sync_workers_per_subscription,omitempty"` MaxWalSenders *int `json:"max_wal_senders,omitempty"` MaxWalSize *string `json:"max_wal_size,omitempty"` MaxWorkerProcesses *int `json:"max_worker_processes,omitempty"` @@ -4398,6 +4400,7 @@ type UpdatePostgresConfigBody struct { MaintenanceWorkMem *string `json:"maintenance_work_mem,omitempty"` MaxConnections *int `json:"max_connections,omitempty"` MaxLocksPerTransaction *int `json:"max_locks_per_transaction,omitempty"` + MaxLogicalReplicationWorkers *int `json:"max_logical_replication_workers,omitempty"` MaxParallelMaintenanceWorkers *int `json:"max_parallel_maintenance_workers,omitempty"` MaxParallelWorkers *int `json:"max_parallel_workers,omitempty"` MaxParallelWorkersPerGather *int `json:"max_parallel_workers_per_gather,omitempty"` @@ -4405,6 +4408,7 @@ type UpdatePostgresConfigBody struct { MaxSlotWalKeepSize *string `json:"max_slot_wal_keep_size,omitempty"` MaxStandbyArchiveDelay *string `json:"max_standby_archive_delay,omitempty"` MaxStandbyStreamingDelay *string `json:"max_standby_streaming_delay,omitempty"` + MaxSyncWorkersPerSubscription *int `json:"max_sync_workers_per_subscription,omitempty"` MaxWalSenders *int `json:"max_wal_senders,omitempty"` MaxWalSize *string `json:"max_wal_size,omitempty"` MaxWorkerProcesses *int `json:"max_worker_processes,omitempty"` From 60c2d17c5c3670e35547e956fe91c75d5da7df1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:13:04 +0000 Subject: [PATCH 45/48] fix(deps): bump github.com/posthog/posthog-go from 1.16.2 to 1.17.2 in /apps/cli-go in the go-minor group across 1 directory (#5805) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the go-minor group with 1 update in the /apps/cli-go directory: [github.com/posthog/posthog-go](https://github.com/posthog/posthog-go). Updates `github.com/posthog/posthog-go` from 1.16.2 to 1.17.2
Release notes

Sourced from github.com/posthog/posthog-go's releases.

1.17.2

Unreleased

1.17.1

Unreleased

1.17.0

Unreleased

Changelog

Sourced from github.com/posthog/posthog-go's changelog.

1.17.2

Patch Changes

  • d98ef79: Retry feature flag requests after transient network errors only. The feature flag request retry count defaults to 1 and can be set to 0 to disable retries.

1.17.1

Patch Changes

  • 651b303: Preserve caller-provided system context values when building capture v1 event properties.
  • e0eb8bd: Send capture batches uncompressed when request-body compression fails instead of failing the batch.
  • 71f5c2e: Expose capture default properties to BeforeSend hooks before serialization.

1.17.0

Minor Changes

  • ff0e74b: Enrich capture-v1 failure reporting with typed errors and verbose result logging. When CaptureMode is CaptureModeAnalyticsV1, Callback.Failure now receives either a *CaptureEventError (a single event the server dropped, or one still asking to retry once attempts are exhausted — exposing EventUUID, Result, Details, and Exhausted) or a *CaptureRequestError (a whole request that failed on a non-2xx status, transport error, or malformed body — exposing StatusCode, Code, Description, and unwrapping to the underlying error). Inspect them with errors.As. Additionally, with Verbose: true the SDK logs one debug line per 2xx response summarizing the per-event directive counts (ok/warning/drop/retry/other) so partial-submission outcomes are easy to debug. The legacy path and its callback errors are unchanged.
  • ff0e74b: Add opt-in capture-v1 support via a new Config.CaptureMode option. It defaults to CaptureModeLegacy (the existing POST /batch/ endpoint), so upgrading is transparent and requires no code changes. Set CaptureMode: posthog.CaptureModeAnalyticsV1 to send events to POST /i/v1/analytics/events instead, which uses Bearer auth, per-event delivery results, and partial retry (only the events the server asks to retry are re-sent). For v1, the Callback interface is unchanged but outcomes become per-event: a drop result fails an individual event (and can fire Failure on an HTTP 200), warning counts as success, and a uuid missing from the results is silently dropped. All other configuration (host, API key, Callback, Logger, MaxRetries, Compression) is reused across both modes.
  • ff0e74b: Add zstd, deflate, and brotli compression for the capture-v1 path, alongside the existing gzip. Select one via Config.Compression (CompressionZstd, CompressionDeflate, CompressionBrotli) and the SDK sets the matching Content-Encoding header (zstd, deflate, br). These three codecs require CaptureMode: posthog.CaptureModeAnalyticsV1 — the legacy POST /batch/ endpoint only understands gzip, so configuring them with CaptureModeLegacy returns a validation error. CompressionGzip and CompressionNone continue to work on both capture modes. All codecs use pure-Go libraries (stdlib compress/zlib, github.com/klauspost/compress/zstd, github.com/andybalholm/brotli), so CGO_ENABLED=0 builds are unaffected.
Commits
  • c2ee6b1 chore: release v1.17.2 [version bump] [skip ci]
  • d98ef79 fix(flags): retry flag requests on transient network errors (#240)
  • 63feb5e chore: release v1.17.1 [version bump] [skip ci]
  • e0eb8bd fix: fall back to uncompressed on compression failure (#242)
  • 651b303 fix: preserve v1 caller system context (#243)
  • 71f5c2e fix: expose capture defaults to BeforeSend hooks (#244)
  • 45e5e96 chore: release v1.17.0 [version bump] [skip ci]
  • ff0e74b feat(capture): add v1 wire types and serialization (unwired) (#235)
  • 2832a6e chore: use pnpm setup runtime action (#241)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/posthog/posthog-go&package-manager=go_modules&previous-version=1.16.2&new-version=1.17.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) 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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli-go/go.mod | 2 +- apps/cli-go/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/cli-go/go.mod b/apps/cli-go/go.mod index 8205e55032..82b8ea4526 100644 --- a/apps/cli-go/go.mod +++ b/apps/cli-go/go.mod @@ -42,7 +42,7 @@ require ( github.com/multigres/multigres v0.0.0-20260126223308-f5a52171bbc4 github.com/oapi-codegen/nullable v1.2.0 github.com/olekukonko/tablewriter v1.1.4 - github.com/posthog/posthog-go v1.16.2 + github.com/posthog/posthog-go v1.17.2 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/apps/cli-go/go.sum b/apps/cli-go/go.sum index 948d460a26..f929d36494 100644 --- a/apps/cli-go/go.sum +++ b/apps/cli-go/go.sum @@ -970,8 +970,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posthog/posthog-go v1.16.2 h1:zjbAZZELbt8/GNTWyFmZuVNURf8prGdlo1BcJOX9RK4= -github.com/posthog/posthog-go v1.16.2/go.mod h1:xsVOW9YImilUcazwPNEq4PJDqEZf2KeCS758zXjwkPg= +github.com/posthog/posthog-go v1.17.2 h1:w0TaCAd+Z3WoEgVyR/nlcXlqNN2tpoBfIyxuGssDgCE= +github.com/posthog/posthog-go v1.17.2/go.mod h1://M430hNH3e8CDv4i8SJesb26816Mpa6GIZaiP4pNQU= github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= From 2b8eeea38c3f74a7a64ce874b72ee5fba71f62f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:13:31 +0000 Subject: [PATCH 46/48] fix(deps): bump the npm-major group with 8 updates (#5808) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm-major group with 8 updates: | Package | From | To | | --- | --- | --- | | [@anthropic-ai/claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-typescript) | `0.3.195` | `0.3.196` | | [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript) | `0.106.0` | `0.107.0` | | [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.38.6` | `5.38.8` | | [fumadocs-core](https://github.com/fuma-nama/fumadocs) | `16.10.6` | `16.10.7` | | [fumadocs-ui](https://github.com/fuma-nama/fumadocs) | `16.10.6` | `16.10.7` | | [@typescript/native-preview](https://github.com/microsoft/typescript-go) | `7.0.0-dev.20260628.1` | `7.0.0-dev.20260629.1` | | [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) | `0.56.0` | `0.57.0` | | [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) | `1.71.0` | `1.72.0` | Updates `@anthropic-ai/claude-agent-sdk` from 0.3.195 to 0.3.196
Release notes

Sourced from @​anthropic-ai/claude-agent-sdk's releases.

v0.3.196

What's changed

  • Added prompt_id field to hook input payloads for correlating hook events with OpenTelemetry prompt-level events
  • Fixed control protocol deduplication dropping tool-use IDs after 1000 resolutions, which could cause duplicate tool_result deliveries in long-running sessions

Update

npm install @anthropic-ai/claude-agent-sdk@0.3.196
# or
yarn add @anthropic-ai/claude-agent-sdk@0.3.196
# or
pnpm add @anthropic-ai/claude-agent-sdk@0.3.196
# or
bun add @anthropic-ai/claude-agent-sdk@0.3.196
Changelog

Sourced from @​anthropic-ai/claude-agent-sdk's changelog.

0.3.196

  • Added prompt_id field to hook input payloads for correlating hook events with OpenTelemetry prompt-level events
  • Fixed control protocol deduplication dropping tool-use IDs after 1000 resolutions, which could cause duplicate tool_result deliveries in long-running sessions
Commits

Updates `@anthropic-ai/sdk` from 0.106.0 to 0.107.0
Release notes

Sourced from @​anthropic-ai/sdk's releases.

sdk: v0.107.0

0.107.0 (2026-06-29)

Full Changelog: sdk-v0.106.0...sdk-v0.107.0

Features

  • api: add support for 20260318 web fetch and support tools (d7057ea)

Bug Fixes

  • agent-toolset: bound canonicalize symlink hops; resolve glob matches before confinement check (#142) (277be80)
  • types: restore BatchCreateParams.Request.params dropped by codegen merge (#165) (c6b68d2)

Chores

  • api: accept user profile ID's when counting tokens (6cda67e)
  • docs: updates to descriptions and example values (51a5f81)

Documentation

  • update readme requirements to match anthropic docs (#146) (3818412)
Changelog

Sourced from @​anthropic-ai/sdk's changelog.

0.107.0 (2026-06-29)

Full Changelog: sdk-v0.106.0...sdk-v0.107.0

Features

  • api: add support for 20260318 web fetch and support tools (d7057ea)

Bug Fixes

  • agent-toolset: bound canonicalize symlink hops; resolve glob matches before confinement check (#142) (277be80)
  • types: restore BatchCreateParams.Request.params dropped by codegen merge (#165) (c6b68d2)

Chores

  • api: accept user profile ID's when counting tokens (6cda67e)
  • docs: updates to descriptions and example values (51a5f81)

Documentation

  • update readme requirements to match anthropic docs (#146) (3818412)
Commits
  • f1662fe chore: release main
  • c7d44fd fix(types): restore BatchCreateParams.Request.params dropped by codegen merge...
  • e345810 feat(api): add support for 20260318 web fetch and support tools
  • c625b04 fix(agent-toolset): bound canonicalize symlink hops; resolve glob matches bef...
  • 3a7c513 chore(docs): updates to descriptions and example values
  • 756ec9e docs: update readme requirements to match anthropic docs (#146)
  • 9d37c71 chore(api): accept user profile ID's when counting tokens
  • d3c49c7 codegen metadata
  • See full diff in compare view

Updates `posthog-node` from 5.38.6 to 5.38.8
Release notes

Sourced from posthog-node's releases.

posthog-node@5.38.8

5.38.8

Patch Changes

  • #3974 08b404b Thanks @​mjfaga! - Fix local evaluation ignoring the in/not_in operator on cohort-based flag conditions. "Not in cohort" was evaluated as "in cohort", inverting cohort-exclusion flags. Now applies the operator to the cohort membership result. (2026-06-29)

posthog-node@5.38.7

5.38.7

Patch Changes

  • #3961 619a25c Thanks @​marandaneto! - Retry feature flag requests after transient network errors only. The feature flag request retry count defaults to 1 and can be set to 0 to disable retries. (2026-06-29)
  • Updated dependencies [619a25c]:
    • @​posthog/core@​1.38.1
Changelog

Sourced from posthog-node's changelog.

5.38.8

Patch Changes

  • #3974 08b404b Thanks @​mjfaga! - Fix local evaluation ignoring the in/not_in operator on cohort-based flag conditions. "Not in cohort" was evaluated as "in cohort", inverting cohort-exclusion flags. Now applies the operator to the cohort membership result. (2026-06-29)

5.38.7

Patch Changes

  • #3961 619a25c Thanks @​marandaneto! - Retry feature flag requests after transient network errors only. The feature flag request retry count defaults to 1 and can be set to 0 to disable retries. (2026-06-29)
  • Updated dependencies [619a25c]:
    • @​posthog/core@​1.38.1
Commits
  • 479d5ea chore: update versions and lockfile [version bump]
  • 08b404b fix(node): apply in/not_in operator on flag-level cohort conditions in local ...
  • 272c193 chore: update versions and lockfile [version bump]
  • 619a25c fix(flags): retry flag requests on transient network errors (#3961)
  • See full diff in compare view

Updates `fumadocs-core` from 16.10.6 to 16.10.7
Commits

Updates `fumadocs-ui` from 16.10.6 to 16.10.7
Commits

Updates `@typescript/native-preview` from 7.0.0-dev.20260628.1 to 7.0.0-dev.20260629.1
Commits

Updates `oxfmt` from 0.56.0 to 0.57.0
Commits

Updates `oxlint` from 1.71.0 to 1.72.0
Release notes

Sourced from oxlint's releases.

oxlint v1.27.0 && oxfmt v0.12.0

Oxlint v1.27.0

🚀 Features

  • 222a8f0 linter/plugins: Implement SourceCode#isSpaceBetween (#15498) (overlookmotel)
  • 2f9735d linter/plugins: Implement context.languageOptions (#15486) (overlookmotel)
  • bc731ff linter/plugins: Stub out all Context APIs (#15479) (overlookmotel)
  • 5822cb4 linter/plugins: Add extend method to FILE_CONTEXT (#15477) (overlookmotel)
  • 7b1e6f3 apps: Add pure rust binaries and release to github (#15469) (Boshen)
  • 2a89b43 linter: Introduce debug assertions after fixes to assert validity (#15389) (camc314)
  • ad3c45a editor: Add oxc.path.node option (#15040) (Sysix)

🐛 Bug Fixes

  • 6f3cd77 linter/no-var: Incorrect warning for blocks (#15504) (Hamir Mahal)
  • 6957fb9 linter/plugins: Do not allow access to Context#id in createOnce (#15489) (overlookmotel)
  • 7409630 linter/plugins: Allow access to cwd in createOnce in ESLint interop mode (#15488) (overlookmotel)
  • 732205e parser: Reject using / await using in a switch case / default clause (#15225) (sapphi-red)
  • a17ca32 linter/plugins: Replace Context class (#15448) (overlookmotel)
  • ecf2f7b language_server: Fail gracefully when tsgolint executable not found (#15436) (camc314)
  • 3c8d3a7 lang-server: Improve logging in failure case for tsgolint (#15299) (camc314)
  • ef71410 linter: Use jsx if source type is JS in fix debug assertion (#15434) (camc314)
  • e32bbf6 linter/no-var: Handle TypeScript declare keyword in fixer (#15426) (camc314)
  • 6565dbe linter/switch-case-braces: Skip comments when searching for : token (#15425) (camc314)
  • 85bd19a linter/prefer-class-fields: Insert value after type annotation in fixer (#15423) (camc314)
  • fde753e linter/plugins: Block access to context.settings in createOnce (#15394) (overlookmotel)
  • ddd9f9f linter/forward-ref-uses-ref: Dont suggest removing wrapper in invalid positions (#15388) (camc314)
  • dac2a9c linter/no-template-curly-in-string: Remove fixer (#15387) (camc314)
  • 989b8e3 linter/no-var: Only fix to const if the var has an initializer (#15385) (camc314)
  • cc403f5 linter/plugins: Return empty object for unimplemented parserServices (#15364) (magic-akari)

⚡ Performance

  • 25d577e language_server: Start tools in parallel (#15500) (Sysix)
  • 3c57291 linter/plugins: Optimize loops (#15449) (overlookmotel)
  • 3166233 linter/plugins: Remove Arcs (#15431) (overlookmotel)
  • 9de1322 linter/plugins: Lazily deserialize settings JSON (#15395) (overlookmotel)
  • 3049ec2 linter/plugins: Optimize deepFreezeSettings (#15392) (overlookmotel)
  • 444ebfd linter/plugins: Use single object for parserServices (#15378) (overlookmotel)

📚 Documentation

  • 97d2104 linter: Update comment in lint.rs about default value for tsconfig path (#15530) (Connor Shea)
  • 2c6bd9e linter: Always refer as "ES2015" instead of "ES6" (#15411) (sapphi-red)
  • a0c5203 linter/import/named: Update "ES7" comment in examples (#15410) (sapphi-red)
  • 3dc24b5 linter,minifier: Always refer as "ES Modules" instead of "ES6 Modules" (#15409) (sapphi-red)
  • 2ad77fb linter/no-this-before-super: Correct "Why is this bad?" section (#15408) (sapphi-red)
  • 57f0ce1 linter: Add backquotes where appropriate (#15407) (sapphi-red)

Oxfmt v0.12.0

... (truncated)

Changelog

Sourced from oxlint's changelog.

[1.72.0] - 2026-06-29

🚀 Features

  • 1c8f50c linter: Add schema for eslint/no-restricted-import (#23642) (Sysix)

🐛 Bug Fixes

  • 742be36 refactor/node/handle-callback-err: Reject invalid regex config (#23740) (camc314)
Commits

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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/cli/package.json | 6 +- apps/docs/package.json | 4 +- pnpm-lock.yaml | 755 +++++++++++++++++++++-------------------- pnpm-workspace.yaml | 6 +- 4 files changed, 394 insertions(+), 377 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index e22b4791bb..c184c44d66 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -43,8 +43,8 @@ "jose": "^6.2.3" }, "devDependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.3.195", - "@anthropic-ai/sdk": "^0.106.0", + "@anthropic-ai/claude-agent-sdk": "^0.3.196", + "@anthropic-ai/sdk": "^0.107.0", "@clack/prompts": "^1.6.0", "@effect/atom-react": "catalog:", "@effect/platform-bun": "catalog:", @@ -76,7 +76,7 @@ "oxlint-tsgolint": "catalog:", "pg": "^8.22.0", "pg-copy-streams": "^7.0.0", - "posthog-node": "^5.38.6", + "posthog-node": "^5.38.8", "react": "^19.2.7", "react-devtools-core": "^7.0.1", "semantic-release": "^25.0.5", diff --git a/apps/docs/package.json b/apps/docs/package.json index 898d807b07..492c9218e3 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -8,9 +8,9 @@ "build": "bun run generate && next build" }, "dependencies": { - "fumadocs-core": "^16.10.6", + "fumadocs-core": "^16.10.7", "fumadocs-mdx": "^15.0.13", - "fumadocs-ui": "^16.10.6", + "fumadocs-ui": "^16.10.7", "next": "^16.2.9", "react": "^19.2.7", "react-dom": "^19.2.7" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae8e53cb34..a8a9506881 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,8 +37,8 @@ catalogs: specifier: ^1.3.14 version: 1.3.14 '@typescript/native-preview': - specifier: 7.0.0-dev.20260628.1 - version: 7.0.0-dev.20260628.1 + specifier: 7.0.0-dev.20260629.1 + version: 7.0.0-dev.20260629.1 '@vitest/coverage-istanbul': specifier: ^4.1.9 version: 4.1.9 @@ -52,11 +52,11 @@ catalogs: specifier: ^23.0.0 version: 23.0.1 oxfmt: - specifier: ^0.56.0 - version: 0.56.0 + specifier: ^0.57.0 + version: 0.57.0 oxlint: - specifier: ^1.70.0 - version: 1.71.0 + specifier: ^1.72.0 + version: 1.73.0 oxlint-tsgolint: specifier: ^0.23.0 version: 0.23.0 @@ -97,11 +97,11 @@ importers: version: 6.2.3 devDependencies: '@anthropic-ai/claude-agent-sdk': - specifier: ^0.3.195 - version: 0.3.195(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) + specifier: ^0.3.196 + version: 0.3.196(@anthropic-ai/sdk@0.107.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) '@anthropic-ai/sdk': - specifier: ^0.106.0 - version: 0.106.0(zod@4.4.3) + specifier: ^0.107.0 + version: 0.107.0(zod@4.4.3) '@clack/prompts': specifier: ^1.6.0 version: 1.6.0 @@ -155,7 +155,7 @@ importers: version: 19.2.17 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260628.1 + version: 7.0.0-dev.20260629.1 '@vercel/detect-agent': specifier: ^1.2.3 version: 1.2.3 @@ -182,10 +182,10 @@ importers: version: 6.23.0 oxfmt: specifier: 'catalog:' - version: 0.56.0 + version: 0.57.0 oxlint: specifier: 'catalog:' - version: 1.71.0(oxlint-tsgolint@0.23.0) + version: 1.73.0(oxlint-tsgolint@0.23.0) oxlint-tsgolint: specifier: 'catalog:' version: 0.23.0 @@ -196,8 +196,8 @@ importers: specifier: ^7.0.0 version: 7.0.0 posthog-node: - specifier: ^5.38.6 - version: 5.38.6 + specifier: ^5.38.8 + version: 5.38.8 react: specifier: ^19.2.7 version: 19.2.7 @@ -259,7 +259,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260628.1 + version: 7.0.0-dev.20260629.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -268,10 +268,10 @@ importers: version: 6.23.0 oxfmt: specifier: 'catalog:' - version: 0.56.0 + version: 0.57.0 oxlint: specifier: 'catalog:' - version: 1.71.0(oxlint-tsgolint@0.23.0) + version: 1.73.0(oxlint-tsgolint@0.23.0) oxlint-tsgolint: specifier: 'catalog:' version: 0.23.0 @@ -282,14 +282,14 @@ importers: apps/docs: dependencies: fumadocs-core: - specifier: ^16.10.6 - version: 16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + specifier: ^16.10.7 + version: 16.10.7(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) fumadocs-mdx: specifier: ^15.0.13 - version: 15.0.13(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(rolldown@1.0.2)(vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) + version: 15.0.13(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.10.7(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(rolldown@1.0.2)(vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) fumadocs-ui: - specifier: ^16.10.6 - version: 16.10.6(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: ^16.10.7 + version: 16.10.7(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.10.7(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next: specifier: ^16.2.9 version: 16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -339,7 +339,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260628.1 + version: 7.0.0-dev.20260629.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -348,10 +348,10 @@ importers: version: 6.23.0 oxfmt: specifier: 'catalog:' - version: 0.56.0 + version: 0.57.0 oxlint: specifier: 'catalog:' - version: 1.71.0(oxlint-tsgolint@0.23.0) + version: 1.73.0(oxlint-tsgolint@0.23.0) oxlint-tsgolint: specifier: 'catalog:' version: 0.23.0 @@ -381,7 +381,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260628.1 + version: 7.0.0-dev.20260629.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -390,10 +390,10 @@ importers: version: 6.23.0 oxfmt: specifier: 'catalog:' - version: 0.56.0 + version: 0.57.0 oxlint: specifier: 'catalog:' - version: 1.71.0(oxlint-tsgolint@0.23.0) + version: 1.73.0(oxlint-tsgolint@0.23.0) oxlint-tsgolint: specifier: 'catalog:' version: 0.23.0 @@ -431,7 +431,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260628.1 + version: 7.0.0-dev.20260629.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -440,10 +440,10 @@ importers: version: 6.23.0 oxfmt: specifier: 'catalog:' - version: 0.56.0 + version: 0.57.0 oxlint: specifier: 'catalog:' - version: 1.71.0(oxlint-tsgolint@0.23.0) + version: 1.73.0(oxlint-tsgolint@0.23.0) oxlint-tsgolint: specifier: 'catalog:' version: 0.23.0 @@ -471,7 +471,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260628.1 + version: 7.0.0-dev.20260629.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -480,10 +480,10 @@ importers: version: 6.23.0 oxfmt: specifier: 'catalog:' - version: 0.56.0 + version: 0.57.0 oxlint: specifier: 'catalog:' - version: 1.71.0(oxlint-tsgolint@0.23.0) + version: 1.73.0(oxlint-tsgolint@0.23.0) oxlint-tsgolint: specifier: 'catalog:' version: 0.23.0 @@ -523,7 +523,7 @@ importers: version: 1.3.14 '@typescript/native-preview': specifier: 'catalog:' - version: 7.0.0-dev.20260628.1 + version: 7.0.0-dev.20260629.1 '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.9(vitest@4.1.9) @@ -532,10 +532,10 @@ importers: version: 6.23.0 oxfmt: specifier: 'catalog:' - version: 0.56.0 + version: 0.57.0 oxlint: specifier: 'catalog:' - version: 1.71.0(oxlint-tsgolint@0.23.0) + version: 1.73.0(oxlint-tsgolint@0.23.0) oxlint-tsgolint: specifier: 'catalog:' version: 0.23.0 @@ -570,60 +570,60 @@ packages: resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==} engines: {node: '>=18'} - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.195': - resolution: {integrity: sha512-WIMM/8HRCLsTDHFTIwQvvE8WCA/oaMJtdQxsP7iNyfzIGwXbuOyU95V8vYIhZfaO2yaSpbBRncunq4CtR5H4ng==} + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.196': + resolution: {integrity: sha512-k1MKRDhSiNKpkTwhtU8QKGzfuRfr3YXS6oqsTuldMROX56L5iMXjzC7AYU1/KmPTeMl3SCQBQeDu0i8ciA0hQA==} cpu: [arm64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.195': - resolution: {integrity: sha512-RY7DB+4LXosE0MJ+XELmakfPrDN1YX4lkk9CTDm28jGCVcESRz9kAEqbyaiC48dZcmN9V1NCLutzINGdcr1TBg==} + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.196': + resolution: {integrity: sha512-bBwx/7yKZMQ9NSUt4bg8P+zp6pgd/O/DTkzdqsRIivBvARmwo1QY/2qGrLO8RH0T8CG2lTjFNfDfOlJWbAkvAg==} cpu: [x64] os: [darwin] - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.195': - resolution: {integrity: sha512-ZmyBA/AFzhgutcxb7dbhCm6GTjJytwNYXTxJoKE2B3A409WCYccjMqeji6vCMNxyyfylglGo5D8dVMIxW9aoug==} + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.196': + resolution: {integrity: sha512-BhLxfx4j6mC3Uzmve1IbhFS1uvNlwATeo6uWyYDOMW4n3XKjiSgjD8bTfkamijrxCMvJo5swLju2y14ayDsokA==} cpu: [arm64] os: [linux] libc: [musl] - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.195': - resolution: {integrity: sha512-JuIq5Fnz/F1snl0aqi1gcuRZqPWoPNrL9dJ0DuievCxKkO8hnEz/Mmn5Zos7x1X8HE//ZnEvmQXoEQEZXonJew==} + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.196': + resolution: {integrity: sha512-fR5fy+pSQSpKZK0zTtAl3LZEGQTuwVK7svutH1bZUS5RGT2HdUWc71oltSZgW4upGaslj+gyusHGJHA5eAc+dw==} cpu: [arm64] os: [linux] libc: [glibc] - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.195': - resolution: {integrity: sha512-nf8Q/LauB+ZOC6QDjxNhbsvwUtYjKYnaWJLTYFwhkmsLujePnety1AtT/1ubaUoq5AM1j297DhMlYTasa79OUA==} + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.196': + resolution: {integrity: sha512-EOiNbxCXQLYzV7SQhMWkUC1ScWyTw/Qp+JyV2sEmIbzl/e7KMOxNE9J20k+lpPs6CXdxVzuMwH7yAGuDu5y+IQ==} cpu: [x64] os: [linux] libc: [musl] - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.195': - resolution: {integrity: sha512-s1lNi1cL93luoqsItH+fNO4KpIhdkvnVhWGGQUQ/8ftwa2gfmcIQnOg1hG8Ks+KzeD3UUQ8L9YEVHVADnFI/9A==} + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.196': + resolution: {integrity: sha512-9spZON7/tn0q9J+jICrdfHi7o7Fmjs9pIohCCxL+Yv7HbBXWVtEYJbYipBGLTl8ICG3mgPeEVMNsvOuh/jDTuA==} cpu: [x64] os: [linux] libc: [glibc] - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.195': - resolution: {integrity: sha512-hbkDE+xPIZzRWm+D+BKrH9uJH6USIZdDIlsyrIlGi3JFHoieYoA1vdUNyldSS9+F3ZqQtfPjr2Qy08IVB6akYA==} + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.196': + resolution: {integrity: sha512-0xXkAWlDof/qFi3k5KJZ5WYbgp1X8hZHjyasOWTZWAmldoYaENI0vkL+PVMarvoAFYRRqcWoS81FSgm0QgH2sA==} cpu: [arm64] os: [win32] - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.195': - resolution: {integrity: sha512-av0piEB3X1Dzhpr8A+DqHVZ9y8s1jpn8enzwX0TKKUPBn5IqLTWC7wD6v66aoUgu4f+g4ThZirmDZA6shyPEZQ==} + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.196': + resolution: {integrity: sha512-FxWLA3aOYgDf2J0o6Ov1/wgg9X6RkrgnX4ifUWO1i3+6mbc4efNLHkDZLxCHEnQgW8yBidX+iro19f9k64vV8A==} cpu: [x64] os: [win32] - '@anthropic-ai/claude-agent-sdk@0.3.195': - resolution: {integrity: sha512-FVmXu9pvOMbuBKWrF8YsYQdQ/upOpv5rS8lFAnFO5jbyXT/2hN7kEPd2vd2GJpaMvNcO/KptyQUK5AxjjTz3+w==} + '@anthropic-ai/claude-agent-sdk@0.3.196': + resolution: {integrity: sha512-yqsp1/04T2/tJ54jz+7YLsTZOPeQ/myUCrv17/IVZ9dbl+izIMP9ULuLpPCH5pj5msXxR80es+hpVgHoFuNm0A==} engines: {node: '>=18.0.0'} peerDependencies: '@anthropic-ai/sdk': '>=0.93.0' '@modelcontextprotocol/sdk': ^1.29.0 zod: ^4.0.0 - '@anthropic-ai/sdk@0.106.0': - resolution: {integrity: sha512-ufwVvYNDBj2dzOGupBCTaNzBLxqcTnGOzI4z8Wouxlt+mT3J3HuOmatgCy1VmwCHOUueqZ41ERhm0O99OUcbWA==} + '@anthropic-ai/sdk@0.107.0': + resolution: {integrity: sha512-RWDWyvIeZnatUTzyX8+ayFzAqqLyoDHKnDEODFyW8H89zH+qEsh5h6XAmnbHY5DCoa58o3rjuNe3F3Hg851ayA==} hasBin: true peerDependencies: zod: ^3.25.0 || ^4.0.0 @@ -1730,124 +1730,124 @@ packages: cpu: [x64] os: [win32] - '@oxfmt/binding-android-arm-eabi@0.56.0': - resolution: {integrity: sha512-CSCxi7ovYojgfdPOdUb9T508HKeAdDIKeRGg7x8IZwVJrWz9gVgX7MbUnFqtQAE4QvoNo07mj2JlwnOzJw4qqA==} + '@oxfmt/binding-android-arm-eabi@0.57.0': + resolution: {integrity: sha512-qVBsEO+KugOsCmUHcO8iqNnqc65p7PCKpCs8M66mPZ+Ri+CWbcpoQOEJBg2OTu03+0qu++NK1jj6IzvQVs0Sig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.56.0': - resolution: {integrity: sha512-HYJFnd+PkDwf6S9ZPGzXXtjNqvRWFnnhdbWaouh4mi/SxU8wmDuzlMn3xo/wDTGnr4Q1VA7ZzOaE/D4biW0W6A==} + '@oxfmt/binding-android-arm64@0.57.0': + resolution: {integrity: sha512-mp6PibWbao3aizijcheOeHQaYEhcUAt8pwLniYbtLfHxL/psFF0BykAwCj+s3c6qIpa8yN8keZICWrqtZ70w8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.56.0': - resolution: {integrity: sha512-sftR/bEOr+t1gs+evwsHi/Xbq2FAPA2uU3VMr8n6ZU9PoK/IMSfnfu7+OEe/uy1+knhrFl4Wvy7Vkm3uo9mJ7g==} + '@oxfmt/binding-darwin-arm64@0.57.0': + resolution: {integrity: sha512-T+0stuCBqmUVY+aMIvrgXhzGhHO3sD5tNiiEcYqgSdPsnukskQqn2u5qOVD0sv1l7RLdFS5Z/f5Wi9Ktyjr3Eg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.56.0': - resolution: {integrity: sha512-z66SdjLqa3MUPKvTp3Mbb5nSjKSbnYxJGeB+Wx987s8T5hPcIRiBMfnJ6zcPgYtQn3x5xjvdzNVkXrSeYH6ZFg==} + '@oxfmt/binding-darwin-x64@0.57.0': + resolution: {integrity: sha512-O+3JbqWs/mCI2oi4xfhRO2IVPFJNDDEBV8Odo+ZpmsUOeKJfjXoNH7nDmBEQcDgK7NfjDIyE7kRgYSZcTLDO0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.56.0': - resolution: {integrity: sha512-t2tkrV1vtZyaItSQ71dTi2ZVKZEI39b/LqLT12V5KMfIeXK6N32TUC1jhOXKVQmhECq9j2ZXMQV3JeT1kh9Vmg==} + '@oxfmt/binding-freebsd-x64@0.57.0': + resolution: {integrity: sha512-pxwhxVC+JkLX9twOQ/8C/vbuOQcMZyKIDmiRDZfO7yITuVcIdZCiLRqqf4QOxb2+8FWrRXzQpm+1DBKcMpHSSQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.56.0': - resolution: {integrity: sha512-+gCy+Tp3RHeXQ9y/QrS76lXIpZkbziTyp6hIgjB2MssCwfMph3vG/GEfkhO34Rai1vhYIaUkvv8UT1BcDorJPw==} + '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': + resolution: {integrity: sha512-pxBU4zH2imB/MDBfth2rOMeVxXUMjRQLCazagwLARIFH3hVlxZJBlM4nSnHXaIHJK4/qezoFCIORN6AY8Mra4A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.56.0': - resolution: {integrity: sha512-0kKkVvQ2I+FJ2sxQyUu1zJ0yWP5kcWse/yVFnGQSFCXMwSSkfEaUGu0dW774O7nyy3jrcBGap7OSc8dZmU/CdA==} + '@oxfmt/binding-linux-arm-musleabihf@0.57.0': + resolution: {integrity: sha512-JAprOzt8tycYou36ZgEw14DlRHTiN8qdtKANdV3VZIRIvTI/lh/cX13c9pJ/EnDk2GT3FASH7KvCgQ2AufAifQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.56.0': - resolution: {integrity: sha512-npkA2siMbyWRh+wEhi1aTAx4RirukGcGNt8V4Ch86pG+xU9aurqS1MZOnKYMu03ISwat3rB6zkQx51SsB9obNw==} + '@oxfmt/binding-linux-arm64-gnu@0.57.0': + resolution: {integrity: sha512-ajtjaxSaj9xl4BW7REt+Cef/ttzbAq00Bq4z7JUDZEfgFXdwSjH8K9bF+IcIJzZB9lKqMfQ4eHuSFOvvlvtqOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.56.0': - resolution: {integrity: sha512-UekqOjGkV4/MkqreCV9SPIB2jlR3/HbXrmhV1rVXJZ9wfDXMyCMriLtq3tHqLY4PkbVWNtfcm1kMojJ26KLSJw==} + '@oxfmt/binding-linux-arm64-musl@0.57.0': + resolution: {integrity: sha512-p4Y/+RYk9Bk5WO+zHSUXAClRmZ2fbJCejMuCAsU2HhyME4jqf6Ftt/mJYEwIah1wGCBDYOB7wEGV1x5bCEZ6hA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.56.0': - resolution: {integrity: sha512-XSzveSpeZMD5XJpew5lRFVtNnT04xd3rJxENXmk7wkZzN9oWzv2aFJyoNDhOtoz69BYaS/fg4SYl+CfEZRpB0Q==} + '@oxfmt/binding-linux-ppc64-gnu@0.57.0': + resolution: {integrity: sha512-By6tRALAZsno0F4zedmtG+wdMvJiJmJoXM4d3+A9zHE4HRXLqXITwRH8mgrlcXc5yJM2g2W3riRPwTYdgemZLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.56.0': - resolution: {integrity: sha512-EkQ0nJa7k7HDDIVuPF7WY+k4k+bzdclLYtyIXNt7/OqVghfNiMym6YGppFBgx1XRIHW6QylxBz5OogumPjPJbQ==} + '@oxfmt/binding-linux-riscv64-gnu@0.57.0': + resolution: {integrity: sha512-skYeG+RgvyzspqVEBsEprL90OYYZfoVNqB3HcCNR6QDJyXKOzfDRT3zncnHmUaFluIlBHuY23mU1b5WGgR98hA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.56.0': - resolution: {integrity: sha512-dyjAGW8jKRge0ik6U/dgvQG0nVpA3iBlRskQTz5qJLvQWIrySxX5jpqzPetLBNIIZ231KA82fDdi1nLTk8ENCw==} + '@oxfmt/binding-linux-riscv64-musl@0.57.0': + resolution: {integrity: sha512-FFgACrZOXAXUh5KQh2mt1CDOVOZmn+QzHP71wM9QobNwyQvoFfyAeefVUltW83g3sm7LTiH3yfFqLLVUpA5ZFQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.56.0': - resolution: {integrity: sha512-60ZGH3LtfqlW8X6vcLdSFY4lvCQYINurttYBKaALnHCDVAUCYJ1LsUgS6p1XOzVlzEDx3yNUZvDF1Lvt59zoZw==} + '@oxfmt/binding-linux-s390x-gnu@0.57.0': + resolution: {integrity: sha512-Nm/BAOfQeFiiKd502mZn/GAVKJwtd0RdCg17G3Wz/WSOIQmDi3+7/SZH4BHn1Ye5KvTVH3ua8WvfwLLycNIuvA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.56.0': - resolution: {integrity: sha512-u1suj1tgJHK4ZqB7buCtdbNef2n8+d0lXTPJwLHNmtyK6p+DTpsaoDvmqhQrA56fgKYv4LuRxNtL8YooebKOew==} + '@oxfmt/binding-linux-x64-gnu@0.57.0': + resolution: {integrity: sha512-BiSy5Ku3mQqyxS6YIqAJgd403wEUWvI7kerfzPxc2l/txZVmZM0pSj7oDM+4bGBExowxOi7o73jEam1W0EDTZg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.56.0': - resolution: {integrity: sha512-aYGLvlQHt80y+qKEtfJY/Nm27G0125Lv+qyh9SJ4Cjc6lXnXjD+ndfhqQnbV24POpMi7rNRi0jvx/0d70FRpCQ==} + '@oxfmt/binding-linux-x64-musl@0.57.0': + resolution: {integrity: sha512-BCRkJiotz5s9afLYD2LuMvzAoDYx9H17E/YbDyu4xK7l4zHDPeny9ErSXL//i/nJyaOwRk08x4b8cgJC00+JDg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.56.0': - resolution: {integrity: sha512-H/re/gO+7ysVc+kywHNuzY3C33EN9sQcZhg0kp1ZwOZl7y998ZE5mhnBiuGR/nYI0pqLL5xQfrHVUOJ/cIJsCA==} + '@oxfmt/binding-openharmony-arm64@0.57.0': + resolution: {integrity: sha512-4Oaxe1qrGgXfpCJ1C/ERJ2iCtV2rN1R79ga9fsfyVHfSQRu/hVW780u2KDqZWFZ/iGTHODJji0JemxqFZ63eIQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.56.0': - resolution: {integrity: sha512-6qLNXfXmtAs8jXDvYMkxk6Wec5SUJoew+ZX1uOZmqaR7ks0EJFbAohuOCELDyJMWyVlxotVG8Xf8m74Bfq0O2w==} + '@oxfmt/binding-win32-arm64-msvc@0.57.0': + resolution: {integrity: sha512-MYLAsDnhdNsSGheLYhWgbk0vfIrlS84iQYun/y21fX6u0jj8iBtYtbpZMdiqYeuf8U12eVPUjVY2xE2NrCfJ0g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.56.0': - resolution: {integrity: sha512-UXEXuKphAe15bsob4AswNMArCw38XSmUIs3wk1s6e6MX9OWGW/IRWU95s1hZDiVg09STy1jHgyN2qkqbu1FT0w==} + '@oxfmt/binding-win32-ia32-msvc@0.57.0': + resolution: {integrity: sha512-PBwdzZALJY/jcCx2E6is0yu+cuVXeySTDmwuseD+9j0mHqlRNxwlKgsyRTBed/woPeqfVfuXfWjoq4Cx2Zt3Eg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.56.0': - resolution: {integrity: sha512-HPyNDjky+NIOuaMvHZflR+kst3YWdUOH2JUQYkf99grqZ5mEBTQM6h9iGy501Z8Xt5xMScrwHOuVCOlqDrktRw==} + '@oxfmt/binding-win32-x64-msvc@0.57.0': + resolution: {integrity: sha512-bQJdH9i4RRfw55jm7+8/xS7GzHLLTbHx4huhrrDxQJaJtbSDbsyOnODvP1ftT7EG0KFKAYO2S+q6AcioXODx8w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1882,124 +1882,124 @@ packages: cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.71.0': - resolution: {integrity: sha512-ImGmd1njEg4FEJH03jhRnveEegtO3czCtfptvaHivKAZQIYATbVFBrrzbaYMYv0oJioTnxZAZVSyV+oL7W8S2g==} + '@oxlint/binding-android-arm-eabi@1.73.0': + resolution: {integrity: sha512-HZQRN/UMBu+Ut+/9MiAChkbP4qZqrNOWBcNI45vOT40GVhbGR0JgHB87L48D4iAqFQIdVmeQYtV9RF89AjTKkg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.71.0': - resolution: {integrity: sha512-4A5BEexBrwY1YFF8Kiq/lp/wQPRG79G3BWIE1FuWaM5MvmpYSd+7ZySVcKkHdwo0UDzdQGddp6pD9mpctMqLnw==} + '@oxlint/binding-android-arm64@1.73.0': + resolution: {integrity: sha512-Gp+KJRylv2aW7thRpG5p1KTxZq4ZJFbWowrKzufNq9d3ssl3r3JviYV45/+p+7CN1Nv0zDd1e8Ex0b/HUDq4TQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.71.0': - resolution: {integrity: sha512-9wJA9GJulLwS2usU3CEisI/ESDO1n1z9eyTCvApMDrAkbJ1ve0mORgTMjcWWsKxkzkeZ2N/Gpra5IQE7x8tYgQ==} + '@oxlint/binding-darwin-arm64@1.73.0': + resolution: {integrity: sha512-3de96NdtXhxERMjIz7wsp2HYMY6pMQycGxFWac2mFecAx6VeARF/IqFb1QIaqiCRIdfzBwzTed+pCTCoiS+CYA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.71.0': - resolution: {integrity: sha512-PlLCjS06V0PeJMAJwzjrExw1sYNW9Gch3JtNlcwwZDXGlTYDuwHNN89zYH8LTXFfgkVtsYvs2nv0FqrzyuFDzg==} + '@oxlint/binding-darwin-x64@1.73.0': + resolution: {integrity: sha512-5zx/uPW32TiaOeVY1dQ/H5iOf0K1HOdFKOJhLqGl4o63+i1fpzoqqu/mKtd7OFgFjNCdhlyTGgjVkQTZm1ELcg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.71.0': - resolution: {integrity: sha512-Lhil7bWre0ncxbUoDoxfS0JzpTz17BRQKW7iwoAUY8GJ66+WwJEfYPCFJ1P0WgVZR5/O/b3Q2pENlHOjeXLOGQ==} + '@oxlint/binding-freebsd-x64@1.73.0': + resolution: {integrity: sha512-qNe4gKHaGnLuZJ8toUg90JAa0S2vTVvDw+0bRi3q1avXZXDT4u5mMeECf3nD4HYrbdn1O7dXqWut4onY/yx/Xg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.71.0': - resolution: {integrity: sha512-Oo9/L58PYD3RC0x05d2upAPLllHytTjHQGsnC06P6Ynn7jKkp5mdImQxXdJ3+FnBaKspNpGogzgVsi6g872LiA==} + '@oxlint/binding-linux-arm-gnueabihf@1.73.0': + resolution: {integrity: sha512-cCehYh5hTbfShm/fxTD6wwrGUWIpvX+N5OxmAMhFhDeTGXvw+BeNj889tpxsFQ9ZLatQ6wImuY8tsKLZ+FMz7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.71.0': - resolution: {integrity: sha512-mSHfyfgJrEbyIR29ejaeS50BdPk+GoNPlC1dckpDiUZbJAIel68sjSMdOt4WY0/gva+ECC7FNITQkxMJU+vSBw==} + '@oxlint/binding-linux-arm-musleabihf@1.73.0': + resolution: {integrity: sha512-d5j5GDU/2dMgjVhw7TQT9ITrsIr1Y02KEXKyVGIXUkD+KiaxE9TP65FS2ZdgTBemQvoRL+gSBdbrIm3cQIeacg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.71.0': - resolution: {integrity: sha512-n9yY4M2tiy3aij4AqtlnspzpfdpeT5JQfK2/w2d8oyp5W0FRwOb1dIeX99nORNcxGr08iD9bH8N5XFz3I2iy8w==} + '@oxlint/binding-linux-arm64-gnu@1.73.0': + resolution: {integrity: sha512-Eyf1SrP3+yR1DI3OJgOY2Pvrr9dWP9TK37xPaDYycwTtlGlI45erJAVIfH5/m/xosDt6BupJYEFi47bvbTuuyw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.71.0': - resolution: {integrity: sha512-fJZrs5sDZtTaPIOiemRQQmo82Ezy+vOGXemPc4Ok7iVVsYsFa7SlW6Z5XN819VfsqBHRm3NJ3rTdnR8+bJYJdQ==} + '@oxlint/binding-linux-arm64-musl@1.73.0': + resolution: {integrity: sha512-IlT/OJApEDKaMmCooHuncgJZbbCe7T5QIWmTZBEtYscWvzPQuuEinVcid6kwQRVQOUdb7PUCz4jQHnaYXdfJXw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.71.0': - resolution: {integrity: sha512-cwl7VKGERIy9p+G+AvZdfy/06q0aHXaTt/mMRReC751iuNYJgqKjB7NydXSS30nBT9vtr2tunciOtrR4fD6FUA==} + '@oxlint/binding-linux-ppc64-gnu@1.73.0': + resolution: {integrity: sha512-L+JYcb/vdg5fmcH08V6o0YYLU28cTH1SPNulwJdvK9NK49aXSkYy6oNpKBmddArVOXYqNepriDGiZ04G54kh1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.71.0': - resolution: {integrity: sha512-eZ8ieVXvzGi8jr7+ybQGPK2STw3mldfxZlgA2738iflfB/rzA69sE6m5rDRpQaxC7dpm745Enlh1Tod0QAk9Gg==} + '@oxlint/binding-linux-riscv64-gnu@1.73.0': + resolution: {integrity: sha512-Qtk0g3bKV6OwWjIm7R8kQN1uOZRKQt/MODK2a8QfkwhTpXBD53ozx5XLVWLGDQAVyp2otLW4D2wB98XfAfMPGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.71.0': - resolution: {integrity: sha512-puMDbQYe6+NXwfMusojoA7CXGn2b3utukmd23PQqc1E3XhVCwyZ+FueSMzDYeNgDV2dUfIVXAAKZBcFDeCL6sA==} + '@oxlint/binding-linux-riscv64-musl@1.73.0': + resolution: {integrity: sha512-wX0NQKZVxltkAOVmzFcpOaMpdaUvsq1Eqpx9tkAfl71UdkTlSo1R4AdAnGccR1Fm2+TzFgZ22CyyGuZ41RDr/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.71.0': - resolution: {integrity: sha512-4NJLxBs1ujISCt3L/1FcywLs73PWtJuw+piD6feK2V6h6OS6P7xu9/sWt1DTRLibe6QCzmfZzmM/2HPORoV/Lg==} + '@oxlint/binding-linux-s390x-gnu@1.73.0': + resolution: {integrity: sha512-vPe7UGBMWyiLTtnqS4xxgMQFSFGmtQwhwCxuiw6lXygaO6bVt0D8dFVg8Xv05eaiN3ybC0HXXHUAohFMFvqoCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.71.0': - resolution: {integrity: sha512-cFDaiR8L3430qp88tfZnvFlt3KotFhR/DlbIL0nHOMMYiG/9Wy4l+6f7t8G8pTa9bd8Lt8+M0y/qjRQ/xcB74g==} + '@oxlint/binding-linux-x64-gnu@1.73.0': + resolution: {integrity: sha512-2CwIWr9cemFC/CbRBWZvuk5mffz6ObmfFkfcC/9rTQ7f+icNhYr2kOjf9Rt8lLvugvkdGDOmkoVoFFHh6ClCTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.71.0': - resolution: {integrity: sha512-orfixdt76KlpNly9z0PkWBBNfwjKz+JFVLP/7wnVchlKNU9Dpt9InU/ZggeSej6fC7qwHmHNOGlhLnQXcYoGuA==} + '@oxlint/binding-linux-x64-musl@1.73.0': + resolution: {integrity: sha512-nDadfJgg7NBBxG0N560wOe7LLX5QiYp6qBaI7viuk5EUORFBktU/NfV0MbTqU3gTqQDCh4VyxKdo5VADxk9w8Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.71.0': - resolution: {integrity: sha512-9emQu2lAp6yhPB3XuI+++vR+l/o6JR1X+EpxwcumPdQXBWXEPAsquPGL7l158EqU8SebQMXTUa/S5zN98juyHw==} + '@oxlint/binding-openharmony-arm64@1.73.0': + resolution: {integrity: sha512-wGjJC+NLH9xP+IKGn9RDW94ojJR/wPbg5WCnQjj/oReaOtCQthr8ws1zICe77JFmo4ouUdeTHHZL/ESGiF6Pmw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.71.0': - resolution: {integrity: sha512-bd5kI8spYwTm3BILDtGhi73zoup5dw8MlPQNT8YB3BD5UIsjNe3K9/4ctrzQMX4SZMoK5HgzVLkLJzacEXB7fA==} + '@oxlint/binding-win32-arm64-msvc@1.73.0': + resolution: {integrity: sha512-I7X47GPGljw225YUQ5SbC/rb1Kkdrd0yQf0x+hYxeKS6DpfjMbo9ccQPQ6LNY6BoJQ1sHhgDUGuMn5Vg5gHT6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.71.0': - resolution: {integrity: sha512-W4HvOHGzVLHcrmFu+bMrJlho+/yrlX5ZNdJZqGe8MEldkQG+RHYhxxad9P4jvWAYFmIqUA5i9DQ8QsJqSU9GIw==} + '@oxlint/binding-win32-ia32-msvc@1.73.0': + resolution: {integrity: sha512-5lWj+3h+74Fm1jYOO9qkJA4xkAlZA099DkXppuXsk7UpnpZLttsefrZU469vChGaG6hcSqrkKXQOvMTZtbjeNg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.71.0': - resolution: {integrity: sha512-D2kyEIPHk/G/wiZLnwTVC/sVst+T/lKldVOjAFpgTIBUAOlry72e5OiapDbDBF4LfJLkN5ypJb/8Eu6yJzkveQ==} + '@oxlint/binding-win32-x64-msvc@1.73.0': + resolution: {integrity: sha512-WaNRvh4f6zY9CvUQk2YoA1O90ieWrIklI84+HXFr9Isjz9CSESrdqo/RtIYt4Dll/cAchqGDMehfaZd0vqEFZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -2116,11 +2116,11 @@ packages: '@radix-ui/number@1.1.2': resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==} - '@radix-ui/primitive@1.1.4': - resolution: {integrity: sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==} + '@radix-ui/primitive@1.1.5': + resolution: {integrity: sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==} - '@radix-ui/react-accordion@1.2.15': - resolution: {integrity: sha512-24Zz/0SYx8F2bSVThBnQrdJs2VbKelyuJordcFRRdA0fRAhrq/wSegGCqaQz34VQoiWqSMGYCYXEhynLSlyQlg==} + '@radix-ui/react-accordion@1.2.16': + resolution: {integrity: sha512-BpZJNmetujnGgUI6OX0jEhEmlA46WPqgub8Rv09Kyquwd0cc1ndMKpiPYCjmBU6KSSRPAMtgLpEoZSG/tdNIWQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2145,8 +2145,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collapsible@1.1.15': - resolution: {integrity: sha512-8A1zibu5skAQ+UVbaeNH5hVMibiFCRJzgMuM14LTWGttnTZKQL9jwYnhAbHRuxrtCqPXa4JvvnVUq1pTNgyZYw==} + '@radix-ui/react-collapsible@1.1.16': + resolution: {integrity: sha512-opfXRe6nnzyGmCDPx+l1Aqo/RbqWtQal2FnsBqF9hhePp6j0LsRoBaRxcMOlTv+uYTJVtWYZKg9t9wTe+BA/ZA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2158,8 +2158,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collection@1.1.11': - resolution: {integrity: sha512-djW9+zeg137KQdlPtmE8xnaD+K2rcXXMWFrSg0hsmYZ6HRbdTA7tDHFgpaW9+huWVEu0RCabL+985T4TA0BE7g==} + '@radix-ui/react-collection@1.1.12': + resolution: {integrity: sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2180,8 +2180,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-context@1.1.4': - resolution: {integrity: sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==} + '@radix-ui/react-context@1.2.0': + resolution: {integrity: sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2189,8 +2189,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.18': - resolution: {integrity: sha512-apa28mldjMgORmE6g/w3sCcA0Y9UAVeeDVoozN4i7kOw12mLl9RBchfzK3Nn6qxOWjrZhK1Lfy7f07kyzxtnBw==} + '@radix-ui/react-dialog@1.1.19': + resolution: {integrity: sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2211,8 +2211,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.14': - resolution: {integrity: sha512-4lUhWTWAjbDIqFrAPWJ3WqBOpO5YchVZ88X3nh6H9Lu5AFi5nCUeTPj3D8FSDmabmFeRe9ME0BDA4MwKTha5GQ==} + '@radix-ui/react-dismissable-layer@1.1.15': + resolution: {integrity: sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2233,8 +2233,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-focus-scope@1.1.11': - resolution: {integrity: sha512-Mn88Vg2whaRocGJNOH+DKFqYm6ySFPQaiwHNxZPyjn99B52KAEJWWY9NP83+nWdk2HM3rdov+STu9AG471Rt9w==} + '@radix-ui/react-focus-scope@1.1.12': + resolution: {integrity: sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2255,8 +2255,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-navigation-menu@1.2.17': - resolution: {integrity: sha512-fYeYQvbeNn5AQk2RBbpO7koLm2YbS00UYxC/IL2sgLlninEH5UNIv+X3E0KJ1Vy4WIo+dhN9w8GNqSHhbHWCIg==} + '@radix-ui/react-navigation-menu@1.2.18': + resolution: {integrity: sha512-K9HiuxZ6xCwSaHcIuUpxyhy4w5gpwzWjh9dHTSbMN3Ix4qAyVObS9RlU3zMycb0PO3v9Tpk0BXMwWvXOUbVXew==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2268,8 +2268,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popover@1.1.18': - resolution: {integrity: sha512-qdXDes+eHlnMUGlBAAAe5EG7oOQvqsXuq4mq585diMudg80iB+jHbsSeG3+Q4eWNsogNyhqU2p/3i+Y0iEepqg==} + '@radix-ui/react-popover@1.1.19': + resolution: {integrity: sha512-jkrTdQVxnIB8fpn0NyyxW9CTB5aCXZZelVz5z+Xmii6g5WxMqS3fInNslZ63puP39+Puu4jYohUK31y3dT87gQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2281,8 +2281,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popper@1.3.2': - resolution: {integrity: sha512-3QXNeMkdshed1MR3LNoiCirBywRFPkD8ETJa/HlPuLwSajaQixf2ro+isoDNJlGABg9ug41XuZpINZJIle4XWg==} + '@radix-ui/react-popper@1.3.3': + resolution: {integrity: sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2307,8 +2307,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-presence@1.1.6': - resolution: {integrity: sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==} + '@radix-ui/react-presence@1.1.7': + resolution: {integrity: sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2333,8 +2333,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.14': - resolution: {integrity: sha512-8Qcnx9447tx/aCBgw6Jenfqg4Skq+vqab9mCBmuGNipIS5YXvL275wbKEu7+ICYHIlAPgCduUMJH1XOYewKF6Q==} + '@radix-ui/react-roving-focus@1.1.15': + resolution: {integrity: sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2346,8 +2346,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-scroll-area@1.2.13': - resolution: {integrity: sha512-7tncSubo2G0UY1e8rk+72qe3XRzrGnOLtZQ1PL1KoBfRUNX0NrJT5akb+0kfwSCc3gVR4wdHqyhAQBDpDNOwDw==} + '@radix-ui/react-scroll-area@1.2.14': + resolution: {integrity: sha512-bBODCWZK7JTbQLHs0uIP4f73wIWatakK4OS33UzkR1x897wu0PuO658a3f+6P2GEGyDzGYMuHRatMVoAk9WZTw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2368,8 +2368,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-tabs@1.1.16': - resolution: {integrity: sha512-v3Ab2l7z6U7tRB4xA0IyKdq0OsqaO1o9ZjsIEoKKnSZ/l96mZz8aCTX0NCXw+YVHJXr8Km4d+Mn6/Q8YjXa+gw==} + '@radix-ui/react-tabs@1.1.17': + resolution: {integrity: sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2408,6 +2408,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-is-hydrated@0.1.1': + resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-layout-effect@1.1.2': resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} peerDependencies: @@ -2855,50 +2864,50 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260628.1': - resolution: {integrity: sha512-eHHDHAZjbZ681sHyW87tg8mH4+xIs+Q7cHKIlrdafqeny1KYWorj3O9Qfnjvcl2Yd2Eq+IzJxffF6Tepy13L4Q==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260629.1': + resolution: {integrity: sha512-wXRExZJweYoTzE4atRR7T5HwKJYkl6/KHxON0eF0iy2fvgLXDlyq4AQqhmV8mMx10PQKc/4sNbfhD4kjWWvm8A==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260628.1': - resolution: {integrity: sha512-xIsJSXa0Fsv0pPfQ0YYa7nUQJ/nGRF/r8p60e0Aa29SexxgOXMsu3YhOnUnJEdbvaPzqlKqa1GqNGpbGnLQcLA==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260629.1': + resolution: {integrity: sha512-xkL/tETzUuDxfRkk2rD0/CjjjGLyOLHeE103T52rpgj0VNSXMnn7tTiw+TSdxnS61b8ImOrWgQJujhPFTp0jng==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260628.1': - resolution: {integrity: sha512-p3yj2a70vkaFB3OD+Vt4oNUaiE7I30fwiXs6LVNAW6k0GSHNc4ucYcWVlpcp8+cej9RBQgxMnMH5MSVYmNhUvw==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260629.1': + resolution: {integrity: sha512-y4ey82krq4iLNHML4G1dUObBWY6gNAjVqaUHFDv7+LBu5bfAJy8VClBaLtAJce3siQQeB0/4SkN4XsAa0oZktQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260628.1': - resolution: {integrity: sha512-BoclteE+MBOnfK0Qh21mQgrvYPy/v2k7CPTPufcNp1g1fsSvsF3Xv6K8I/grEjo3ZjNrgIvVxivoAqaQhSMlGA==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260629.1': + resolution: {integrity: sha512-QlTxO+O6yELc0NYZZYIbncZhkhw+K/vBoVg+mKipbEIK5b1E6cwW7ivWIrDp1FfssQ0Wu7zxincPappoci7KNw==} engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260628.1': - resolution: {integrity: sha512-LKNKDoTnM8aacpbt1u8kJR1feXpBuLlvKKbVt0RYBL4j1OA148TXKjLtbVu37I0lcVxjqERYuAybpvut2xq31w==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260629.1': + resolution: {integrity: sha512-ckb4HyugC5beDpOMPOVpEx1H3l2Z2RgsGPxFOLGMU6LLIHQwlCaSdRjSfH8Hq8yffPgKuotTQLdA89cP1IC8xQ==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260628.1': - resolution: {integrity: sha512-XUGCYlDAfeA4PIm7ZSZtVHmvffVoMct0LhTA/CoALhSQFnFnJdipOfsZghSyU6TCpTuzBoOhWCjBufrQC23xOQ==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260629.1': + resolution: {integrity: sha512-Ktgepu9p0blqrbiryzYrw11ddnbESXPdeGKURDG/wXESoHQETsMNxKWhqAo587ItNnWjKOBFxoEM22eP9OzKnw==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260628.1': - resolution: {integrity: sha512-rJMZ+YaRv9XybOZBYAsJt7x/K2IWmX9bgRatHobl0wwkKmfKd3giNnRXcDwOqYeCaWzunCbUhAirUtuUprRcnQ==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260629.1': + resolution: {integrity: sha512-b2wmeIpFJs/peej3NG26320ya+iYQz21JzFYDrnvtsEeR/g66rB9uvp4Owo4J1AG/BcmRWC/nuwrBy3Jdb+UDA==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260628.1': - resolution: {integrity: sha512-359WmBk3vA/bJxfeWyLbFeeejmky7Wssc8HMu0Iabu490WJLj/FqkDC51V65yuDp+anMAEkgeKO43fj6pMb/ZA==} + '@typescript/native-preview@7.0.0-dev.20260629.1': + resolution: {integrity: sha512-KImWFkxGUa/V78bUEAgzeJQT+6wKQXDDYHHrOavof8wnbMCpj86KuPNyBIPQMtoHiz2If8aNTums2m50oE3oqw==} engines: {node: '>=16.20.0'} hasBin: true @@ -4008,8 +4017,8 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - fumadocs-core@16.10.6: - resolution: {integrity: sha512-vd/hidAsC1d8ldrfCvr/vb/H46AC/iRm0Zp4SOWWWfa1iNoFw1mFIRGHwaNDB9ETkZPZ7xUwhi3QkhlXPrYbVw==} + fumadocs-core@16.10.7: + resolution: {integrity: sha512-lR1hDOtJ8ubsLKYH2VMkp+iVZTDdOiMh4StFaWtuTvy8Wfnngz0YSHeFijmm2K+4lg2DLXLMuCEHQ6my54g2Eg==} peerDependencies: '@mdx-js/mdx': '*' '@mixedbread/sdk': 0.x.x @@ -4098,13 +4107,13 @@ packages: vite: optional: true - fumadocs-ui@16.10.6: - resolution: {integrity: sha512-Sgp3r1FKNMnuG1AQlLUDsOcuE9kR3GU6RGuIUC5io3PFUNRc+IE4cjwGYv4i8vZi+VPV1Qqwm8oWztHCkwLvsg==} + fumadocs-ui@16.10.7: + resolution: {integrity: sha512-zE93/DKW5bhedXRKHYg3rhE/juYi+kXx1xl3ey1dArWbCiPx6lq6/4RLswYXS0lQp1W1f8NsbY1TeA8wSQuEvw==} peerDependencies: '@takumi-rs/image-response': '*' '@types/mdx': '*' '@types/react': '*' - fumadocs-core: 16.10.6 + fumadocs-core: 16.10.7 next: 16.x.x react: ^19.2.0 react-dom: ^19.2.0 @@ -5364,8 +5373,8 @@ packages: oxc-resolver@11.21.3: resolution: {integrity: sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==} - oxfmt@0.56.0: - resolution: {integrity: sha512-9Dv0wV3zKiyvhjD7bRKaInKmHQ1sPx3RGOjQkGFJbbdQ16576yf8qhMSO9Q9cvHcs+1NpBsRTkuDDYFFPTJ6gw==} + oxfmt@0.57.0: + resolution: {integrity: sha512-ZB7Bi+rGDSqmVIo9jwcLyFgjxXvQhDdU+jx+ZrVy6VRiVXK2+CHc4hO3J4dUQjHe7V0ymHB+MDuv5z+NhK07HA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -5381,12 +5390,12 @@ packages: resolution: {integrity: sha512-3mBv3CoPbh8dFbzfDGIWa2ytZjn2v+3EX4aKRXjIhsoGFzG8GCjfRirz3rwZf1wYbZzsNLTSgpw8VjQuWdp/jA==} hasBin: true - oxlint@1.71.0: - resolution: {integrity: sha512-U1m1X+C0vDj7DC1e13IoZULzEcPczE7UOMTs8VlZGHUEIUaSTZKo5qkPsQEfzpgnQ29Pea/w3Xntk62UCecxZw==} + oxlint@1.73.0: + resolution: {integrity: sha512-u91G9TJzU6yqKWNZUYprQB07W7YvntZXaRxQ6CkoytepYhLWUXWsr1M8zUJ34VatNPuUAr3Z8GH+O2A331CluQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.22.1' + oxlint-tsgolint: '>=0.24.0' vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: @@ -5658,8 +5667,8 @@ packages: postgres-range@1.1.4: resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} - posthog-node@5.38.6: - resolution: {integrity: sha512-Sm2mCAa9/lTTYppnKyy0AhQrriq8fOd8B2vwd3EE/9uihyIx9qkJ1xGYbvADxQJlF7HqM1/TIFCKsI0JvF8gjQ==} + posthog-node@5.38.8: + resolution: {integrity: sha512-AWsp9Tigf4iZepabPErAt/2sLTGwJ7w6631JP+3ifDqWzaBPyzcukD7cTPeoNDKGwJreQ69Ju4l53n9g2+X6nQ==} engines: {node: ^20.20.0 || >=22.22.0} peerDependencies: rxjs: ^7.0.0 @@ -6845,46 +6854,46 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 - '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.195': + '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.196': optional: true - '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.195': + '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.196': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.195': + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.196': optional: true - '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.195': + '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.196': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.195': + '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.196': optional: true - '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.195': + '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.196': optional: true - '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.195': + '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.196': optional: true - '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.195': + '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.196': optional: true - '@anthropic-ai/claude-agent-sdk@0.3.195(@anthropic-ai/sdk@0.106.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': + '@anthropic-ai/claude-agent-sdk@0.3.196(@anthropic-ai/sdk@0.107.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': dependencies: - '@anthropic-ai/sdk': 0.106.0(zod@4.4.3) + '@anthropic-ai/sdk': 0.107.0(zod@4.4.3) '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) zod: 4.4.3 optionalDependencies: - '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.195 - '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.195 - '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.195 - '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.195 - '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.195 - '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.195 - '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.195 - '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.195 + '@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.196 + '@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.196 + '@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.196 + '@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.196 + '@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.196 + '@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.196 + '@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.196 + '@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.196 - '@anthropic-ai/sdk@0.106.0(zod@4.4.3)': + '@anthropic-ai/sdk@0.107.0(zod@4.4.3)': dependencies: json-schema-to-ts: 3.1.1 standardwebhooks: 1.0.0 @@ -7792,61 +7801,61 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.21.3': optional: true - '@oxfmt/binding-android-arm-eabi@0.56.0': + '@oxfmt/binding-android-arm-eabi@0.57.0': optional: true - '@oxfmt/binding-android-arm64@0.56.0': + '@oxfmt/binding-android-arm64@0.57.0': optional: true - '@oxfmt/binding-darwin-arm64@0.56.0': + '@oxfmt/binding-darwin-arm64@0.57.0': optional: true - '@oxfmt/binding-darwin-x64@0.56.0': + '@oxfmt/binding-darwin-x64@0.57.0': optional: true - '@oxfmt/binding-freebsd-x64@0.56.0': + '@oxfmt/binding-freebsd-x64@0.57.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.56.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.56.0': + '@oxfmt/binding-linux-arm-musleabihf@0.57.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.56.0': + '@oxfmt/binding-linux-arm64-gnu@0.57.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.56.0': + '@oxfmt/binding-linux-arm64-musl@0.57.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.56.0': + '@oxfmt/binding-linux-ppc64-gnu@0.57.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.56.0': + '@oxfmt/binding-linux-riscv64-gnu@0.57.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.56.0': + '@oxfmt/binding-linux-riscv64-musl@0.57.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.56.0': + '@oxfmt/binding-linux-s390x-gnu@0.57.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.56.0': + '@oxfmt/binding-linux-x64-gnu@0.57.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.56.0': + '@oxfmt/binding-linux-x64-musl@0.57.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.56.0': + '@oxfmt/binding-openharmony-arm64@0.57.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.56.0': + '@oxfmt/binding-win32-arm64-msvc@0.57.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.56.0': + '@oxfmt/binding-win32-ia32-msvc@0.57.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.56.0': + '@oxfmt/binding-win32-x64-msvc@0.57.0': optional: true '@oxlint-tsgolint/darwin-arm64@0.23.0': @@ -7867,61 +7876,61 @@ snapshots: '@oxlint-tsgolint/win32-x64@0.23.0': optional: true - '@oxlint/binding-android-arm-eabi@1.71.0': + '@oxlint/binding-android-arm-eabi@1.73.0': optional: true - '@oxlint/binding-android-arm64@1.71.0': + '@oxlint/binding-android-arm64@1.73.0': optional: true - '@oxlint/binding-darwin-arm64@1.71.0': + '@oxlint/binding-darwin-arm64@1.73.0': optional: true - '@oxlint/binding-darwin-x64@1.71.0': + '@oxlint/binding-darwin-x64@1.73.0': optional: true - '@oxlint/binding-freebsd-x64@1.71.0': + '@oxlint/binding-freebsd-x64@1.73.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.71.0': + '@oxlint/binding-linux-arm-gnueabihf@1.73.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.71.0': + '@oxlint/binding-linux-arm-musleabihf@1.73.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.71.0': + '@oxlint/binding-linux-arm64-gnu@1.73.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.71.0': + '@oxlint/binding-linux-arm64-musl@1.73.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.71.0': + '@oxlint/binding-linux-ppc64-gnu@1.73.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.71.0': + '@oxlint/binding-linux-riscv64-gnu@1.73.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.71.0': + '@oxlint/binding-linux-riscv64-musl@1.73.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.71.0': + '@oxlint/binding-linux-s390x-gnu@1.73.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.71.0': + '@oxlint/binding-linux-x64-gnu@1.73.0': optional: true - '@oxlint/binding-linux-x64-musl@1.71.0': + '@oxlint/binding-linux-x64-musl@1.73.0': optional: true - '@oxlint/binding-openharmony-arm64@1.71.0': + '@oxlint/binding-openharmony-arm64@1.73.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.71.0': + '@oxlint/binding-win32-arm64-msvc@1.73.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.71.0': + '@oxlint/binding-win32-ia32-msvc@1.73.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.71.0': + '@oxlint/binding-win32-x64-msvc@1.73.0': optional: true '@parcel/watcher-android-arm64@2.5.6': @@ -8006,15 +8015,15 @@ snapshots: '@radix-ui/number@1.1.2': {} - '@radix-ui/primitive@1.1.4': {} + '@radix-ui/primitive@1.1.5': {} - '@radix-ui/react-accordion@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-accordion@1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collapsible': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-collection': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-collapsible': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -8034,13 +8043,13 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-collapsible@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-collapsible@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.5 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) @@ -8050,10 +8059,10 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-collection@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-collection@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 @@ -8068,23 +8077,23 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-context@1.1.4(@types/react@19.2.17)(react@19.2.7)': + '@radix-ui/react-context@1.2.0(@types/react@19.2.17)(react@19.2.7)': dependencies: react: 19.2.7 optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-dialog@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-dialog@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.5 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) @@ -8102,9 +8111,9 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-dismissable-layer@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-dismissable-layer@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.5 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) @@ -8121,7 +8130,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-focus-scope@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-focus-scope@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -8139,16 +8148,16 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-navigation-menu@1.2.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-navigation-menu@1.2.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) @@ -8161,18 +8170,18 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-popover@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-popover@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.5 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-popper': 1.3.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-popper': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) @@ -8184,12 +8193,12 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-popper@1.3.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-popper@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-arrow': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) @@ -8212,7 +8221,7 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-presence@1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-presence@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 @@ -8230,31 +8239,33 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-roving-focus@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-roving-focus@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-collection': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-scroll-area@1.2.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-scroll-area@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/number': 1.1.2 - '@radix-ui/primitive': 1.1.4 + '@radix-ui/primitive': 1.1.5 '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) @@ -8271,15 +8282,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-tabs@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@radix-ui/react-tabs@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.4 - '@radix-ui/react-context': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-roving-focus': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -8308,6 +8319,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 + '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: react: 19.2.7 @@ -8731,36 +8748,36 @@ snapshots: dependencies: '@types/node': 26.0.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260628.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260629.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260628.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260629.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260628.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260629.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260628.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260629.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260628.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260629.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260628.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260629.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260628.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260629.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260628.1': + '@typescript/native-preview@7.0.0-dev.20260629.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260628.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260628.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260628.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260628.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260628.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260628.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260628.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260629.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260629.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260629.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260629.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260629.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260629.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260629.1 '@ungap/structured-clone@1.3.2': {} @@ -10040,7 +10057,7 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3): + fumadocs-core@16.10.7(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3): dependencies: '@orama/orama': 3.1.18 estree-util-value-to-estree: 3.5.0 @@ -10073,14 +10090,14 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-mdx@15.0.13(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(rolldown@1.0.2)(vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)): + fumadocs-mdx@15.0.13(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.17)(fumadocs-core@16.10.7(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(rolldown@1.0.2)(vite@8.0.14(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 chokidar: 5.0.0 esbuild: 0.28.1 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + fumadocs-core: 16.10.7(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) js-yaml: 5.2.1 mdast-util-mdx: 3.0.0 picocolors: 1.1.1 @@ -10103,23 +10120,23 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-ui@16.10.6(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + fumadocs-ui@16.10.7(@types/mdx@2.0.14)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.10.7(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@fuma-translate/react': 1.0.2(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@fumadocs/tailwind': 0.0.5 - '@radix-ui/react-accordion': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-collapsible': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-dialog': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-accordion': 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-collapsible': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dialog': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-navigation-menu': 1.2.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-popover': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-scroll-area': 1.2.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-navigation-menu': 1.2.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-popover': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-scroll-area': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-tabs': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-tabs': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) class-variance-authority: 0.7.1 cnfast: 0.0.8 - fumadocs-core: 16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + fumadocs-core: 16.10.7(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.23.0(react@19.2.7))(next@16.2.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) lucide-react: 1.23.0(react@19.2.7) motion: 12.42.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next-themes: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -11758,29 +11775,29 @@ snapshots: '@oxc-resolver/binding-win32-arm64-msvc': 11.21.3 '@oxc-resolver/binding-win32-x64-msvc': 11.21.3 - oxfmt@0.56.0: + oxfmt@0.57.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.56.0 - '@oxfmt/binding-android-arm64': 0.56.0 - '@oxfmt/binding-darwin-arm64': 0.56.0 - '@oxfmt/binding-darwin-x64': 0.56.0 - '@oxfmt/binding-freebsd-x64': 0.56.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.56.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.56.0 - '@oxfmt/binding-linux-arm64-gnu': 0.56.0 - '@oxfmt/binding-linux-arm64-musl': 0.56.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.56.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.56.0 - '@oxfmt/binding-linux-riscv64-musl': 0.56.0 - '@oxfmt/binding-linux-s390x-gnu': 0.56.0 - '@oxfmt/binding-linux-x64-gnu': 0.56.0 - '@oxfmt/binding-linux-x64-musl': 0.56.0 - '@oxfmt/binding-openharmony-arm64': 0.56.0 - '@oxfmt/binding-win32-arm64-msvc': 0.56.0 - '@oxfmt/binding-win32-ia32-msvc': 0.56.0 - '@oxfmt/binding-win32-x64-msvc': 0.56.0 + '@oxfmt/binding-android-arm-eabi': 0.57.0 + '@oxfmt/binding-android-arm64': 0.57.0 + '@oxfmt/binding-darwin-arm64': 0.57.0 + '@oxfmt/binding-darwin-x64': 0.57.0 + '@oxfmt/binding-freebsd-x64': 0.57.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.57.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.57.0 + '@oxfmt/binding-linux-arm64-gnu': 0.57.0 + '@oxfmt/binding-linux-arm64-musl': 0.57.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-musl': 0.57.0 + '@oxfmt/binding-linux-s390x-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-musl': 0.57.0 + '@oxfmt/binding-openharmony-arm64': 0.57.0 + '@oxfmt/binding-win32-arm64-msvc': 0.57.0 + '@oxfmt/binding-win32-ia32-msvc': 0.57.0 + '@oxfmt/binding-win32-x64-msvc': 0.57.0 oxlint-tsgolint@0.23.0: optionalDependencies: @@ -11791,27 +11808,27 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 0.23.0 '@oxlint-tsgolint/win32-x64': 0.23.0 - oxlint@1.71.0(oxlint-tsgolint@0.23.0): + oxlint@1.73.0(oxlint-tsgolint@0.23.0): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.71.0 - '@oxlint/binding-android-arm64': 1.71.0 - '@oxlint/binding-darwin-arm64': 1.71.0 - '@oxlint/binding-darwin-x64': 1.71.0 - '@oxlint/binding-freebsd-x64': 1.71.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.71.0 - '@oxlint/binding-linux-arm-musleabihf': 1.71.0 - '@oxlint/binding-linux-arm64-gnu': 1.71.0 - '@oxlint/binding-linux-arm64-musl': 1.71.0 - '@oxlint/binding-linux-ppc64-gnu': 1.71.0 - '@oxlint/binding-linux-riscv64-gnu': 1.71.0 - '@oxlint/binding-linux-riscv64-musl': 1.71.0 - '@oxlint/binding-linux-s390x-gnu': 1.71.0 - '@oxlint/binding-linux-x64-gnu': 1.71.0 - '@oxlint/binding-linux-x64-musl': 1.71.0 - '@oxlint/binding-openharmony-arm64': 1.71.0 - '@oxlint/binding-win32-arm64-msvc': 1.71.0 - '@oxlint/binding-win32-ia32-msvc': 1.71.0 - '@oxlint/binding-win32-x64-msvc': 1.71.0 + '@oxlint/binding-android-arm-eabi': 1.73.0 + '@oxlint/binding-android-arm64': 1.73.0 + '@oxlint/binding-darwin-arm64': 1.73.0 + '@oxlint/binding-darwin-x64': 1.73.0 + '@oxlint/binding-freebsd-x64': 1.73.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.73.0 + '@oxlint/binding-linux-arm-musleabihf': 1.73.0 + '@oxlint/binding-linux-arm64-gnu': 1.73.0 + '@oxlint/binding-linux-arm64-musl': 1.73.0 + '@oxlint/binding-linux-ppc64-gnu': 1.73.0 + '@oxlint/binding-linux-riscv64-gnu': 1.73.0 + '@oxlint/binding-linux-riscv64-musl': 1.73.0 + '@oxlint/binding-linux-s390x-gnu': 1.73.0 + '@oxlint/binding-linux-x64-gnu': 1.73.0 + '@oxlint/binding-linux-x64-musl': 1.73.0 + '@oxlint/binding-openharmony-arm64': 1.73.0 + '@oxlint/binding-win32-arm64-msvc': 1.73.0 + '@oxlint/binding-win32-ia32-msvc': 1.73.0 + '@oxlint/binding-win32-x64-msvc': 1.73.0 oxlint-tsgolint: 0.23.0 p-cancelable@2.1.1: {} @@ -12053,7 +12070,7 @@ snapshots: postgres-range@1.1.4: {} - posthog-node@5.38.6: + posthog-node@5.38.8: dependencies: '@posthog/core': 1.39.6 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 536d238908..d42a59a931 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -22,13 +22,13 @@ catalog: "@swc/core": "^1.15.43" "@tsconfig/bun": "^1.0.10" "@types/bun": "^1.3.14" - "@typescript/native-preview": "7.0.0-dev.20260628.1" + "@typescript/native-preview": "7.0.0-dev.20260629.1" "@vitest/coverage-istanbul": "^4.1.9" "effect": "4.0.0-beta.93" "knip": "^6.17.1" "nx": "^23.0.0" - "oxfmt": "^0.56.0" - "oxlint": "^1.70.0" + "oxfmt": "^0.57.0" + "oxlint": "^1.72.0" "oxlint-tsgolint": "^0.23.0" "tldts": "^7.4.5" "vitest": "^4.1.9" From 1cbf45ad95a1b80663a35b776069f7b961dc8bbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 08:20:32 +0200 Subject: [PATCH 47/48] fix(docker): bump the docker-minor group in /apps/cli-go/pkg/config/templates with 3 updates (#5806) Bumps the docker-minor group in /apps/cli-go/pkg/config/templates with 3 updates: supabase/studio, supabase/realtime and supabase/storage-api. Updates `supabase/studio` from 2026.06.29-sha-20290c7 to 2026.07.06-sha-66cf431 Updates `supabase/realtime` from v2.112.4 to v2.112.6 Updates `supabase/storage-api` from v1.61.13 to v1.62.5 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` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- apps/cli-go/pkg/config/templates/Dockerfile | 6 +++--- packages/stack/src/versions.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index 977948cb20..3847dc5752 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -5,14 +5,14 @@ FROM library/kong:2.8.1 AS kong FROM axllent/mailpit:v1.30.2 AS mailpit FROM postgrest/postgrest:v14.14 AS postgrest FROM supabase/postgres-meta:v0.96.6 AS pgmeta -FROM supabase/studio:2026.06.29-sha-20290c7 AS studio +FROM supabase/studio:2026.07.06-sha-66cf431 AS studio FROM darthsim/imgproxy:v3.8.0 AS imgproxy FROM supabase/edge-runtime:v1.74.2 AS edgeruntime FROM timberio/vector:0.53.0-alpine AS vector FROM supabase/supavisor:2.9.7 AS supavisor FROM supabase/gotrue:v2.192.0 AS gotrue -FROM supabase/realtime:v2.112.4 AS realtime -FROM supabase/storage-api:v1.61.13 AS storage +FROM supabase/realtime:v2.112.6 AS realtime +FROM supabase/storage-api:v1.62.5 AS storage FROM supabase/logflare:1.46.0 AS logflare # Append to JobImages when adding new dependencies below FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ diff --git a/packages/stack/src/versions.ts b/packages/stack/src/versions.ts index 14666bfcc9..144979c78f 100644 --- a/packages/stack/src/versions.ts +++ b/packages/stack/src/versions.ts @@ -50,12 +50,12 @@ export const DEFAULT_VERSIONS: VersionManifest = { postgrest: "14.14", auth: "2.192.0", "edge-runtime": "1.74.2", - realtime: "2.112.4", - storage: "1.61.13", + realtime: "2.112.6", + storage: "1.62.5", imgproxy: "v3.8.0", mailpit: "v1.30.2", pgmeta: "0.96.6", - studio: "2026.06.29-sha-20290c7", + studio: "2026.07.06-sha-66cf431", analytics: "1.46.0", vector: "0.53.0-alpine", pooler: "2.9.7", From 6d4c19870ed213ba7f682f117d0345c8a40bfa94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 08:20:59 +0200 Subject: [PATCH 48/48] fix(docker): bump supabase/postgres from 17.6.1.142 to 17.6.1.143 in /apps/cli-go/pkg/config/templates (#5807) Bumps supabase/postgres from 17.6.1.142 to 17.6.1.143. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=supabase/postgres&package-manager=docker&previous-version=17.6.1.142&new-version=17.6.1.143)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) 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 this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- apps/cli-go/pkg/config/templates/Dockerfile | 2 +- packages/stack/src/versions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/cli-go/pkg/config/templates/Dockerfile b/apps/cli-go/pkg/config/templates/Dockerfile index 3847dc5752..ec7e643391 100644 --- a/apps/cli-go/pkg/config/templates/Dockerfile +++ b/apps/cli-go/pkg/config/templates/Dockerfile @@ -1,5 +1,5 @@ # Exposed for updates by .github/dependabot.yml -FROM supabase/postgres:17.6.1.142 AS pg +FROM supabase/postgres:17.6.1.143 AS pg # Append to ServiceImages when adding new dependencies below FROM library/kong:2.8.1 AS kong FROM axllent/mailpit:v1.30.2 AS mailpit diff --git a/packages/stack/src/versions.ts b/packages/stack/src/versions.ts index 144979c78f..ce7b086789 100644 --- a/packages/stack/src/versions.ts +++ b/packages/stack/src/versions.ts @@ -46,7 +46,7 @@ export interface VersionManifest { } export const DEFAULT_VERSIONS: VersionManifest = { - postgres: "17.6.1.142", + postgres: "17.6.1.143", postgrest: "14.14", auth: "2.192.0", "edge-runtime": "1.74.2",