Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/public/humans.txt
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ Sana Cordeaux
Sasi Kanumuri
Sara Read
Satya Rohith Gannamanedi
Sean Geoghegan
Sean Oliver
Sean Romberg
Sean Thompson
Expand Down
10 changes: 8 additions & 2 deletions apps/studio/TANSTACK_MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] A `routes/_app/org/$slug/index.tsx` ← `pages/org/[slug]/index.tsx`
- [x] A `routes/_app/org/$slug/apps.tsx` ← `pages/org/[slug]/apps.tsx`
- [x] A `routes/_app/org/$slug/audit.tsx` ← `pages/org/[slug]/audit.tsx`
- [x] A `routes/_app/org/$slug/audit-log-drains.tsx` ← `pages/org/[slug]/audit-log-drains.tsx` (wraps in OrganizationSettingsLayout inline)
- [x] A `routes/_app/org/$slug/billing.tsx` ← `pages/org/[slug]/billing.tsx`
- [x] A `routes/_app/org/$slug/documents.tsx` ← `pages/org/[slug]/documents.tsx`
- [x] A `routes/_app/org/$slug/general.tsx` ← `pages/org/[slug]/general.tsx`
Expand Down Expand Up @@ -170,6 +171,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] A `routes/project/$ref/database/functions.tsx` ← `pages/project/[ref]/database/functions.tsx`
- [x] A `routes/project/$ref/database/indexes.tsx` ← `pages/project/[ref]/database/indexes.tsx`
- [x] A `routes/project/$ref/database/migrations.tsx` ← `pages/project/[ref]/database/migrations.tsx`
- [x] A `routes/project/$ref/database/policies.tsx` ← `pages/project/[ref]/database/policies.tsx`
- [x] A `routes/project/$ref/database/roles.tsx` ← `pages/project/[ref]/database/roles.tsx`
- [x] A `routes/project/$ref/database/settings.tsx` ← `pages/project/[ref]/database/settings.tsx`
- [x] A `routes/project/$ref/database/types.tsx` ← `pages/project/[ref]/database/types.tsx`
Expand All @@ -192,7 +194,6 @@ These are the layout-only TanStack files. Most hold a single product layout comp

