fix(studio): add @supabase/ssr to Connect Sheet install step (#44694)

The Connect Sheet install step only listed `@supabase/supabase-js`, but
the generated code for Next.js (app router) and Remix imports from
`@supabase/ssr` – so users following the steps immediately hit import
errors.

**Added:**
- `EXTRA_PACKAGES` map in `connect.schema.ts` – frameworks declare
additional packages on top of the base library install, keyed by
`framework/variant` for granularity (e.g. `nextjs/app` gets
`@supabase/ssr`, `nextjs/pages` does not)
- Install content component appends extras automatically
- Step title pluralises to "Install packages" when extras are present
- Tests for extra packages, variant-specific install commands, and step
titles

**Changed:**
- Next.js steps now branch on `frameworkVariant` so app router and pages
router can have different install steps
- Remix gets an explicit entry in the step tree (previously fell through
to DEFAULT)

## To test

- Open Connect Sheet → Framework → Next.js → App Router
- Install step should say "Install packages" and show `npm install
@supabase/supabase-js @supabase/ssr`
- Switch to Pages Router
- Install step should say "Install package" and show `npm install
@supabase/supabase-js`
- Switch to Remix
- Install step should say "Install packages" and show `npm install
@supabase/supabase-js @supabase/ssr`
- Other frameworks (Vue, SvelteKit, etc.) should be unchanged

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Enhanced package installation guidance to include framework-specific
additional packages (e.g., @supabase/ssr for Next.js App Router and
Remix).
* Installation step labels now accurately reflect the number of packages
being installed.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
This commit is contained in:
Alaister YoungandAlaister Young authored and GitHub committed 2026-04-09 07:07:55 -06:00
1 parent 4f90833ed9
commit ed09fbc6ad
3 files changed
+128 -14

No files matched your search

@@ -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)
@@ -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<string, string> = {
supabasejs: 'npm install @supabase/supabase-js',
@@ -12,6 +12,19 @@ export const INSTALL_COMMANDS: Record<string, string> = {
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<string, Record<string, string[]>> = {
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],
},
},
@@ -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) {