From c0f1ef51fb9c083ab3a2e6867def0c4c7b2fa521 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Mon, 3 Aug 2026 10:52:44 +1000 Subject: [PATCH] feat(docs): migrate resources and getting-started to ContentListings (#48517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What kind of change does this PR introduce? Docs update / follow-up to #48379. ## What is the current behavior? `/guides/resources` and `/guides/getting-started` hand-roll `GlassPanel` grids in MDX. They look like ContentListings cards after the chrome PR, but they do not use the shared data files, so they miss PostHog `docs_content_listing_clicked` telemetry and the CONTRIBUTING contribution path. ## What is the new behavior? Those pages use `` backed by `resources.data.ts` and `getting-started.data.ts`, same pattern as storage. - Section-level `$Show` wrappers stay for framework / web / mobile blocks - Nimbus stays a `$Partial` behind `$Show` - New optional per-item `feature` field gates SDK links (e.g. Flutter / Swift / Kotlin) without splitting whole sections - CONTRIBUTING notes when to use `feature` vs a partial-level `$Show` ## To test Compare the following against `master`: - [Resources](https://docs-git-dnywh-docs-content-listings-resources-239158-supabase.vercel.app/docs/guides/resources): overview, migrate, and postgres grids; icons in light/dark - [Getting started](https://docs-git-dnywh-docs-content-listings-resources-239158-supabase.vercel.app/docs/guides/getting-started): overview, use cases, framework quickstarts, web demos, mobile tutorials; nimbus partial when enabled - Click a card and confirm `docs_content_listing_clicked` fires with the expected `listingId` Everything should look and feel the same. It’s just that we’re using `ContentListings` instead of `GlassPanel` grids. ## Summary by CodeRabbit - **New Features** - Added centralized Getting Started and Resources content listings, including quickstarts, demos, tutorials, migration guides, and Postgres resources. - Added feature-based visibility controls for individual content listing items. - **Improvements** - Disabled content is now automatically hidden from documentation pages and generated Markdown. - Pages and sections with no available content are omitted entirely. - External documentation links are more secure. - Updated contribution guidance with instructions and examples for feature flags. --- apps/docs/CONTRIBUTING.md | 2 +- .../ContentListings.client.tsx | 12 +- apps/docs/content/guides/getting-started.mdx | 372 +----------------- apps/docs/content/guides/resources.mdx | 186 +-------- .../content-listings/getting-started.data.ts | 327 +++++++++++++++ apps/docs/data/content-listings/index.ts | 16 + .../data/content-listings/resources.data.ts | 131 ++++++ .../markdown-schema/ContentListings.ts | 11 +- apps/docs/lib/content-listings.test.ts | 68 ++++ apps/docs/lib/content-listings.utils.ts | 8 +- apps/docs/lib/content-listings.zod.mjs | 5 + 11 files changed, 581 insertions(+), 557 deletions(-) create mode 100644 apps/docs/data/content-listings/getting-started.data.ts create mode 100644 apps/docs/data/content-listings/resources.data.ts diff --git a/apps/docs/CONTRIBUTING.md b/apps/docs/CONTRIBUTING.md index 41cb00108b4..3f868c0feb1 100644 --- a/apps/docs/CONTRIBUTING.md +++ b/apps/docs/CONTRIBUTING.md @@ -260,7 +260,7 @@ Run `pnpm test:local lib/content-listings.test.ts` from apps/docs. **Manually add content listings:** 1. Add or update a `ContentListingGroup` export in [`data/content-listings/[topic].data.ts`](data/content-listings/). The `id` field must be globally unique across all listing groups. For example, use `storage-get-started` rather than `get-started`. The ID is both the lookup key and the telemetry `listingId`. -2. Place the component inline in guide MDX, for example ``. Use a partial only when the block is reused or gated with `$Show` at the partial level. +2. Place the component inline in guide MDX, for example ``. Use a partial only when the block is reused or gated with `$Show` at the partial level. For individual items that depend on a feature flag (for example `sdk:dart`), set `feature` on the item instead of wrapping the whole listing. 3. Run `pnpm test:local lib/content-listings.test.ts` from `apps/docs`. Code snippets for manually adding content listings are available in [`.vscode/content-listing.code-snippets`](../../.vscode/content-listing.code-snippets). Use `cl-data` for a data export with a namespaced ID. Use `cl-inline` for an MDX component. diff --git a/apps/docs/components/ContentListings/ContentListings.client.tsx b/apps/docs/components/ContentListings/ContentListings.client.tsx index f71fd0236b1..548bde14d50 100644 --- a/apps/docs/components/ContentListings/ContentListings.client.tsx +++ b/apps/docs/components/ContentListings/ContentListings.client.tsx @@ -2,13 +2,14 @@ import type { ContentListingGroup, ContentListingItem } from '~/lib/content-listings.schema' import { + filterContentListingItems, getContentListingById, getContentListingGroupLabel, isExternalContentListingHref, } from '~/lib/content-listings.utils' import { useSendTelemetryEvent } from '~/lib/telemetry' import Link from 'next/link' -import { useCallback } from 'react' +import { useCallback, useMemo } from 'react' import { Badge } from 'ui' import { GlassPanel } from 'ui-patterns/GlassPanel' import { Heading } from 'ui/src/components/CustomHTMLElements' @@ -52,10 +53,13 @@ function ContentListingGroupHeading({ group }: { group: ContentListingGroup }) { function ContentListingsGroup({ group }: { group: ContentListingGroup }) { const { trackClick } = useContentListingClickHandler(group) + const items = useMemo(() => filterContentListingItems(group.items), [group.items]) const isGrid = group.type === 'grid' const listClassName = isGrid ? 'grid md:grid-cols-12 gap-4' : 'list-disc pl-6 space-y-2' const gridItemClassName = isGrid ? GRID_ITEM_CLASS[group.columns ?? 3] : undefined + if (!items.length) return null + // Heading stays outside `not-prose` so it inherits the surrounding MDX prose // typography. The list itself opts out so its explicit Tailwind layout wins. return ( @@ -64,7 +68,7 @@ function ContentListingsGroup({ group }: { group: ContentListingGroup }) {
{group.description &&

{group.description}

}
    - {group.items.map((item) => { + {items.map((item) => { const external = isExternalContentListingHref(item.href) const key = `${group.id}-${item.href}` @@ -77,6 +81,7 @@ function ContentListingsGroup({ group }: { group: ContentListingGroup }) { className="block h-full" onClick={() => trackClick(item)} target={external ? '_blank' : undefined} + rel={external ? 'noopener noreferrer' : undefined} > trackClick(item)} target={external ? '_blank' : undefined} + rel={external ? 'noopener noreferrer' : undefined} > {item.title}: {item.description} @@ -120,7 +126,7 @@ function ContentListingsGroup({ group }: { group: ContentListingGroup }) { export function ContentListings({ id }: { id: string }) { const group = getContentListingById(id) - if (!group || !group.items.length) return null + if (!group || !filterContentListingItems(group.items).length) return null return (
    diff --git a/apps/docs/content/guides/getting-started.mdx b/apps/docs/content/guides/getting-started.mdx index 9ec3e750fd9..ede5583753d 100644 --- a/apps/docs/content/guides/getting-started.mdx +++ b/apps/docs/content/guides/getting-started.mdx @@ -5,214 +5,13 @@ description: 'Resources for getting started with Supabase.' hideToc: true --- -
    + -
    - -
    - - - Develop with Supabase AI-first using plugins, MCP, and skills. - - - - - Learn about the different API keys in Supabase and how to use them. - - - - - Use the Supabase CLI to develop locally and collaborate between teams. - - -
    - -
    - -
    - -## Use cases - -
    - - - Build AI-enabled applications using our Vector toolkit. - - - - - Clone, deploy, and fully customize a SaaS subscription application with Next.js. - - - - - Postgres full-text search, image storage, and more. - - -
    - -## Framework quickstarts + <$Show if="docs:framework_quickstarts"> -
    - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from a React app. - - - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from a Next.js app. - - - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from a Nuxt app. - - - - - Learn how to create a Supabase project, add some sample data to your database, secure it with - auth, and query the data from a Hono app. - - - - - Learn how to create a Supabase project, add some sample data to your database using Prisma - migration and seeds, and query the data from a RedwoodJS app. - - - <$Show if="sdk:dart"> - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from a Flutter app. - - - - <$Show if="sdk:swift"> - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from an iOS app. - - - - <$Show if="sdk:kotlin"> - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from an Android Kotlin app. - - - - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from a SvelteKit app. - - - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from a SolidJS app. - - - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from a Vue app. - - - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from a TanStack Start app. - - - - - Learn how to create a Supabase project, add some sample data to your database, and query the - data from a Refine app. - - -
    + @@ -224,173 +23,12 @@ hideToc: true <$Show if="docs:web_apps"> -## Web app demos + -
    - - - Learn how to build a user management app with Next.js and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to build a user management app with Vue 3 and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to build a user management app with Angular and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to build a user management app with RedwoodJS and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to build a user management app with Svelte and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to build a user management app with SvelteKit and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to build a user management app with Refine and Supabase Database, Auth, and Storage functionality. - - -
    <$Show if="docs:mobile_tutorials"> -## Mobile tutorials + -
    - <$Show if="sdk:dart"> - - - Learn how to build a user management app with Flutter and Supabase Database, Auth, and Storage functionality. - - - - - - Learn how to build a user management app with Expo React Native and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to implement social authentication in an app with Expo React Native and Supabase Database and Auth functionality. - - - <$Show if="sdk:kotlin"> - - - Learn how to build a product management app with Android and Supabase Database, Auth, and Storage functionality. - - - - <$Show if="sdk:swift"> - - - Learn how to build a user management app with iOS and Supabase Database, Auth, and Storage functionality. - - - - - - Learn how to build a user management app with Ionic React and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to build a user management app with Ionic Vue and Supabase Database, Auth, and Storage functionality. - - - - - Learn how to build a user management app with Ionic Angular and Supabase Database, Auth, and Storage functionality. - - -
    diff --git a/apps/docs/content/guides/resources.mdx b/apps/docs/content/guides/resources.mdx index 5268fbf5927..158c2666525 100644 --- a/apps/docs/content/guides/resources.mdx +++ b/apps/docs/content/guides/resources.mdx @@ -6,188 +6,8 @@ hideToc: true {/* */} -
    + -
    + -
    - - - Official GitHub examples, curated content from the community, and more. - - - - - Definitions for terminology and acronyms used in the Supabase documentation. - - -
    - -
    - -
    -
    - -## Migrate to Supabase - -
    - -
    - - - Move your auth users from Auth0 to a Supabase project. - - - - - Move your auth users from a Firebase project to a Supabase project. - - - - - Migrate the contents of a Firestore collection to a single Postgres table. - - - - - Convert your Firebase Storage files to Supabase Storage. - - - - - Migrate your Heroku Postgres database to Supabase. - - - - - Migrate your Render Postgres database to Supabase. - - - - - Migrate your Amazon RDS database to Supabase. - - - - - Migrate your Postgres database to Supabase. - - - - - Migrate your MySQL database to Supabase. - - - - - Migrate your Microsoft SQL Server database to Supabase. - - -
    - -
    - -
    -
    - -## Postgres resources - -
    - -
    - - - Improve query performance using various index types in Postgres. - - - - - Understand the types of foreign key constraint deletes. - - - - - Delete all tables in a given schema. - - - - - Retrieve the first row in each distinct group. - - - - - Find out which version of Postgres you are running. - - -
    - -
    - -{/* end of container */} - -
    + diff --git a/apps/docs/data/content-listings/getting-started.data.ts b/apps/docs/data/content-listings/getting-started.data.ts new file mode 100644 index 00000000000..2d5ae192dce --- /dev/null +++ b/apps/docs/data/content-listings/getting-started.data.ts @@ -0,0 +1,327 @@ +import type { ContentListingGroup } from '~/lib/content-listings.schema' + +export const gettingStartedOverview: ContentListingGroup = { + id: 'getting-started-overview', + type: 'grid', + items: [ + { + title: 'Build with AI tools', + href: '/guides/ai-tools', + description: 'Develop with Supabase AI-first using plugins, MCP, and skills.', + }, + { + title: 'API Keys', + href: '/guides/getting-started/api-keys', + description: 'Learn about the different API keys in Supabase and how to use them.', + }, + { + title: 'Local Development', + href: '/guides/local-development', + description: 'Use the Supabase CLI to develop locally and collaborate between teams.', + }, + ], +} + +export const gettingStartedUseCases: ContentListingGroup = { + id: 'getting-started-use-cases', + heading: 'Use cases', + headingLevel: 'h3', + type: 'grid', + items: [ + { + title: 'AI, Vectors, and embeddings', + href: '/guides/ai#examples', + icon: '/docs/img/icons/openai_logo', + hasLightIcon: true, + description: 'Build AI-enabled applications using our Vector toolkit.', + }, + { + title: 'Subscription Payments (SaaS)', + href: 'https://github.com/vercel/nextjs-subscription-payments#nextjs-subscription-payments-starter', + icon: '/docs/img/icons/nextjs-icon', + hasLightIcon: false, + description: + 'Clone, deploy, and fully customize a SaaS subscription application with Next.js.', + }, + { + title: 'Partner Gallery', + href: 'https://github.com/supabase-community/partner-gallery-example#supabase-partner-gallery-example', + icon: '/docs/img/icons/nextjs-icon', + hasLightIcon: false, + description: 'Postgres full-text search, image storage, and more.', + }, + ], +} + +export const gettingStartedFrameworkQuickstarts: ContentListingGroup = { + id: 'getting-started-framework-quickstarts', + heading: 'Framework quickstarts', + headingLevel: 'h3', + type: 'grid', + items: [ + { + title: 'React', + href: '/guides/getting-started/quickstarts/reactjs', + icon: '/docs/img/icons/react-icon', + hasLightIcon: false, + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a React app.', + }, + { + title: 'Next.js', + href: '/guides/getting-started/quickstarts/nextjs', + icon: '/docs/img/icons/nextjs-icon', + hasLightIcon: true, + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Next.js app.', + }, + { + title: 'Nuxt', + href: '/guides/getting-started/quickstarts/nuxtjs', + icon: '/docs/img/icons/nuxt-icon', + hasLightIcon: false, + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Nuxt app.', + }, + { + title: 'Hono', + href: '/guides/getting-started/quickstarts/hono', + icon: '/docs/img/icons/hono-icon', + hasLightIcon: false, + description: + 'Learn how to create a Supabase project, add some sample data to your database, secure it with auth, and query the data from a Hono app.', + }, + { + title: 'RedwoodJS', + href: '/guides/getting-started/quickstarts/redwoodjs', + icon: '/docs/img/icons/redwood-icon', + hasLightIcon: false, + description: + 'Learn how to create a Supabase project, add some sample data to your database using Prisma migration and seeds, and query the data from a RedwoodJS app.', + }, + { + title: 'Flutter', + href: '/guides/getting-started/quickstarts/flutter', + icon: '/docs/img/icons/flutter-icon', + hasLightIcon: false, + feature: 'sdk:dart', + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Flutter app.', + }, + { + title: 'iOS SwiftUI', + href: '/guides/getting-started/quickstarts/ios-swiftui', + icon: '/docs/img/icons/swift-icon', + hasLightIcon: false, + feature: 'sdk:swift', + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from an iOS app.', + }, + { + title: 'Android Kotlin', + href: '/guides/getting-started/quickstarts/kotlin', + icon: '/docs/img/icons/kotlin-icon', + hasLightIcon: false, + feature: 'sdk:kotlin', + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from an Android Kotlin app.', + }, + { + title: 'SvelteKit', + href: '/guides/getting-started/quickstarts/sveltekit', + icon: '/docs/img/icons/svelte-icon', + hasLightIcon: false, + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SvelteKit app.', + }, + { + title: 'SolidJS', + href: '/guides/getting-started/quickstarts/solidjs', + icon: '/docs/img/icons/solidjs-icon', + hasLightIcon: false, + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SolidJS app.', + }, + { + title: 'Vue', + href: '/guides/getting-started/quickstarts/vue', + icon: '/docs/img/icons/vuejs-icon', + hasLightIcon: false, + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Vue app.', + }, + { + title: 'TanStack Start', + href: '/guides/getting-started/quickstarts/tanstack', + icon: '/docs/img/icons/tanstack-icon', + hasLightIcon: true, + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a TanStack Start app.', + }, + { + title: 'Refine', + href: '/guides/getting-started/quickstarts/refine', + icon: '/docs/img/icons/refine-icon', + hasLightIcon: false, + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Refine app.', + }, + ], +} + +export const gettingStartedWebAppDemos: ContentListingGroup = { + id: 'getting-started-web-app-demos', + heading: 'Web app demos', + headingLevel: 'h3', + type: 'grid', + items: [ + { + title: 'Next.js', + href: '/guides/getting-started/tutorials/with-nextjs', + icon: '/docs/img/icons/nextjs-icon', + hasLightIcon: true, + description: + 'Learn how to build a user management app with Next.js and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'React', + href: '/guides/getting-started/tutorials/with-react', + icon: '/docs/img/icons/react-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'Vue 3', + href: '/guides/getting-started/tutorials/with-vue-3', + icon: '/docs/img/icons/vuejs-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with Vue 3 and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'Nuxt 3', + href: '/guides/getting-started/tutorials/with-nuxt-3', + icon: '/docs/img/icons/nuxt-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'Angular', + href: '/guides/getting-started/tutorials/with-angular', + icon: '/docs/img/icons/angular-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with Angular and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'RedwoodJS', + href: '/guides/getting-started/tutorials/with-redwoodjs', + icon: '/docs/img/icons/redwood-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with RedwoodJS and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'Svelte', + href: '/guides/getting-started/tutorials/with-svelte', + icon: '/docs/img/icons/svelte-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with Svelte and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'SvelteKit', + href: '/guides/getting-started/tutorials/with-sveltekit', + icon: '/docs/img/icons/svelte-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with SvelteKit and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'Refine', + href: '/guides/getting-started/tutorials/with-refine', + icon: '/docs/img/icons/refine-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with Refine and Supabase Database, Auth, and Storage functionality.', + }, + ], +} + +export const gettingStartedMobileTutorials: ContentListingGroup = { + id: 'getting-started-mobile-tutorials', + heading: 'Mobile tutorials', + headingLevel: 'h3', + type: 'grid', + items: [ + { + title: 'Flutter', + href: '/guides/getting-started/tutorials/with-flutter', + icon: '/docs/img/icons/flutter-icon', + hasLightIcon: false, + feature: 'sdk:dart', + description: + 'Learn how to build a user management app with Flutter and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'Expo React Native', + href: '/guides/getting-started/tutorials/with-expo-react-native', + icon: '/docs/img/icons/expo-icon', + hasLightIcon: true, + description: + 'Learn how to build a user management app with Expo React Native and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'Expo React Native Social Auth', + href: '/guides/auth/quickstarts/with-expo-react-native-social-auth', + icon: '/docs/img/icons/expo-icon', + hasLightIcon: true, + description: + 'Learn how to implement social authentication in an app with Expo React Native and Supabase Database and Auth functionality.', + }, + { + title: 'Android Kotlin', + href: '/guides/getting-started/tutorials/with-kotlin', + icon: '/docs/img/icons/kotlin-icon', + hasLightIcon: false, + feature: 'sdk:kotlin', + description: + 'Learn how to build a product management app with Android and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'iOS Swift', + href: '/guides/getting-started/tutorials/with-swift', + icon: '/docs/img/icons/swift-icon', + hasLightIcon: false, + feature: 'sdk:swift', + description: + 'Learn how to build a user management app with iOS and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'Ionic React', + href: '/guides/getting-started/tutorials/with-ionic-react', + icon: '/docs/img/icons/ionic-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with Ionic React and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'Ionic Vue', + href: '/guides/getting-started/tutorials/with-ionic-vue', + icon: '/docs/img/icons/ionic-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with Ionic Vue and Supabase Database, Auth, and Storage functionality.', + }, + { + title: 'Ionic Angular', + href: '/guides/getting-started/tutorials/with-ionic-angular', + icon: '/docs/img/icons/ionic-icon', + hasLightIcon: false, + description: + 'Learn how to build a user management app with Ionic Angular and Supabase Database, Auth, and Storage functionality.', + }, + ], +} diff --git a/apps/docs/data/content-listings/index.ts b/apps/docs/data/content-listings/index.ts index bf0e5f93537..ef18cdc48ae 100644 --- a/apps/docs/data/content-listings/index.ts +++ b/apps/docs/data/content-listings/index.ts @@ -11,8 +11,16 @@ import { functionsExamplesWebhooksPayments, functionsGetStarted, } from './functions.data' +import { + gettingStartedFrameworkQuickstarts, + gettingStartedMobileTutorials, + gettingStartedOverview, + gettingStartedUseCases, + gettingStartedWebAppDemos, +} from './getting-started.data' import { logDrainsDestinations } from './log-drains.data' import { realtimeExamples, realtimeGetStarted, realtimeResources } from './realtime.data' +import { resourcesMigrate, resourcesOverview, resourcesPostgres } from './resources.data' import { selfHostingCommunity, selfHostingGetHelp, @@ -37,10 +45,18 @@ const ALL_GROUPS: readonly ContentListingGroup[] = [ functionsExamplesAiMedia, functionsExamplesMessaging, functionsExamplesOperations, + gettingStartedOverview, + gettingStartedUseCases, + gettingStartedFrameworkQuickstarts, + gettingStartedWebAppDemos, + gettingStartedMobileTutorials, logDrainsDestinations, realtimeGetStarted, realtimeExamples, realtimeResources, + resourcesOverview, + resourcesMigrate, + resourcesPostgres, selfHostingGetStarted, selfHostingCommunity, selfHostingResolveIssues, diff --git a/apps/docs/data/content-listings/resources.data.ts b/apps/docs/data/content-listings/resources.data.ts new file mode 100644 index 00000000000..bca9a8af826 --- /dev/null +++ b/apps/docs/data/content-listings/resources.data.ts @@ -0,0 +1,131 @@ +import type { ContentListingGroup } from '~/lib/content-listings.schema' + +export const resourcesOverview: ContentListingGroup = { + id: 'resources-overview', + type: 'grid', + items: [ + { + title: 'Examples', + href: '/guides/getting-started', + description: 'Official GitHub examples, curated content from the community, and more.', + }, + { + title: 'Glossary', + href: '/guides/resources/glossary', + description: 'Definitions for terminology and acronyms used in the Supabase documentation.', + }, + ], +} + +export const resourcesMigrate: ContentListingGroup = { + id: 'resources-migrate', + heading: 'Migrate to Supabase', + headingLevel: 'h3', + type: 'grid', + items: [ + { + title: 'Auth0', + href: '/guides/platform/migrating-to-supabase/auth0', + icon: '/docs/img/icons/auth0-icon', + hasLightIcon: true, + description: 'Move your auth users from Auth0 to a Supabase project.', + }, + { + title: 'Firebase Auth', + href: '/guides/platform/migrating-to-supabase/firebase-auth', + icon: '/docs/img/icons/firebase-icon', + hasLightIcon: false, + description: 'Move your auth users from a Firebase project to a Supabase project.', + }, + { + title: 'Firestore Data', + href: '/guides/platform/migrating-to-supabase/firestore-data', + icon: '/docs/img/icons/firebase-icon', + hasLightIcon: false, + description: 'Migrate the contents of a Firestore collection to a single Postgres table.', + }, + { + title: 'Firebase Storage', + href: '/guides/platform/migrating-to-supabase/firebase-storage', + icon: '/docs/img/icons/firebase-icon', + hasLightIcon: false, + description: 'Convert your Firebase Storage files to Supabase Storage.', + }, + { + title: 'Heroku', + href: '/guides/platform/migrating-to-supabase/heroku', + icon: '/docs/img/icons/heroku-icon', + hasLightIcon: false, + description: 'Migrate your Heroku Postgres database to Supabase.', + }, + { + title: 'Render', + href: '/guides/platform/migrating-to-supabase/render', + icon: '/docs/img/icons/render-icon', + hasLightIcon: false, + description: 'Migrate your Render Postgres database to Supabase.', + }, + { + title: 'Amazon RDS', + href: '/guides/platform/migrating-to-supabase/amazon-rds', + icon: '/docs/img/icons/aws-rds-icon', + hasLightIcon: false, + description: 'Migrate your Amazon RDS database to Supabase.', + }, + { + title: 'Postgres', + href: '/guides/platform/migrating-to-supabase/postgres', + icon: '/docs/img/icons/postgres-icon', + hasLightIcon: false, + description: 'Migrate your Postgres database to Supabase.', + }, + { + title: 'MySQL', + href: '/guides/platform/migrating-to-supabase/mysql', + icon: '/docs/img/icons/mysql-icon', + hasLightIcon: false, + description: 'Migrate your MySQL database to Supabase.', + }, + { + title: 'Microsoft SQL Server', + href: '/guides/platform/migrating-to-supabase/mssql', + icon: '/docs/img/icons/mssql-icon', + hasLightIcon: false, + description: 'Migrate your Microsoft SQL Server database to Supabase.', + }, + ], +} + +export const resourcesPostgres: ContentListingGroup = { + id: 'resources-postgres', + heading: 'Postgres resources', + headingLevel: 'h3', + type: 'grid', + items: [ + { + title: 'Managing Indexes', + href: '/guides/database/postgres/indexes', + description: 'Improve query performance using various index types in Postgres.', + }, + { + title: 'Cascade Deletes', + href: '/guides/database/postgres/cascade-deletes', + description: 'Understand the types of foreign key constraint deletes.', + }, + { + title: 'Drop all tables in schema', + href: '/guides/database/postgres/dropping-all-tables-in-schema', + description: 'Delete all tables in a given schema.', + }, + { + title: 'Select first row per group', + href: '/guides/database/postgres/first-row-in-group', + description: 'Retrieve the first row in each distinct group.', + }, + { + title: 'Print Postgres version', + href: '/guides/database/postgres/which-version-of-postgres', + description: 'Find out which version of Postgres you are running.', + }, + ], +} diff --git a/apps/docs/internals/markdown-schema/ContentListings.ts b/apps/docs/internals/markdown-schema/ContentListings.ts index 299b23c06ec..ce78463b70a 100644 --- a/apps/docs/internals/markdown-schema/ContentListings.ts +++ b/apps/docs/internals/markdown-schema/ContentListings.ts @@ -1,6 +1,10 @@ import { withDocsBasePath } from '~/internals/internal-links' import type { ContentListingGroup } from '~/lib/content-listings.schema' -import { getContentListingById, isExternalContentListingHref } from '~/lib/content-listings.utils' +import { + filterContentListingItems, + getContentListingById, + isExternalContentListingHref, +} from '~/lib/content-listings.utils' import { getInternalLinkBaseUrl } from '../internal-links' @@ -14,6 +18,9 @@ export function serializeContentListingGroupToMarkdown( group: ContentListingGroup, linkBaseUrl: string ): string { + const items = filterContentListingItems(group.items) + if (!items.length) return '' + const lines: string[] = [] if (group.heading) { const level = group.headingLevel ?? 'h2' @@ -26,7 +33,7 @@ export function serializeContentListingGroupToMarkdown( lines.push('') } - for (const item of group.items) { + for (const item of items) { const href = isExternalContentListingHref(item.href) ? item.href : `${linkBaseUrl}${withDocsBasePath(item.href)}` diff --git a/apps/docs/lib/content-listings.test.ts b/apps/docs/lib/content-listings.test.ts index 0c609a60699..87b0d5d82fe 100644 --- a/apps/docs/lib/content-listings.test.ts +++ b/apps/docs/lib/content-listings.test.ts @@ -144,6 +144,74 @@ describe('serializeContentListingGroupToMarkdown', () => { expect(markdown).not.toMatch(/^#+\s/m) expect(markdown).toContain('**[Connect]') }) + + it('omits feature-gated items when those features are disabled', () => { + const previous = process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL + process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL = 'true' + + try { + const markdown = serializeContentListingGroupToMarkdown( + { + id: 'frameworks', + heading: 'Frameworks', + items: [ + { + title: 'React', + href: '/guides/getting-started/quickstarts/reactjs', + description: 'Web framework.', + }, + { + title: 'Flutter', + href: '/guides/getting-started/quickstarts/flutter', + description: 'Mobile framework.', + feature: 'sdk:dart', + }, + ], + }, + '' + ) + + expect(markdown).toContain('**[React]') + expect(markdown).not.toContain('Flutter') + } finally { + if (previous === undefined) { + delete process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL + } else { + process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL = previous + } + } + }) + + it('returns empty string when every item is feature-gated off', () => { + const previous = process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL + process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL = 'true' + + try { + const markdown = serializeContentListingGroupToMarkdown( + { + id: 'sdk-only', + heading: 'SDKs', + items: [ + { + title: 'Flutter', + href: '/guides/getting-started/quickstarts/flutter', + description: 'Mobile framework.', + feature: 'sdk:dart', + }, + ], + }, + '' + ) + + expect(markdown).toBe('') + } finally { + if (previous === undefined) { + delete process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL + } else { + process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL = previous + } + } + }) }) describe('ContentListings markdown handler', () => { diff --git a/apps/docs/lib/content-listings.utils.ts b/apps/docs/lib/content-listings.utils.ts index 10c73b4d4f1..93c12304399 100644 --- a/apps/docs/lib/content-listings.utils.ts +++ b/apps/docs/lib/content-listings.utils.ts @@ -1,6 +1,7 @@ import { CONTENT_LISTINGS } from '~/data/content-listings' +import { isFeatureEnabled, type Feature } from 'common/enabled-features' -import type { ContentListingGroup } from './content-listings.schema' +import type { ContentListingGroup, ContentListingItem } from './content-listings.schema' /** Label for telemetry — prefers heading, falls back to id. */ export function getContentListingGroupLabel(group: ContentListingGroup): string { @@ -14,3 +15,8 @@ export function isExternalContentListingHref(href: string): boolean { export function getContentListingById(id: string): ContentListingGroup | undefined { return CONTENT_LISTINGS[id] } + +/** Omits items whose `feature` flag is disabled. Shared by UI and markdown export. */ +export function filterContentListingItems(items: ContentListingItem[]): ContentListingItem[] { + return items.filter((item) => !item.feature || isFeatureEnabled(item.feature as Feature)) +} diff --git a/apps/docs/lib/content-listings.zod.mjs b/apps/docs/lib/content-listings.zod.mjs index 3f7060ef8cb..a678cb6b578 100644 --- a/apps/docs/lib/content-listings.zod.mjs +++ b/apps/docs/lib/content-listings.zod.mjs @@ -29,6 +29,11 @@ export const contentListingItemSchema = z.object({ badge: z.string().min(1).optional(), /** Grid cards only. Defaults to inline (next to the title), matching existing usage. */ badgePosition: z.enum(['inline', 'below']).optional(), + /** + * When set, the item is omitted unless `isFeatureEnabled(feature)` is true. + * Use for SDK- or product-gated links (e.g. `sdk:dart`) inside a shared listing. + */ + feature: z.string().min(1).optional(), }) export const contentListingGroupTypeSchema = z.enum(['list', 'grid'])