Files
supabase/e2e/studio/features/wrappers.spec.ts
e66d8eb094 chore(deps): bump Supabase CLI to ^2.114.0 (speculative: Selfhosted Studio E2E Start supabase flake) (#49198)
<!-- ccr-slack-attribution -->
_Requested by **Ivan Vasilov** · [Slack
thread](https://supabase.slack.com/archives/C063LNYJJKS/p1787058646458219?thread_ts=1787058646.458219&cid=C063LNYJJKS)_

**Before:** the root `package.json` pins the Supabase CLI at `supabase:
^2.76.10`, and `pnpm-lock.yaml` resolves it to `2.76.14`.

**After:** it pins `supabase: ^2.114.0`.

This bumps the Supabase CLI that `pnpm run e2e:setup:cli` and `pnpm run
setup:cli` shell out to, so local dev and the E2E workflows boot the
local stack with a CLI from this month instead of one from ~38 minor
releases ago.

**How:** a one-line version change to the `supabase` devDependency in
the root `package.json`. Nothing else in the repo changes — no workflow,
config, or test changes.

### ⚠️ This PR is incomplete: `pnpm-lock.yaml` still needs regenerating

`pnpm-lock.yaml` is **not** updated in this PR, so `pnpm install
--frozen-lockfile` will fail until someone runs:

```bash
pnpm install --lockfile-only
```

and pushes the result to this branch. The lockfile could not be
regenerated in the environment this PR was authored in: pnpm re-resolves
`apps/studio`'s `"@std/path": "npm:@jsr/std__path@^1.0.8"` on every
install, and `npm.jsr.io` is not reachable from there
(`ERR_PNPM_FETCH_403`). Treat this PR as needing one extra commit before
it can go green.

### Why `^2.114.0` and not `^2.115.0`

`2.115.0` is the current `latest` on npm, but it was published only
hours ago, and `pnpm-workspace.yaml` sets `minimumReleaseAge: 4320` (3
days) with `supabase` not in `minimumReleaseAgeExclude`. Pinning
`2.115.0` today would fail the repo's own supply-chain check. `2.114.0`
(2026-08-12) is the newest release that satisfies that policy.

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Dependency bump. **Speculative** — this is an experiment, not a
confirmed fix.

## What is the current behavior?

The `Selfhosted Studio E2E Tests` workflow has been failing on `master`
at the `Start supabase` step. Recent runs:

- https://github.com/supabase/supabase/actions/runs/32092940311
- https://github.com/supabase/supabase/actions/runs/32131961447

In the Slack thread, Ivan Vasilov suggested trying a newer CLI and
Alaister Young endorsed giving it a go.

## What is the new behavior?

The workflow runs `supabase start` with CLI 2.114.0 instead of 2.76.14.

The question this PR is trying to answer is simply **"does a newer CLI
help this flake?"** It is not a diagnosis and not a claimed fix. If CI
still fails at `Start supabase` on this branch, the bump can be kept or
dropped on its own merits and the investigation continues elsewhere.

## Additional context

**Verification status:** none locally. The bump was not exercised
locally — this repo checkout has no `node_modules` (see the lockfile
note above), so `pnpm typecheck`, `pnpm lint`, and `pnpm test:studio`
were not run, and neither was `supabase start`. CI on this PR is the
only signal.

**Call-site compatibility check.** CLI 2.99/2.100 moved to a new
TypeScript shell with a stricter argument parser: command-specific flags
must now come *after* the subcommand. Both call sites in the root
`package.json` already use that order, so no script changes are needed:

```
supabase stop --all --no-backup --workdir ./e2e/studio
supabase start --exclude studio,mailpit --workdir ./e2e/studio
```

**Changelog entries between 2.76.14 and 2.114.0 that touch `supabase
start` or local config.** Listed so reviewers know what changed in the
range — **not** as a claim about what is failing in CI:

- **2.112.0** — `supabase start` no longer hangs when analytics
migrations fail; the analytics container exits and retries instead of
booting against an unmigrated database
([#6093](https://github.com/supabase/cli/pull/6093)).
- **2.112.0** — `supabase start` reuses existing volumes instead of
failing when they already exist
([#6037](https://github.com/supabase/cli/pull/6037)); Kong reloads after
`supabase db reset`
([#6017](https://github.com/supabase/cli/pull/6017)); custom auth email
templates survive `db reset`
([#6065](https://github.com/supabase/cli/pull/6065)); `supabase start`
works on SELinux-enforcing hosts
([#6000](https://github.com/supabase/cli/pull/6000)).
- **2.106.0 — behavior change worth watching.**
`[api].auto_expose_new_tables` now resolves to `false` when unset, and
local start/reset revokes default Data API privileges for newly created
`public` tables, sequences, and functions
([#5524](https://github.com/supabase/cli/pull/5524)). Neither
`supabase/config.toml` nor `e2e/studio/supabase/config.toml` sets this
key, so this default applies. If E2E specs create `public` objects and
then read them through the Data API, they may need explicit `GRANT`s
(the deprecated escape hatch is `auto_expose_new_tables = true`).
- **2.106.0** — when the CLI detects a coding-agent environment, or
`--agent yes` is passed, commands default to JSON output
([#5532](https://github.com/supabase/cli/pull/5532)). `e2e:setup:cli`
already passes `--output json` to `supabase status` explicitly, so this
should be a no-op here.
- **2.100.0** — stricter flag ordering, covered above.
- **2.112.0** — `functions deploy` no longer forwards `NPM_AUTH_TOKEN`
into Docker bundling
([#6005](https://github.com/supabase/cli/pull/6005)). Not used by these
workflows.
- **2.107.0** — pg-delta is the default schema diff engine for `db diff`
/ `db pull` on new projects
([#5511](https://github.com/supabase/cli/pull/5511)).
- Many bundled Docker image bumps across the range (`supabase/postgres`
17.6.1.087 → later patches, `postgres-meta`, `vector` 0.28.1 → 0.53.0,
Studio image), plus `fix(analytics): wait for logflare before starting
vector` (2.84.3) and `fix: use correct docker.sock binding with vector`
(2.84.7).

Full comparison:
https://github.com/supabase/cli/compare/v2.76.14...v2.114.0


---
_Generated by [Claude
Code](https://claude.ai/code/session_0143DrDMGnSSwuHebTPJv7ZY)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
2026-08-19 10:52:25 +02:00

143 lines
6.0 KiB
TypeScript

import { expect } from '@playwright/test'
import { env } from '../env.config.js'
import { query } from '../utils/db/client.js'
import { test, withSetupCleanup } from '../utils/test.js'
import { toUrl } from '../utils/to-url.js'
const testRunner = env.IS_PLATFORM ? test.describe.serial : test.describe
testRunner('Stripe', () => {
test('can create a stripe wrapper with schema', async ({ page, ref }) => {
const wrapperName = 'stripe_schema'
const schemaName = 'stripe'
await using _ = await withSetupCleanup(
async () => {
await query(`
create schema if not exists extensions;
create extension if not exists wrappers
schema extensions
version '0.6.2'
cascade;
`)
},
async () => {
// Make the request in the page context so that the it can delete wrappers that belongs to supabase_admin
await page.request.post(toUrl(`/api/platform/pg-meta/${ref}/query`), {
failOnStatusCode: true,
data: {
query: `
drop foreign data wrapper if exists ${wrapperName} cascade;
delete from vault.secrets where name = '${wrapperName}_api_key_id';
drop schema ${schemaName} cascade;`,
},
})
}
)
await page.goto(toUrl(`/project/${ref}/integrations/stripe_wrapper/overview`))
await page.getByRole('button', { name: 'Add new wrapper' }).click()
await page.getByRole('textbox', { name: 'Wrapper Name' }).fill(wrapperName)
await page.getByRole('textbox', { name: 'Stripe Secret Key' }).fill('my secret')
await page.getByRole('radio', { name: 'Schema' }).click()
await page
.getByRole('textbox', { name: 'Specify a new schema to create all wrapper tables in' })
.fill(schemaName)
await page.getByRole('button', { name: 'Create wrapper' }).click()
await expect(page.getByText('Successfully created Stripe foreign data wrapper')).toBeVisible()
})
test('can create a stripe wrapper with tables', async ({ page, ref }) => {
const wrapperName = 'stripe_tables'
const tableName = 'stripe_accounts'
await using _ = await withSetupCleanup(
async () => {
await query(`
create schema if not exists extensions;
create extension if not exists wrappers
schema extensions
version '0.6.2'
cascade;
`)
},
async () => {
// Make the request in the page context so that the it can delete wrappers that belongs to supabase_admin
await page.request.post(toUrl(`/api/platform/pg-meta/${ref}/query`), {
failOnStatusCode: true,
data: {
query: `
drop foreign data wrapper if exists ${wrapperName} cascade;
delete from vault.secrets where name = '${wrapperName}_api_key_id';
drop table if exists public.${tableName};`,
},
})
}
)
await page.goto(toUrl(`/project/${ref}/integrations/stripe_wrapper/overview`))
await page.getByRole('button', { name: 'Add new wrapper' }).click()
await page.getByRole('textbox', { name: 'Wrapper Name' }).fill(wrapperName)
await page.getByRole('textbox', { name: 'Stripe Secret Key' }).fill('my secret')
await page.getByRole('button', { name: 'Add foreign table' }).click()
await page.getByRole('combobox').click()
await page.getByRole('option', { name: 'Accounts List of accounts on' }).click()
await page.getByRole('textbox', { name: 'Table name' }).fill(tableName)
await page.getByRole('button', { name: 'Save' }).click()
await page.getByRole('button', { name: 'Create wrapper' }).click()
await expect(page.getByText('Successfully created Stripe foreign data wrapper')).toBeVisible()
})
})
testRunner('S3 Wrapper', () => {
test('can create an S3 wrapper', async ({ page, ref }) => {
const wrapperName = 'test_s3_wrapper'
const tableName = 'test_s3_wrapper_table'
await using _ = await withSetupCleanup(
async () => {
await query(`
create schema if not exists extensions;
create extension if not exists wrappers
schema extensions
version '0.6.2'
cascade;
`)
},
async () => {
// Make the request in the page context so that the it can delete wrappers that belongs to supabase_admin
await page.request.post(toUrl(`/api/platform/pg-meta/${ref}/query`), {
failOnStatusCode: true,
data: {
query: `
drop foreign data wrapper if exists ${wrapperName} cascade;
delete from vault.secrets where name = '${wrapperName}_vault_access_key_id';
delete from vault.secrets where name = '${wrapperName}_vault_secret_access_key';
drop table if exists public.${tableName};`,
},
})
}
)
await page.goto(toUrl(`/project/${ref}/integrations/s3_wrapper/overview`))
await page.getByRole('button', { name: 'Add new wrapper' }).click()
await page.getByRole('textbox', { name: 'Wrapper Name' }).fill(wrapperName)
await page.getByRole('textbox', { name: 'Access Key ID' }).fill('s3 access id')
await page.getByRole('textbox', { name: 'Access Key Secret' }).fill('s3 access secret')
await page.getByRole('button', { name: 'Add foreign table' }).click()
await page.getByRole('combobox').click()
await page.getByRole('option', { name: 'S3 File' }).click()
await page.getByRole('textbox', { name: 'Table name' }).fill(tableName)
await page.getByRole('textbox', { name: 'URI' }).fill('s3://bucket/s3_table.csv')
await page.getByRole('button', { name: 'Add column' }).click()
// FIXME: Necessary because this component is somehow remounted
await page.waitForTimeout(500)
await page.getByRole('textbox', { name: 'Name', exact: true }).fill('s3_column')
await page.getByRole('button', { name: 'Save' }).click()
await page.getByRole('button', { name: 'Create wrapper' }).click()
await expect(page.getByText('Successfully created S3 foreign data wrapper')).toBeVisible()
})
})