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

112 lines
3.2 KiB
TypeScript

import { WORKERS_REGION, workerUrl } from './Workers.constants'
import type { WorkerAccess } from './Workers.types'
import { formatSize } from './Workers.utils'
import { CLI_NAME } from '@/lib/constants/workers'
export interface WorkerSnippetInput {
name: string
endpoint: string | undefined
protocol?: string
runtime: string | undefined
size: string
access: WorkerAccess
instances: number
}
export interface WorkerSnippets {
configToml: string
cli: string
curl: string
javascript: string
python: string
}
export const EXAMPLE_WORKER: Omit<WorkerSnippetInput, 'endpoint' | 'protocol'> = {
name: 'my-worker',
runtime: 'node',
size: '2gb-1vcpu',
access: 'public',
instances: 1,
}
const safeName = (name: string) => (name.trim().length > 0 ? name.trim() : 'my-worker')
export function buildWorkerSnippets(input: WorkerSnippetInput): WorkerSnippets {
const name = safeName(input.name)
const runtime = input.runtime ?? 'node'
const url =
workerUrl({ endpoint: input.endpoint, protocol: input.protocol, name }) ?? '[YOUR WORKER URL]'
const cli = [
`supabase ${CLI_NAME} new ${name} --runtime ${runtime}`,
`supabase ${CLI_NAME} push ${name}`,
].join('\n')
const configToml = [
`# supabase/config.toml`,
``,
`[${CLI_NAME}.${name}]`,
`runtime = "${runtime}"`,
`size = "${input.size}" # ${formatSize(input.size)}`,
`access = "${input.access}"`,
`instances = ${input.instances}`,
`# region is locked to ${WORKERS_REGION} at alpha`,
].join('\n')
const authHeader =
input.access === 'public'
? ` -H 'Authorization: Bearer [YOUR ANON KEY]' \\`
: ` -H 'Authorization: Bearer [YOUR SERVICE ROLE KEY]' \\`
const curl = [
`curl -L -X POST '${url}' \\`,
authHeader,
` -H 'Content-Type: application/json' \\`,
` --data '{"name":"world"}'`,
].join('\n')
const keyPlaceholder = input.access === 'public' ? '[YOUR ANON KEY]' : '[YOUR SERVICE ROLE KEY]'
const javascript = [
`const res = await fetch('${url}', {`,
` method: 'POST',`,
` headers: {`,
` Authorization: 'Bearer ${keyPlaceholder}',`,
` 'Content-Type': 'application/json',`,
` },`,
` body: JSON.stringify({ name: 'world' }),`,
`})`,
`const data = await res.json()`,
].join('\n')
const python = [
`import requests`,
``,
`res = requests.post(`,
` "${url}",`,
` headers={"Authorization": "Bearer ${keyPlaceholder}"},`,
` json={"name": "world"},`,
`)`,
`print(res.json())`,
].join('\n')
return { configToml, cli, curl, javascript, python }
}
export interface WorkerCliCommand {
comment: string
command: string
}
export function buildWorkerCliCommands(name: string): WorkerCliCommand[] {
const slug = safeName(name)
return [
{ comment: 'Recreate the source locally', command: `supabase ${CLI_NAME} pull ${slug}` },
{ comment: 'Run it locally on a port', command: `supabase ${CLI_NAME} serve ${slug}` },
{ comment: 'Deploy a new version', command: `supabase ${CLI_NAME} push ${slug}` },
{ comment: 'Stream logs', command: `supabase ${CLI_NAME} logs ${slug} --follow` },
{ comment: 'Delete the worker', command: `supabase ${CLI_NAME} delete ${slug}` },
]
}