mirror of
https://github.com/supabase/supabase.git
synced 2026-05-23 19:13:13 +08:00
267 lines
7.8 KiB
TypeScript
267 lines
7.8 KiB
TypeScript
import { fromMarkdown } from 'mdast-util-from-markdown'
|
|
import { toMarkdown } from 'mdast-util-to-markdown'
|
|
import { mdxFromMarkdown, mdxToMarkdown } from 'mdast-util-mdx'
|
|
import { mdxjs } from 'micromark-extension-mdxjs'
|
|
import type { Metadata, ResolvingMetadata } from 'next'
|
|
import { redirect } from 'next/navigation'
|
|
import { visit } from 'unist-util-visit'
|
|
|
|
import { REFERENCES, clientSdkIds, selfHostingServices } from '~/content/navigation.references'
|
|
import { getFlattenedSections } from '~/features/docs/Reference.generated.singleton'
|
|
import { generateOpenGraphImageMeta } from '~/features/seo/openGraph'
|
|
import { BASE_PATH } from '~/lib/constants'
|
|
|
|
export interface AbbrevApiReferenceSection {
|
|
id: string
|
|
type: string
|
|
title?: string
|
|
slug?: string
|
|
items?: Array<AbbrevApiReferenceSection>
|
|
excludes?: Array<string>
|
|
meta?: {
|
|
shared?: boolean
|
|
}
|
|
}
|
|
|
|
export function parseReferencePath(slug: Array<string>) {
|
|
const isClientSdkReference = clientSdkIds.includes(slug[0])
|
|
const isCliReference = slug[0] === 'cli'
|
|
const isApiReference = slug[0] === 'api'
|
|
const isSelfHostingReference = slug[0].startsWith('self-hosting-')
|
|
|
|
if (isClientSdkReference) {
|
|
let [sdkId, maybeVersion, maybeCrawlers, ...path] = slug
|
|
if (!/v\d+/.test(maybeVersion)) {
|
|
maybeVersion = null
|
|
maybeCrawlers = maybeVersion
|
|
path = [maybeCrawlers, ...path]
|
|
}
|
|
if (maybeCrawlers !== 'crawlers') {
|
|
maybeCrawlers = null
|
|
path = [maybeCrawlers, ...path]
|
|
}
|
|
|
|
return {
|
|
__type: 'clientSdk' as const,
|
|
sdkId,
|
|
maybeVersion,
|
|
maybeCrawlers,
|
|
path,
|
|
}
|
|
} else if (isCliReference) {
|
|
return {
|
|
__type: 'cli' as const,
|
|
path: slug.slice(1),
|
|
}
|
|
} else if (isApiReference) {
|
|
return {
|
|
__type: 'api' as const,
|
|
path: slug.slice(1),
|
|
}
|
|
} else if (isSelfHostingReference) {
|
|
return {
|
|
__type: 'self-hosting' as const,
|
|
service: slug[0].replace('self-hosting-', ''),
|
|
servicePath: slug[0],
|
|
path: slug.slice(1),
|
|
}
|
|
} else {
|
|
return {
|
|
__type: 'UNIMPLEMENTED' as const,
|
|
}
|
|
}
|
|
}
|
|
|
|
async function generateStaticParamsForSdkVersion(sdkId: string, version: string) {
|
|
const flattenedSections = await getFlattenedSections(sdkId, version)
|
|
|
|
return flattenedSections
|
|
.filter((section) => section.type !== 'category' && !!section.slug)
|
|
.map((section) => ({
|
|
slug: [
|
|
sdkId,
|
|
version === REFERENCES[sdkId].versions[0] ? null : version,
|
|
'crawlers',
|
|
section.slug,
|
|
].filter(Boolean),
|
|
}))
|
|
}
|
|
|
|
export async function generateReferenceStaticParams() {
|
|
const sdkPages = clientSdkIds
|
|
.flatMap((sdkId) =>
|
|
REFERENCES[sdkId].versions.map((version) => ({
|
|
sdkId,
|
|
version,
|
|
}))
|
|
)
|
|
.map(({ sdkId, version }) => ({
|
|
slug: [sdkId, version === REFERENCES[sdkId].versions[0] ? null : version].filter(Boolean),
|
|
}))
|
|
|
|
const cliPages = [
|
|
{
|
|
slug: ['cli'],
|
|
},
|
|
]
|
|
|
|
const apiPages = [
|
|
{
|
|
slug: ['api'],
|
|
},
|
|
]
|
|
|
|
const selfHostingPages = selfHostingServices.map((service) => ({
|
|
slug: [REFERENCES[service].libPath],
|
|
}))
|
|
|
|
return [...sdkPages, ...cliPages, ...apiPages, ...selfHostingPages]
|
|
}
|
|
|
|
export async function generateReferenceMetadata(
|
|
{ params: { slug } }: { params: { slug: Array<string> } },
|
|
resolvingParent: ResolvingMetadata
|
|
): Promise<Metadata> {
|
|
console.log('[ENTER] generateReferenceMetadata')
|
|
const { alternates: parentAlternates, openGraph: parentOg } = await resolvingParent
|
|
console.log('[INFO] generateReferenceMetadata: post await resolvingParent')
|
|
|
|
const parsedPath = parseReferencePath(slug)
|
|
const isClientSdkReference = parsedPath.__type === 'clientSdk'
|
|
const isCliReference = parsedPath.__type === 'cli'
|
|
const isApiReference = parsedPath.__type === 'api'
|
|
const isSelfHostingReference = parsedPath.__type === 'self-hosting'
|
|
if (isClientSdkReference) {
|
|
const { sdkId, maybeVersion } = parsedPath
|
|
const version = maybeVersion ?? REFERENCES[sdkId].versions[0]
|
|
|
|
const flattenedSections = await getFlattenedSections(sdkId, version)
|
|
|
|
const displayName = REFERENCES[sdkId].name
|
|
const sectionTitle =
|
|
slug.length > 0
|
|
? flattenedSections.find((section) => section.slug === slug[0])?.title
|
|
: undefined
|
|
const url = [BASE_PATH, 'reference', sdkId, maybeVersion, slug[0]].filter(Boolean).join('/')
|
|
|
|
const images = generateOpenGraphImageMeta({
|
|
type: 'API Reference',
|
|
title: `${displayName}${sectionTitle ? `: ${sectionTitle}` : ''}`,
|
|
})
|
|
|
|
return {
|
|
title: `${displayName} API Reference | Supabase Docs`,
|
|
description: `API reference for the ${displayName} Supabase SDK`,
|
|
...(slug.length > 0
|
|
? {
|
|
alternates: {
|
|
...parentAlternates,
|
|
canonical: url,
|
|
},
|
|
}
|
|
: {}),
|
|
openGraph: {
|
|
...parentOg,
|
|
url,
|
|
images,
|
|
},
|
|
}
|
|
} else if (isCliReference) {
|
|
console.log('[PRE-RETURN] generateReferenceMetadata: isCliReference')
|
|
return {
|
|
title: 'CLI Reference | Supabase Docs',
|
|
description: 'CLI reference for the Supabase CLI',
|
|
}
|
|
} else if (isApiReference) {
|
|
return {
|
|
title: 'Management API Reference | Supabase Docs',
|
|
description: 'Management API reference for the Supabase API',
|
|
}
|
|
} else if (isSelfHostingReference) {
|
|
return {
|
|
title: 'Self-Hosting | Supabase Docs',
|
|
}
|
|
} else {
|
|
return {}
|
|
}
|
|
}
|
|
|
|
export async function redirectNonexistentReferenceSection(
|
|
sdkId: string,
|
|
version: string,
|
|
path: Array<string>,
|
|
isLatestVersion: boolean
|
|
) {
|
|
const initialSelectedSection = path[0]
|
|
|
|
const validSlugs = await generateStaticParamsForSdkVersion(sdkId, version)
|
|
|
|
if (
|
|
initialSelectedSection &&
|
|
!validSlugs.some((params) => params.slug[0] === initialSelectedSection)
|
|
) {
|
|
redirect(`/reference/${sdkId}` + (!isLatestVersion ? '/' + version : ''))
|
|
}
|
|
}
|
|
|
|
export function normalizeMarkdown(markdownUnescaped: string): string {
|
|
/**
|
|
* Need to first escape the braces so that the MDX parser doesn't choke on
|
|
* them. Unlike the MDX parser, the regular Markdown parser handles braces
|
|
* gracefully, so we use it to find the positions of the code blocks, then
|
|
* escape all other braces before the final conversion with the MDX parser.
|
|
*/
|
|
const markdownTree = fromMarkdown(markdownUnescaped)
|
|
|
|
const codeBlocks = [] as Array<{
|
|
type: string
|
|
start: number
|
|
end: number
|
|
}>
|
|
visit(markdownTree, ['code', 'inlineCode'], (node) => {
|
|
codeBlocks.push({
|
|
type: node.type,
|
|
start: node.position.start.offset,
|
|
end: node.position.end.offset,
|
|
})
|
|
})
|
|
// Sort code blocks by start offset in descending order
|
|
codeBlocks.sort((a, b) => b.start - a.start)
|
|
|
|
let markdown = markdownUnescaped
|
|
let lastIndex = markdown.length
|
|
|
|
// Iterate through the sorted code blocks
|
|
for (const block of codeBlocks) {
|
|
// Escape braces in the text between the current code block and the last processed position
|
|
const textBetween = markdown.slice(block.end, lastIndex)
|
|
const escapedTextBetween = textBetween.replace(/(?<!\\)([{}])/g, '\\$1')
|
|
|
|
// Replace the original text with the escaped version
|
|
markdown = markdown.slice(0, block.end) + escapedTextBetween + markdown.slice(lastIndex)
|
|
|
|
// Update the last processed position
|
|
lastIndex = block.start
|
|
}
|
|
|
|
// Escape braces in the remaining text before the first code block
|
|
if (lastIndex > 0) {
|
|
const remainingText = markdown.slice(0, lastIndex)
|
|
const escapedRemainingText = remainingText.replace(/(?<!\\)([{}])/g, '\\$1')
|
|
markdown = escapedRemainingText + markdown.slice(lastIndex)
|
|
}
|
|
|
|
const mdxTree = fromMarkdown(markdown, {
|
|
extensions: [mdxjs()],
|
|
mdastExtensions: [mdxFromMarkdown()],
|
|
})
|
|
visit(mdxTree, 'text', (node) => {
|
|
node.value = node.value.replace(/\n/g, ' ')
|
|
})
|
|
const content = toMarkdown(mdxTree, {
|
|
extensions: [mdxToMarkdown()],
|
|
})
|
|
|
|
return content
|
|
}
|