diff --git a/apps/studio/components/interfaces/ConnectSheet/connect.schema.test.ts b/apps/studio/components/interfaces/ConnectSheet/connect.schema.test.ts index c480deb7e9f..ca1df29f660 100644 --- a/apps/studio/components/interfaces/ConnectSheet/connect.schema.test.ts +++ b/apps/studio/components/interfaces/ConnectSheet/connect.schema.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest' import { resolveSteps } from './connect.resolver' -import { connectSchema, INSTALL_COMMANDS } from './connect.schema' +import { connectSchema, EXTRA_PACKAGES, INSTALL_COMMANDS } from './connect.schema' import type { ConnectState } from './Connect.types' // ============================================================================ @@ -150,6 +150,20 @@ describe('connect.schema:INSTALL_COMMANDS', () => { }) }) +describe('connect.schema:EXTRA_PACKAGES', () => { + test('should have @supabase/ssr as extra package for nextjs app router with supabasejs', () => { + expect(EXTRA_PACKAGES.supabasejs?.['nextjs/app']).toContain('@supabase/ssr') + }) + + test('should not have extra packages for nextjs pages router', () => { + expect(EXTRA_PACKAGES.supabasejs?.['nextjs/pages']).toBeUndefined() + }) + + test('should have @supabase/ssr as extra package for remix with supabasejs', () => { + expect(EXTRA_PACKAGES.supabasejs?.remix).toContain('@supabase/ssr') + }) +}) + // ============================================================================ // Steps Resolution Integration Tests // ============================================================================ @@ -165,6 +179,48 @@ describe('connect.schema:steps resolution', () => { expect(steps.find((s) => s.id === 'install-skills')).toBeDefined() }) + test('should use "Install packages" title for nextjs app router', () => { + const state: ConnectState = { + mode: 'framework', + framework: 'nextjs', + frameworkVariant: 'app', + frameworkUi: false, + } + const steps = resolveSteps(connectSchema, state) + const installStep = steps.find((s) => s.id === 'install') + + expect(installStep?.title).toBe('Install packages') + }) + + test('should use "Install package" title for nextjs pages router', () => { + const state: ConnectState = { + mode: 'framework', + framework: 'nextjs', + frameworkVariant: 'pages', + frameworkUi: false, + } + const steps = resolveSteps(connectSchema, state) + const installStep = steps.find((s) => s.id === 'install') + + expect(installStep?.title).toBe('Install package') + }) + + test('should use "Install packages" title for remix install step', () => { + const state: ConnectState = { mode: 'framework', framework: 'remix' } + const steps = resolveSteps(connectSchema, state) + const installStep = steps.find((s) => s.id === 'install') + + expect(installStep?.title).toBe('Install packages') + }) + + test('should use "Install package" title for frameworks without extra packages', () => { + const state: ConnectState = { mode: 'framework', framework: 'vuejs' } + const steps = resolveSteps(connectSchema, state) + const installStep = steps.find((s) => s.id === 'install') + + expect(installStep?.title).toBe('Install package') + }) + test('should resolve shadcn steps for nextjs with frameworkUi true', () => { const state: ConnectState = { mode: 'framework', framework: 'nextjs', frameworkUi: true } const steps = resolveSteps(connectSchema, state) diff --git a/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts b/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts index 90d16a9e1ce..04200614e29 100644 --- a/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts +++ b/apps/studio/components/interfaces/ConnectSheet/connect.schema.ts @@ -1,7 +1,7 @@ import type { ConnectSchema, StepDefinition } from './Connect.types' /** - * Install commands for different packages + * Base install commands for each library. */ export const INSTALL_COMMANDS: Record = { supabasejs: 'npm install @supabase/supabase-js', @@ -12,6 +12,19 @@ export const INSTALL_COMMANDS: Record = { supabasekt: 'implementation("io.github.jan-tennert.supabase:supabase-kt:VERSION")', } +/** + * Extra packages required by specific frameworks on top of the base library. + * Keyed by library, then by framework (or framework/variant for more specificity). + * The install step checks the most specific key first (e.g. "nextjs/app"), + * then falls back to the framework key (e.g. "nextjs"). + */ +export const EXTRA_PACKAGES: Record> = { + supabasejs: { + 'nextjs/app': ['@supabase/ssr'], + remix: ['@supabase/ssr'], + }, +} + // ============================================================================ // Step Definitions (reusable) // All content paths use template syntax: {{stateKey}} is replaced with state values @@ -24,6 +37,13 @@ const frameworkInstallStep: StepDefinition = { content: 'steps/install', } +const frameworkInstallPackagesStep: StepDefinition = { + id: 'install', + title: 'Install packages', + description: 'Run this command to install the required dependencies.', + content: 'steps/install', +} + const frameworkConfigureStep: StepDefinition = { id: 'configure', title: 'Add files', @@ -307,15 +327,35 @@ export const connectSchema: ConnectSchema = { framework: { framework: { nextjs: { - frameworkUi: { - true: [ - frameworkInstallStep, - frameworkShadcnStep, - frameworkShadcnEnvStep, - frameworkShadcnExploreStep, - skillsInstallStep, - ], - DEFAULT: [frameworkInstallStep, frameworkNextJsFilesStep, skillsInstallStep], + frameworkVariant: { + app: { + frameworkUi: { + true: [ + frameworkInstallPackagesStep, + frameworkShadcnStep, + frameworkShadcnEnvStep, + frameworkShadcnExploreStep, + skillsInstallStep, + ], + DEFAULT: [ + frameworkInstallPackagesStep, + frameworkNextJsFilesStep, + skillsInstallStep, + ], + }, + }, + DEFAULT: { + frameworkUi: { + true: [ + frameworkInstallStep, + frameworkShadcnStep, + frameworkShadcnEnvStep, + frameworkShadcnExploreStep, + skillsInstallStep, + ], + DEFAULT: [frameworkInstallStep, frameworkNextJsFilesStep, skillsInstallStep], + }, + }, }, }, react: { @@ -330,6 +370,7 @@ export const connectSchema: ConnectSchema = { DEFAULT: [frameworkInstallStep, frameworkReactFilesStep, skillsInstallStep], }, }, + remix: [frameworkInstallPackagesStep, frameworkConfigureStep, skillsInstallStep], DEFAULT: [frameworkInstallStep, frameworkConfigureStep, skillsInstallStep], }, }, diff --git a/apps/studio/components/interfaces/ConnectSheet/content/steps/install/content.tsx b/apps/studio/components/interfaces/ConnectSheet/content/steps/install/content.tsx index 5ed41d483cb..8ae30196354 100644 --- a/apps/studio/components/interfaces/ConnectSheet/content/steps/install/content.tsx +++ b/apps/studio/components/interfaces/ConnectSheet/content/steps/install/content.tsx @@ -2,19 +2,36 @@ import { Copy } from 'lucide-react' import { useMemo, useState } from 'react' import { Button, copyToClipboard } from 'ui' -import { INSTALL_COMMANDS } from '@/components/interfaces/ConnectSheet/connect.schema' +import { + EXTRA_PACKAGES, + INSTALL_COMMANDS, +} from '@/components/interfaces/ConnectSheet/connect.schema' import type { StepContentProps } from '@/components/interfaces/ConnectSheet/Connect.types' import { resolveFrameworkLibraryKey } from '@/components/interfaces/ConnectSheet/Connect.utils' /** * Gets the install command for the current framework selection. + * Appends any framework-specific extra packages from EXTRA_PACKAGES, + * checking the most specific key first (framework/variant), then framework-only. */ function getInstallCommand(state: StepContentProps['state']): string | null { const libraryKey = resolveFrameworkLibraryKey(state) + if (!libraryKey || !INSTALL_COMMANDS[libraryKey]) return null - if (libraryKey && INSTALL_COMMANDS[libraryKey]) return INSTALL_COMMANDS[libraryKey] + let command = INSTALL_COMMANDS[libraryKey] - return null + const { framework, frameworkVariant } = state + if (framework) { + const extraMap = EXTRA_PACKAGES[libraryKey] + const extras = + (frameworkVariant && extraMap?.[`${framework}/${frameworkVariant}`]) || + extraMap?.[String(framework)] + if (extras?.length) { + command += ' ' + extras.join(' ') + } + } + + return command } function InstallContent({ state }: StepContentProps) {