mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
# Second try of making a new better process for SDK automation Instead of building a new pipeline. We will take the lessons learned form round 1, plus the good design and improvement on DX quality for drop-in file as a single step required from SDK team and produce almost identical set of files as used right now to render using the current pipeline. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * New reference-content pipeline producing per-library reference artifacts and integrating into prebuilds, search ingestion, and rendering (type-aware examples). * **Documentation** * Added comprehensive JavaScript SDK v2 reference content and partials (Auth MFA, passkeys, admin, TypeScript support, filters, modifiers, Installing, Initializing, Buckets, etc.). * **Tests & CI** * Added regression snapshot test and updated workflows to refresh reference snapshots and ensure spec downloads. * **Chores** * Updated ignore rules, build scripts, Makefile targets, and package lifecycle hooks. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Katerina Skroumpelou <mandarini@users.noreply.github.com> Co-authored-by: Katerina Skroumpelou <sk.katherine@gmail.com>
274 lines
8.1 KiB
TypeScript
274 lines
8.1 KiB
TypeScript
import { clientSdkIds, REFERENCES, 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'
|
|
import { getCustomContent } from '~/lib/custom-content/getCustomContent'
|
|
import { fromMarkdown } from 'mdast-util-from-markdown'
|
|
import { mdxFromMarkdown, mdxToMarkdown } from 'mdast-util-mdx'
|
|
import { toMarkdown } from 'mdast-util-to-markdown'
|
|
import { mdxjs } from 'micromark-extension-mdxjs'
|
|
import type { Metadata, ResolvingMetadata } from 'next'
|
|
import { redirect } from 'next/navigation'
|
|
import { visit } from 'unist-util-visit'
|
|
|
|
const { metadataTitle } = getCustomContent(['metadata:title'])
|
|
|
|
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: string
|
|
let maybeVersion: string | null
|
|
let maybeCrawlers: string | null
|
|
let path: string[]
|
|
;[sdkId, maybeVersion, maybeCrawlers, ...path] = slug
|
|
if (!/v\d+/.test(maybeVersion)) {
|
|
maybeVersion = null
|
|
path = [maybeCrawlers, ...path]
|
|
maybeCrawlers = maybeVersion
|
|
}
|
|
if (maybeCrawlers !== 'crawlers') {
|
|
if (typeof maybeCrawlers === 'string') {
|
|
path = [maybeCrawlers, ...path]
|
|
}
|
|
maybeCrawlers = null
|
|
}
|
|
|
|
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(
|
|
props: { params: Promise<{ slug: Array<string> }> },
|
|
resolvingParent: ResolvingMetadata
|
|
): Promise<Metadata> {
|
|
const { slug } = await props.params
|
|
const { alternates: parentAlternates, openGraph: parentOg } = 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, path } = 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, path[0]].filter(Boolean).join('/')
|
|
|
|
const images = generateOpenGraphImageMeta({
|
|
type: 'API Reference',
|
|
title: `${displayName}${sectionTitle ? `: ${sectionTitle}` : ''}`,
|
|
})
|
|
|
|
return {
|
|
title: `${displayName} API Reference | ${metadataTitle || 'Supabase'}`,
|
|
description: `API reference for the ${displayName} Supabase SDK`,
|
|
...(slug.length > 0
|
|
? {
|
|
alternates: {
|
|
canonical: url,
|
|
},
|
|
}
|
|
: {}),
|
|
openGraph: {
|
|
...parentOg,
|
|
url,
|
|
images,
|
|
},
|
|
}
|
|
} else if (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 || 0,
|
|
end: node.position?.end?.offset || 0,
|
|
})
|
|
})
|
|
// 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
|
|
}
|
|
|
|
export { SUPPORTS_NEW_REFERENCE_PROCESS } from '~/features/docs/Reference.constants'
|