- [x] A `routes/project/$ref/auth/overview.tsx` ← `pages/project/[ref]/auth/overview.tsx`
- [x] A `routes/project/$ref/auth/users.tsx` ← `pages/project/[ref]/auth/users.tsx`
- [x] A `routes/project/$ref/auth/policies.tsx` ← `pages/project/[ref]/auth/policies.tsx`
- [x] A `routes/project/$ref/auth/providers.tsx` ← `pages/project/[ref]/auth/providers.tsx` (sets `skipAuthLayout: true`, wraps in `AuthProvidersLayout` directly)
- [x] A `routes/project/$ref/auth/mfa.tsx` ← `pages/project/[ref]/auth/mfa.tsx`
- [x] A `routes/project/$ref/auth/hooks.tsx` ← `pages/project/[ref]/auth/hooks.tsx`
Expand Down Expand Up @@ -258,6 +259,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] A `routes/project/$ref/logs/dedicated-pooler-logs.tsx` ← `pages/project/[ref]/logs/dedicated-pooler-logs.tsx`
- [x] A `routes/project/$ref/logs/edge-functions-logs.tsx` ← `pages/project/[ref]/logs/edge-functions-logs.tsx`
- [x] A `routes/project/$ref/logs/edge-logs.tsx` ← `pages/project/[ref]/logs/edge-logs.tsx`
- [x] A `routes/project/$ref/logs/multigres-logs.tsx` ← `pages/project/[ref]/logs/multigres-logs.tsx`
- [x] A `routes/project/$ref/logs/pg-upgrade-logs.tsx` ← `pages/project/[ref]/logs/pg-upgrade-logs.tsx`
- [x] A `routes/project/$ref/logs/pgcron-logs.tsx` ← `pages/project/[ref]/logs/pgcron-logs.tsx`
- [x] A `routes/project/$ref/logs/pooler-logs.tsx` ← `pages/project/[ref]/logs/pooler-logs.tsx`
Expand All @@ -278,6 +280,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] A `routes/project/$ref/observability/auth.tsx` ← `pages/project/[ref]/observability/auth.tsx`
- [x] A `routes/project/$ref/observability/database.tsx` ← `pages/project/[ref]/observability/database.tsx`
- [x] A `routes/project/$ref/observability/api-overview.tsx` ← `pages/project/[ref]/observability/api-overview.tsx`
- [x] A `routes/project/$ref/observability/connections.tsx` ← `pages/project/[ref]/observability/connections.tsx`
- [x] A `routes/project/$ref/observability/edge-functions.tsx` ← `pages/project/[ref]/observability/edge-functions.tsx`
- [x] A `routes/project/$ref/observability/postgrest.tsx` ← `pages/project/[ref]/observability/postgrest.tsx`
- [x] A `routes/project/$ref/observability/query-insights.tsx` ← `pages/project/[ref]/observability/query-insights.tsx`
Expand All @@ -298,6 +301,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] A `routes/project/$ref/settings/addons.tsx` ← `pages/project/[ref]/settings/addons.tsx`
- [x] A `routes/project/$ref/settings/api.tsx` ← `pages/project/[ref]/settings/api.tsx` (sets `skipSettingsLayout: true` — page is a useEffect redirect)
- [x] A `routes/project/$ref/settings/dashboard.tsx` ← `pages/project/[ref]/settings/dashboard.tsx`
- [x] A `routes/project/$ref/settings/code-configuration.tsx` ← `pages/project/[ref]/settings/code-configuration.tsx`
- [x] A `routes/project/$ref/settings/infrastructure/index.tsx` ← `pages/project/[ref]/settings/infrastructure.tsx`
- [x] A `routes/project/$ref/settings/infrastructure/replica/$replicaId.tsx` ← `pages/project/[ref]/settings/infrastructure/replica/[replicaId].tsx`
- [x] A `routes/project/$ref/settings/integrations.tsx` ← `pages/project/[ref]/settings/integrations.tsx`
Expand All @@ -322,7 +326,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] A `routes/project/$ref/sql/index.tsx` ← `pages/project/[ref]/sql/index.tsx`
- [x] A `routes/project/$ref/sql/$id.tsx` ← `pages/project/[ref]/sql/[id].tsx`
- [x] A `routes/project/$ref/sql/templates.tsx` ← `pages/project/[ref]/sql/templates.tsx`
- [x] A `routes/project/$ref/sql/quickstarts.tsx` ← `pages/project/[ref]/sql/quickstarts.tsx`
- [x] A `routes/project/$ref/sql/examples.tsx` ← `pages/project/[ref]/sql/examples.tsx`

### Project shell — `/editor/*`

Expand All @@ -335,6 +339,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] A `routes/project/$ref/explorer/index.tsx` ← `pages/project/[ref]/explorer/index.tsx`
- [x] A `routes/project/$ref/explorer/notebook/$id.tsx` ← `pages/project/[ref]/explorer/notebook/[id].tsx`
- [x] A `routes/project/$ref/explorer/chat/$id.tsx` ← `pages/project/[ref]/explorer/chat/[id].tsx`
- [x] A `routes/project/$ref/explorer/query/$id.tsx` ← `pages/project/[ref]/explorer/query/[id].tsx`

### Auth shell — `/sign-in`, `/sign-up`, etc.

Expand All @@ -356,6 +361,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] A `routes/redeem.tsx` ← `pages/redeem.tsx` (RedeemCreditsLayout)
- [x] A `routes/logout.tsx` ← `pages/logout.tsx`
- [x] A `routes/maintenance.tsx` ← `pages/maintenance.tsx`
- [x] A `routes/verify-email.tsx` ← `pages/verify-email.tsx`

### Error pages (handled at root)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const WorkerDetail = () => {
{worker.imageVersion !== undefined && (
<span className="flex items-center gap-2 text-foreground-light">
<Package size={14} strokeWidth={1.5} className="text-foreground-lighter" />
Image {worker.imageVersion}
Version {worker.imageVersion}
</span>
)}
</PageHeaderDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { LISTENING_PORT, WORKERS_REGION_LABEL } from '../Workers.constants'
import type { Worker } from '../Workers.types'
import { formatSize, getRuntimeMeta } from '../Workers.utils'
import { buildWorkerCliCommands } from '../workerSnippets'
import { WorkerSnippetTabs } from '../WorkerSnippetTabs'
import { WORKER_CALL_TABS, WorkerSnippetTabs } from '../WorkerSnippetTabs'
import { CLI_NAME } from '@/lib/constants/workers'

