Files
Jordi EnricandFrancesco Sansalvadore 509d80c9eb feat(studio): CLI deploy instructions for workers (FE-4191) (#49194)
## What

The surface that shows you how to deploy a worker from the CLI, plus the
product rename and the alpha framing.

- **Compute → Workers.** `PRODUCT_NAME` and `CLI_NAME` now say `Workers`
/ `workers`, so the sidebar, page title, command menu, and every
generated snippet match the CLI. One name, not two.
- **`DeployWorkerDialog`** — scaffold / configure / push, with copyable
`supabase workers` and `config.toml` snippets
- **`WorkersEmptyState`** — an `EmptyStatePresentational` with a
permission-gated deploy action
- **`AlphaNotice`** on the list page, and a **New** badge on the sidebar
entry (`Route.isNew`)
- **`WorkerSnippetTabs`** — CLI, `config.toml`, and curl/JS/Python calls
built from one worker shape. Reused by #49195.

Snippet URLs resolve from the project's `app_config.endpoint`
(`https://<project>/workers/v1/<name>`, the same shape as
`/functions/v1/`), and fall back to `[YOUR WORKER URL]` before settings
load rather than printing a wrong host.

## How to test

Only on the **Mockamaster** project in staging — it is the one project
in the alpha allow-list.

1. Staging dashboard → Mockamaster → **Workers** (the sidebar entry
carries a **New** badge)
2. **Deploy a worker** → step through the tabs; every snippet should
name the real worker URL, not a placeholder
3. Copy the cURL snippet and run it: expect `401`. Reaching a deployed
worker needs a second allow-list (`WORKERS_ALLOWED_PROJECTS` in
api-gateway `customer-router/wrangler.toml`), separate from the flag
that unlocks the dashboard.
4. Open a project with no workers to see the empty state

## Tests

`workerSnippets.test.ts` covers the generated output users copy: worker
URL in all three call snippets, the `[YOUR WORKER URL]` fallback, anon
vs service-role placeholder, empty-name fallback and trimming, runtime
default, and every `config.toml` field.

## Unverified copy

The dialog steps and the `supabase workers <sub>` subcommands come from
the original POC spec, not from the shipped CLI. Same for "Dockerfile,
Node.js and Deno supported" in the empty state — only Deno is confirmed
end to end. Worth a check by someone who knows the CLI surface.

Closes FE-4191

---------

Co-authored-by: Francesco Sansalvadore <f.sansalvadore@gmail.com>
2026-08-25 15:38:57 +02:00

82 lines
2.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { WORKERS_REGION } from './Workers.constants'
import { buildWorkerCliCommands, buildWorkerSnippets, EXAMPLE_WORKER } from './workerSnippets'
const input = (overrides: Partial<Parameters<typeof buildWorkerSnippets>[0]> = {}) => ({
...EXAMPLE_WORKER,
endpoint: 'abcdefgh.supabase.co',
...overrides,
})
describe('buildWorkerSnippets', () => {
it('points every call snippet at the worker URL', () => {
const { curl, javascript, python } = buildWorkerSnippets(input({ name: 'embed' }))
const url = 'https://abcdefgh.supabase.co/workers/v1/embed'
expect(curl).toContain(`'${url}'`)
expect(javascript).toContain(`'${url}'`)
expect(python).toContain(`"${url}"`)
})
it('leaves a placeholder URL until the project settings resolve', () => {
const { curl } = buildWorkerSnippets(input({ endpoint: undefined }))
expect(curl).toContain('[YOUR WORKER URL]')
})
it('asks for the anon key on a public worker and the service role key on a private one', () => {
expect(buildWorkerSnippets(input({ access: 'public' })).curl).toContain('[YOUR ANON KEY]')
expect(buildWorkerSnippets(input({ access: 'private' })).curl).toContain(
'[YOUR SERVICE ROLE KEY]'
)
expect(buildWorkerSnippets(input({ access: 'private' })).javascript).toContain(
'[YOUR SERVICE ROLE KEY]'
)
})
it('falls back to a placeholder name when the worker has none', () => {
expect(buildWorkerSnippets(input({ name: ' ' })).cli).toContain('my-worker')
})
it('trims the name before interpolating it', () => {
expect(buildWorkerSnippets(input({ name: ' embed ' })).cli).toContain('new embed --runtime')
})
it('defaults the runtime when the API omits it', () => {
expect(buildWorkerSnippets(input({ runtime: undefined })).cli).toContain('--runtime node')
})
it('writes the worker spec into the config.toml block', () => {
const { configToml } = buildWorkerSnippets(
input({
name: 'embed',
runtime: 'python',
size: '4gb-2vcpu',
access: 'private',
instances: 3,
})
)
expect(configToml).toContain('[workers.embed]')
expect(configToml).toContain('runtime = "python"')
expect(configToml).toContain('size = "4gb-2vcpu" # 4 GB · 2 vCPU')
expect(configToml).toContain('access = "private"')
expect(configToml).toContain('instances = 3')
expect(configToml).toContain(WORKERS_REGION)
})
})
describe('buildWorkerCliCommands', () => {
it('names the worker in every command', () => {
const commands = buildWorkerCliCommands('embed')
expect(commands).not.toHaveLength(0)
for (const { command } of commands) {
expect(command).toContain('embed')
}
})
it('falls back to a placeholder name when the worker has none', () => {
expect(buildWorkerCliCommands(' ')[0].command).toContain('my-worker')
})
})