Files
supabase/apps/docs/app/guides/database/extensions/wrappers/[[...slug]]/page.tsx
Chris Chinchilla e01699cce5 docs: Further toggles for Nimbus project (#39010)
chore: feature flag footer and fdw section

Co-authored-by: Charis Lam <26616127+charislam@users.noreply.github.com>
2025-09-25 14:26:46 +00:00

443 lines
11 KiB
TypeScript

import matter from 'gray-matter'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import { readFile } from 'node:fs/promises'
import { join, relative } from 'node:path'
import rehypeSlug from 'rehype-slug'
import emoji from 'remark-emoji'
// End of third-party imports
import { IS_PROD, isFeatureEnabled } from 'common'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns'
import {
genGuideMeta,
genGuidesStaticParams,
removeRedundantH1,
} from '~/features/docs/GuidesMdx.utils'
import { newEditLink } from '~/features/helpers.edit-link'
import { REVALIDATION_TAGS } from '~/features/helpers.fetch'
import { Guide, GuideArticle, GuideFooter, GuideHeader, GuideMdxContent } from '~/features/ui/guide'
import { GUIDES_DIRECTORY, isValidGuideFrontmatter } from '~/lib/docs'
import { linkTransform, type UrlTransformFunction } from '~/lib/mdx/plugins/rehypeLinkTransform'
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
import { octokit } from '~/lib/octokit'
import type { SerializeOptions } from '~/types/next-mdx-remote-serialize'
// We fetch these docs at build time from an external repo
const org = 'supabase'
const repo = 'wrappers'
const docsDir = 'docs/catalog'
const externalSite = 'https://supabase.github.io/wrappers'
type TagQueryResponse = {
repository: {
refs: {
nodes:
| {
name: string
}[]
| null
pageInfo: {
hasNextPage: boolean
endCursor: string | null
}
}
}
}
const tagQuery = `
query TagQuery($owner: String!, $name: String!, $after: String) {
repository(owner: $owner, name: $name) {
refs(
refPrefix: "refs/tags/",
orderBy: {
field: TAG_COMMIT_DATE,
direction: DESC
},
first: 5,
after: $after
) {
nodes {
name
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`
async function getLatestRelease(after: string | null = null) {
try {
const {
repository: {
refs: {
nodes,
pageInfo: { hasNextPage, endCursor },
},
},
} = await octokit().graphql<TagQueryResponse>(tagQuery, {
owner: org,
name: repo,
after,
request: {
fetch: (url: RequestInfo | URL, options?: RequestInit) =>
fetch(url, {
...options,
next: { tags: [REVALIDATION_TAGS.WRAPPERS] },
}),
},
})
return (
nodes?.find((node) => node?.name?.match(/^docs_v\d+\.\d+\.\d+/))?.name ??
(hasNextPage && endCursor ? await getLatestRelease(endCursor) : null)
)
} catch (error) {
console.error(`Error fetching release tags for wrappers federated pages: ${error}`)
return null
}
}
// Each external docs page is mapped to a local page
const pageMap = [
{
slug: 'airtable',
meta: {
title: 'Airtable',
dashboardIntegrationPath: 'airtable_wrapper',
},
remoteFile: 'airtable.md',
},
{
slug: 'auth0',
meta: {
title: 'Auth0',
dashboardIntegrationPath: 'auth0_wrapper',
},
remoteFile: 'auth0.md',
},
{
slug: 'bigquery',
meta: {
title: 'BigQuery',
dashboardIntegrationPath: 'bigquery_wrapper',
},
remoteFile: 'bigquery.md',
},
{
slug: 'clerk',
meta: {
title: 'Clerk',
dashboardIntegrationPath: 'clerk_wrapper',
},
remoteFile: 'clerk.md',
},
{
slug: 'clickhouse',
meta: {
title: 'ClickHouse',
dashboardIntegrationPath: 'clickhouse_wrapper',
},
remoteFile: 'clickhouse.md',
},
{
slug: 'cognito',
meta: {
title: 'AWS Cognito',
dashboardIntegrationPath: 'cognito_wrapper',
},
remoteFile: 'cognito.md',
},
{
slug: 'duckdb',
meta: {
title: 'DuckDB',
},
remoteFile: 'duckdb.md',
},
{
slug: 'firebase',
meta: {
title: 'Firebase',
dashboardIntegrationPath: 'firebase_wrapper',
},
remoteFile: 'firebase.md',
},
{
slug: 'iceberg',
meta: {
title: 'Iceberg',
dashboardIntegrationPath: 'iceberg_wrapper',
},
remoteFile: 'iceberg.md',
},
{
slug: 'logflare',
meta: {
title: 'Logflare',
dashboardIntegrationPath: 'logflare_wrapper',
},
remoteFile: 'logflare.md',
},
{
slug: 'mssql',
meta: {
title: 'MSSQL',
dashboardIntegrationPath: 'mssql_wrapper',
},
remoteFile: 'mssql.md',
},
{
slug: 'notion',
meta: {
title: 'Notion',
dashboardIntegrationPath: 'notion_wrapper',
},
remoteFile: 'notion.md',
},
{
slug: 'paddle',
meta: {
title: 'Paddle',
dashboardIntegrationPath: 'paddle_wrapper',
},
remoteFile: 'paddle.md',
},
{
slug: 'redis',
meta: {
title: 'Redis',
dashboardIntegrationPath: 'redis_wrapper',
},
remoteFile: 'redis.md',
},
{
slug: 's3',
meta: {
title: 'AWS S3',
dashboardIntegrationPath: 's3_wrapper',
},
remoteFile: 's3.md',
},
{
slug: 'snowflake',
meta: {
title: 'Snowflake',
dashboardIntegrationPath: 'snowflake_wrapper',
},
remoteFile: 'snowflake.md',
},
{
slug: 'stripe',
meta: {
title: 'Stripe',
dashboardIntegrationPath: 'stripe_wrapper',
},
remoteFile: 'stripe.md',
},
]
interface Params {
slug?: string[]
}
const WrappersDocs = async (props: { params: Promise<Params> }) => {
if (!isFeatureEnabled('docs:fdw')) {
notFound()
}
const params = await props.params
const { isExternal, meta, assetsBaseUrl, ...data } = await getContent(params)
// Create a combined URL transformer that handles both regular URLs and asset URLs
const combinedUrlTransformer: UrlTransformFunction = (url, node) => {
// First try assets URL transformation (starts with ../assets/)
const transformedUrl = assetUrlTransform(url, assetsBaseUrl)
// If URL wasn't changed proceed with regular URL transformation
if (transformedUrl === url) {
return urlTransform(url, node)
}
return transformedUrl
}
const options = isExternal
? ({
mdxOptions: {
remarkPlugins: [
remarkMkDocsAdmonition,
emoji,
remarkPyMdownTabs,
[removeTitle, meta.title],
],
rehypePlugins: [[linkTransform, combinedUrlTransformer], rehypeSlug],
},
} as SerializeOptions)
: undefined
const dashboardIntegrationURL = getDashboardIntegrationURL(meta.dashboardIntegrationPath)
return (
<Guide meta={meta}>
<GuideArticle>
<GuideHeader />
{dashboardIntegrationURL && (
<Admonition type="tip" className="mb-4">
<p>You can enable the {meta.title} wrapper right from the Supabase dashboard.</p>
<Button asChild>
<Link href={dashboardIntegrationURL} className="no-underline">
Open wrapper in dashboard
</Link>
</Button>
</Admonition>
)}
<GuideMdxContent content={data.content} mdxOptions={options} />
<GuideFooter editLink={data.editLink} />
</GuideArticle>
</Guide>
)
}
/**
* Fetch markdown from external repo
*/
const getContent = async (params: Params) => {
const federatedPage = pageMap.find(
({ slug }) => params && slug && params.slug && slug === params.slug.at(0)
)
let isExternal: boolean
let meta: any
let content: string
let editLink: string
let assetsBaseUrl: string = ''
if (!federatedPage) {
isExternal = false
editLink = `supabase/supabase/apps/docs/content/guides/database/extensions/wrappers${params.slug?.length ? `/${params.slug.join('/')}` : ''}.mdx`
const rawContent = await readFile(
join(
GUIDES_DIRECTORY,
'database',
'extensions',
`wrappers${params.slug?.length ? `/${params.slug.join('/')}` : ''}.mdx`
),
'utf-8'
)
;({ data: meta, content } = matter(rawContent))
if (!isValidGuideFrontmatter(meta)) {
throw Error(`Expected valid frontmatter, got ${JSON.stringify(meta, null, 2)}`)
}
} else {
isExternal = true
let remoteFile: string
;({ remoteFile, meta } = federatedPage)
const tag = await getLatestRelease()
if (!tag) {
throw new Error('No latest release found for federated wrappers pages')
}
const repoPath = `${org}/${repo}/${tag}/${docsDir}/${remoteFile}`
editLink = `${org}/${repo}/blob/${tag}/${docsDir}/${remoteFile}`
const response = await fetch(`https://raw.githubusercontent.com/${repoPath}`, {
cache: 'force-cache',
next: { tags: [REVALIDATION_TAGS.WRAPPERS] },
})
const rawContent = await response.text()
assetsBaseUrl = `https://raw.githubusercontent.com/${org}/${repo}/${tag}/docs/assets/`
const { content: contentWithoutFrontmatter } = matter(rawContent)
content = removeRedundantH1(contentWithoutFrontmatter)
}
return {
pathname:
`/guides/database/extensions/wrappers${params.slug?.length ? `/${params.slug.join('/')}` : ''}` satisfies `/${string}`,
isExternal,
editLink: newEditLink(editLink),
meta,
content,
assetsBaseUrl,
}
}
const getDashboardIntegrationURL = (wrapperPath?: string) => {
return wrapperPath
? `https://supabase.com/dashboard/project/_/integrations/${wrapperPath}/overview`
: null
}
const assetUrlTransform = (url: string, baseUrl: string): string => {
const assetPattern = /(\.\.\/)+assets\//
if (assetPattern.test(url)) {
return url.replace(assetPattern, baseUrl)
}
return url
}
const urlTransform: UrlTransformFunction = (url) => {
try {
const externalSiteUrl = new URL(externalSite)
const placeholderHostname = 'placeholder'
const { hostname, pathname, hash } = new URL(url, `http://${placeholderHostname}`)
// Don't modify a url with a FQDN or a url that's only a hash
if (hostname !== placeholderHostname || pathname === '/') {
return url
}
const relativePage = (
pathname.endsWith('.md')
? pathname.replace(/\.md$/, '')
: relative(externalSiteUrl.pathname, pathname)
).replace(/^\//, '')
const page = pageMap.find(({ remoteFile }) => `${relativePage}.md` === remoteFile)
// If we have a mapping for this page, use the mapped path
if (page) {
return page.slug + hash
}
// If we don't have this page in our docs, link to original docs
return `${externalSite}/${relativePage}${hash}`
} catch (err) {
console.error('Error transforming markdown URL', err)
return url
}
}
const generateStaticParams = async () => {
if (IS_PROD) {
return []
}
const mdxPaths = await genGuidesStaticParams('database/extensions/wrappers')()
const federatedPaths = pageMap.map(({ slug }) => ({
slug: [slug],
}))
return [...mdxPaths, ...federatedPaths]
}
const generateMetadata = genGuideMeta(getContent)
export default WrappersDocs
export { generateMetadata, generateStaticParams }