interface WorkerOverviewTabProps {
Expand Down Expand Up @@ -156,7 +156,7 @@ export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => {
<RuntimeBadge runtime={worker.runtime} />
</SettingsRow>
{worker.imageVersion !== undefined && (
<SettingsRow label="Image version">
<SettingsRow label="Version">
<span className="font-mono text-xs text-foreground-light">
{worker.imageVersion}
</span>
Expand Down Expand Up @@ -215,9 +215,7 @@ export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => {
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>How to call</PageSectionTitle>
<PageSectionDescription>
Call the worker over its gateway URL. Pass your project API key as a bearer token.
</PageSectionDescription>
<PageSectionDescription>Call the worker over its gateway URL.</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
Expand All @@ -229,7 +227,7 @@ export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => {
access: worker.access,
instances: worker.declaredInstances,
}}
tabs={['cli', 'js', 'python']}
tabs={WORKER_CALL_TABS}
/>
</PageSectionContent>
</PageSection>
Expand Down
13 changes: 11 additions & 2 deletions apps/studio/components/interfaces/Workers/WorkerSnippetTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@ import { buildWorkerSnippets, type WorkerSnippetInput } from './workerSnippets'
import CopyButton from '@/components/ui/CopyButton'
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'

export type WorkerSnippetTab = 'ai' | 'config' | 'cli' | 'js' | 'python'
export type WorkerSnippetTab = 'ai' | 'config' | 'cli' | 'curl' | 'js' | 'python'

export const WORKER_CALL_TABS = [
'curl',
'js',
'python',
] as const satisfies readonly WorkerSnippetTab[]

const TAB_LABEL: Record<WorkerSnippetTab, string> = {
ai: 'AI Prompt',
config: 'config.toml',
cli: 'CLI',
curl: 'cURL',
js: 'JavaScript',
python: 'Python',
}
Expand All @@ -21,13 +28,14 @@ const TAB_ICON: Record<WorkerSnippetTab, LucideIcon> = {
ai: Sparkles,
config: FileCode,
cli: Terminal,
curl: Terminal,
js: FileCode,
python: FileCode,
}

interface WorkerSnippetTabsProps {
input: Omit<WorkerSnippetInput, 'endpoint' | 'protocol'>
tabs?: [WorkerSnippetTab, ...WorkerSnippetTab[]]
tabs?: readonly [WorkerSnippetTab, ...WorkerSnippetTab[]]
className?: string
}

Expand All @@ -45,6 +53,7 @@ export const WorkerSnippetTabs = ({ input, tabs = ['cli'], className }: WorkerSn
ai: snippets.aiPrompt,
config: snippets.configToml,
cli: snippets.cli,
curl: snippets.curl,
js: snippets.javascript,
python: snippets.python,
}
Expand Down
11 changes: 8 additions & 3 deletions apps/studio/components/interfaces/Workers/workerSnippets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,21 @@ const input = (overrides: Partial<Parameters<typeof buildWorkerSnippets>[0]> = {

describe('buildWorkerSnippets', () => {
it('points the invoke examples at the worker URL', () => {
const { javascript, python } = buildWorkerSnippets(input({ name: 'embed' }))
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 invoke URL until the project settings resolve', () => {
const { javascript } = buildWorkerSnippets(input({ endpoint: undefined }))
expect(javascript).toContain('[YOUR WORKER URL]')
const { curl } = buildWorkerSnippets(input({ endpoint: undefined }))
expect(curl).toContain('[YOUR WORKER URL]')
})

it('does not require authorization for the CLI invoke example', () => {
expect(buildWorkerSnippets(input()).curl).not.toContain('Authorization')
})

it('asks for the anon key to invoke a public worker and the service role key for a private one', () => {
Expand Down
9 changes: 8 additions & 1 deletion apps/studio/components/interfaces/Workers/workerSnippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface WorkerSnippets {
aiPrompt: string
configToml: string
cli: string
curl: string
javascript: string
python: string
}
Expand Down Expand Up @@ -46,6 +47,12 @@ export function buildWorkerSnippets(input: WorkerSnippetInput): WorkerSnippets {
...(input.access === 'private' ? [`# note: the CLI can only deploy public workers today`] : []),
].join('\n')

const curl = [
`curl --request POST '${url}' \\`,
` --header 'Content-Type: application/json' \\`,
` --data '{"name":"world"}'`,
].join('\n')

const configBlock = [
`[${CLI_NAME}.${name}]`,
`runtime = "${runtime}"`,
Expand Down Expand Up @@ -102,7 +109,7 @@ export function buildWorkerSnippets(input: WorkerSnippetInput): WorkerSnippets {
`print(res.json())`,
].join('\n')

return { aiPrompt, configToml, cli, javascript, python }
return { aiPrompt, configToml, cli, curl, javascript, python }
}

export interface WorkerCliCommand {
Expand Down
59 changes: 59 additions & 0 deletions apps/studio/redirects.shared.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,62 @@ describe('matchRedirect query/hash preservation', () => {
).toBeNull()
})
})

describe('matchRedirect maintenance mode', () => {
it('sends every other path to /maintenance when enabled', () => {
expect(
matchRedirect({
pathname: '/project/abc/editor',
search: {},
isPlatform: true,
maintenanceMode: true,
})
).toEqual({ destination: '/maintenance', permanent: false })
})

it('carries query and hash onto /maintenance', () => {
expect(
matchRedirect({
pathname: '/project/abc/editor',
search: { a: '1' },
isPlatform: true,
maintenanceMode: true,
hash: 'section',
})
).toEqual({ destination: '/maintenance?a=1#section', permanent: false })
})

it('leaves /maintenance and /img reachable when enabled', () => {
expect(
matchRedirect({
pathname: '/maintenance',
search: {},
isPlatform: true,
maintenanceMode: true,
})
).toBeNull()
expect(
matchRedirect({
pathname: '/img/supabase-logo.svg',
search: {},
isPlatform: true,
maintenanceMode: true,
})
).toBeNull()
})

it('bounces /maintenance back to / when disabled', () => {
expect(matchRedirect({ pathname: '/maintenance', search: {}, isPlatform: true })).toEqual({
destination: '/',
permanent: false,
})
expect(
matchRedirect({
pathname: '/maintenance',
search: {},
isPlatform: true,
maintenanceMode: false,
})
).toEqual({ destination: '/', permanent: false })
})
})
7 changes: 7 additions & 0 deletions apps/studio/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ const IS_NON_PROD_ENV =
process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' ||
process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'

// Mirrors the `MAINTENANCE_MODE` reads in `next.config.ts` and `vercel.ts`.
// The var is unprefixed, so vite.config.ts inlines it explicitly (see the
// define there) rather than it arriving via the NEXT_PUBLIC_ sweep — that
// keeps the toggle a single build-time env var across all three runtimes.
const IS_MAINTENANCE_MODE = process.env.MAINTENANCE_MODE === 'true'

// Keep dev-only components out of the production bundle.
const IS_DEV_TOOLBAR_ENABLED = IS_NON_PROD_ENV

Expand Down Expand Up @@ -329,6 +335,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({
pathname: location.pathname,
search: location.search as Record<string, string | string[] | undefined>,
isPlatform: IS_PLATFORM,
maintenanceMode: IS_MAINTENANCE_MODE,
hash: location.hash,
})
if (!match) return
Expand Down
13 changes: 13 additions & 0 deletions apps/studio/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,19 @@ export default defineConfig(({ command, mode }) => {
}
}

// `MAINTENANCE_MODE` gates the "redirect everything to /maintenance" rule.
// It's deliberately unprefixed, and the other two consumers both read it at
// BUILD time: `next.config.ts` reads it in `redirects()`, which Next bakes
// into `routes-manifest.json` during `next build`, and `vercel.ts` reads it
// while emitting `vercel.json`. So flipping maintenance has always meant a
// rebuild/redeploy, never just a server restart. Inline it here on the same
// terms so the isomorphic `beforeLoad` in `routes/__root.tsx` — which
// mirrors those rules for the TanStack runtime — can read it on the client
// too, without self-hosters having to set a second, NEXT_PUBLIC_-prefixed
// var. Falls back to `''` (not left undefined) so the browser bundle never
// ends up with a bare `process.env` reference.
publicEnvDefines['process.env.MAINTENANCE_MODE'] = JSON.stringify(env.MAINTENANCE_MODE ?? '')

// Sentry init (lib/sentry-client-options.ts, reached via router.tsx) reads
// these at runtime in the browser. When a var is unset it gets no define
// entry above, which would leave a literal `process.env.*` in the built
Expand Down
Loading