refactor(docs): convert self-hosting to app router (#29268)

* chore(docs,refs): prebuild self-hosted ref data

Pregenerate self-hosted ref data files, in the style of the client lib,
Management API, and CLI references. Preparatory step for migrating the
self-hosted references to App Router.

* refactor(docs): convert self-hosting to app router

Convert self-hosting reference pages to App Router.

* fix(docs,sitemap)
This commit is contained in:
Charis authored and GitHub committed 2024-09-13 09:45:55 -04:00
1 parent 66e363e29d
commit 11e19ff29f
24 files changed
+456 -297

No files matched your search

+7 -2
View File
@@ -4,6 +4,7 @@ import { REFERENCES } from '~/content/navigation.references'
import { ApiReferencePage } from '~/features/docs/Reference.apiPage'
import { CliReferencePage } from '~/features/docs/Reference.cliPage'
import { ClientSdkReferencePage } from '~/features/docs/Reference.sdkPage'
import { SelfHostingReferencePage } from '~/features/docs/Reference.selfHostingPage'
import {
generateReferenceMetadata,
generateReferenceStaticParams,
@@ -17,7 +18,7 @@ export default async function ReferencePage({
}: {
params: { slug: Array<string> }
}) {
if (!Object.keys(REFERENCES).includes(slug[0])) {
if (!Object.keys(REFERENCES).includes(slug[0].replaceAll('-', '_'))) {
redirect(notFoundLink(slug.join('/')))
}
@@ -25,6 +26,7 @@ export default async function ReferencePage({
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
@@ -40,8 +42,11 @@ export default async function ReferencePage({
return <CliReferencePage />
} else if (isApiReference) {
return <ApiReferencePage />
} else if (isSelfHostingReference) {
return (
<SelfHostingReferencePage service={parsedPath.service} servicePath={parsedPath.servicePath} />
)
} else {
// Unimplemented -- eventually API
redirect(notFoundLink(slug.join('/')))
}
}
+21 -9
View File
@@ -5,16 +5,28 @@ import { Tabs, TabPanel } from '~/features/ui/Tabs'
type IParamProps = any
const ApiSchema = ({ schema, id }: IParamProps) => {
const example = sample(schema, { skipReadOnly: true, quiet: true })
let example: string
try {
example = sample(schema, { skipReadOnly: true, quiet: true })
} catch {
// ignore
}
return (
<Tabs scrollable size="small" type="underlined" defaultActiveId="example">
<TabPanel key={`${id ?? ''}-example`} id="example" label="example">
<div className="mt-8">
<CodeBlock language="bash" className="relative">
{JSON.stringify(example, null, 2)}
</CodeBlock>
</div>
</TabPanel>
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId={example ? 'example' : 'schema'}
>
{example && (
<TabPanel key={`${id ?? ''}-example`} id="example" label="example">
<div className="mt-8">
<CodeBlock language="bash" className="relative">
{JSON.stringify(example, null, 2)}
</CodeBlock>
</div>
</TabPanel>
)}
<TabPanel key={`${id ?? ''}-schema`} id="schema" label="schema">
<div className="mt-8">
<CodeBlock language="bash" className="relative">
@@ -118,8 +118,47 @@ export const REFERENCES = {
versions: [],
icon: 'reference-api',
},
self_hosting_analytics: {
type: 'self-hosting',
name: 'Self-Hosting Analytics',
libPath: 'self-hosting-analytics',
versions: [],
icon: 'reference-analytics',
},
self_hosting_auth: {
type: 'self-hosting',
name: 'Self-Hosting Auth',
libPath: 'self-hosting-auth',
versions: [],
icon: 'self-hosting',
},
self_hosting_functions: {
type: 'self-hosting',
name: 'Self-Hosting Functions',
libPath: 'self-hosting-functions',
versions: [],
icon: 'reference-functions',
},
self_hosting_realtime: {
type: 'self-hosting',
name: 'Self-Hosting Realtime',
libPath: 'self-hosting-realtime',
versions: [],
icon: 'self-hosting',
},
self_hosting_storage: {
type: 'self-hosting',
name: 'Self-Hosting Storage',
libPath: 'self-hosting-storage',
versions: [],
icon: 'self-hosting',
},
} as const
export const clientSdkIds = Object.keys(REFERENCES).filter(
(reference) => REFERENCES[reference].type === 'sdk'
)
export const selfHostingServices = Object.keys(REFERENCES).filter(
(reference) => REFERENCES[reference].type === 'self-hosting'
)
@@ -12,7 +12,7 @@ export interface IApiEndPoint {
schema: ISchema
}>
requestBody?: {
required: boolean
required?: boolean
content: IApiRequestBody
}
responses: {
@@ -99,13 +99,13 @@ interface ISchemaOneOf extends ISchemaBase {
interface IApiRequestBody extends IApiJsonDTO, IApiFormUrlEncodedDTO {}
interface IApiJsonDTO {
'application/json': {
'application/json'?: {
schema: ISchema
}
}
interface IApiFormUrlEncodedDTO {
'application/x-www-form-urlencoded': {
'application/x-www-form-urlencoded'?: {
schema: ISchema
}
}
@@ -2,6 +2,7 @@ import { keyBy, isPlainObject } from 'lodash'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import slugify from 'slugify'
import { parse } from 'yaml'
import { REFERENCES, clientSdkIds } from '~/content/navigation.references'
@@ -9,9 +10,17 @@ import { parseTypeSpec } from '~/features/docs/Reference.typeSpec'
import type { AbbrevApiReferenceSection } from '~/features/docs/Reference.utils'
import { deepFilterRec } from '~/features/helpers.fn'
import type { Json } from '~/features/helpers.types'
import authSpec from '~/spec/auth_v1_openapi.json' assert { type: 'json' }
import apiCommonSections from '~/spec/common-api-sections.json' assert { type: 'json' }
import cliCommonSections from '~/spec/common-cli-sections.json' assert { type: 'json' }
import commonClientLibSections from '~/spec/common-client-libs-sections.json' assert { type: 'json' }
import selfHostingAnalyticsCommonSections from '~/spec/common-self-hosting-analytics-sections.json' assert { type: 'json' }
import selfHostingAuthCommonSections from '~/spec/common-self-hosting-auth-sections.json'
import selfHostingFunctionsCommonSections from '~/spec/common-self-hosting-functions-sections.json' assert { type: 'json' }
import selfHostingRealtimeCommonSections from '~/spec/common-self-hosting-realtime-sections.json' assert { type: 'json' }
import selfHostingStorageCommonSections from '~/spec/common-self-hosting-storage-sections.json' assert { type: 'json' }
import storageSpec from '~/spec/storage_v0_openapi.json' assert { type: 'json' }
import analyticsSpec from '~/spec/transforms/analytics_v0_openapi_deparsed.json' assert { type: 'json' }
import openApiSpec from '~/spec/transforms/api_v1_openapi_deparsed.json' assert { type: 'json' }
import { IApiEndPoint } from './Reference.api.utils'
@@ -19,6 +28,32 @@ const DOCS_DIRECTORY = join(dirname(fileURLToPath(import.meta.url)), '../..')
const SPEC_DIRECTORY = join(DOCS_DIRECTORY, 'spec')
const GENERATED_DIRECTORY = join(dirname(fileURLToPath(import.meta.url)), 'generated')
const selfHostingSpecs = [
{
id: 'self-hosting-analytics',
sections: selfHostingAnalyticsCommonSections,
spec: analyticsSpec,
},
{
id: 'self-hosting-auth',
sections: selfHostingAuthCommonSections,
spec: authSpec,
},
{
id: 'self-hosting-functions',
sections: selfHostingFunctionsCommonSections,
},
{
id: 'self-hosting-realtime',
sections: selfHostingRealtimeCommonSections,
},
{
id: 'self-hosting-storage',
sections: selfHostingStorageCommonSections,
spec: storageSpec,
},
]
async function getSpec(specFile: string, { ext = 'yml' }: { ext?: string } = {}) {
const specFullPath = join(SPEC_DIRECTORY, `${specFile}.${ext}`)
const rawSpec = await readFile(specFullPath, 'utf-8')
@@ -36,14 +71,17 @@ async function parseFnsList(rawSpec: Json): Promise<Array<{ id: unknown }>> {
return []
}
function mapEndpointsById(spec: typeof openApiSpec): Map<string, IApiEndPoint> {
function mapEndpointsById(
spec: any,
getId = (details: any) => details.operationId
): Map<string, IApiEndPoint> {
const endpoints = spec.paths
const endpointsById = new Map<string, IApiEndPoint>()
Object.entries(endpoints).forEach(([path, methods]) => {
Object.entries(methods).forEach(([method, details]) => {
endpointsById.set(details.operationId, {
id: details.operationId,
endpointsById.set(getId(details), {
id: getId(details),
path,
method,
...details,
@@ -89,6 +127,16 @@ function genApiSectionTree(endpointsById: Map<string, IApiEndPoint>) {
return validSections
}
function genSelfHostedSectionTree(
spec: Array<AbbrevApiReferenceSection>,
endpointsById: Map<string, IApiEndPoint>
) {
const validSections = deepFilterRec(spec, 'items', (section) =>
section.type === 'self-hosted-operation' ? endpointsById.has(section.id) : true
)
return validSections
}
export function flattenCommonClientLibSections(tree: Array<AbbrevApiReferenceSection>) {
return tree.reduce((acc, elem) => {
if ('items' in elem) {
@@ -234,6 +282,61 @@ async function writeApiReferenceSections() {
])
}
async function writeSelfHostingReferenceSections() {
let id = 0
return Promise.all(
selfHostingSpecs.flatMap((service) => {
let tasks: Promise<any>[] = []
let endpointsById: Map<string, IApiEndPoint>
if (service.spec) {
endpointsById = mapEndpointsById(service.spec, (details) =>
slugify(details.summary || `dummy-id-${String(id++)}`, {
lower: true,
remove: /[^\w\s-]/g,
})
)
tasks.push(
writeFile(
join(GENERATED_DIRECTORY, `${service.id}.latest.endpointsById.json`),
JSON.stringify(Array.from(endpointsById.entries()))
)
)
}
const selfHostedSectionTree = genSelfHostedSectionTree(service.sections, endpointsById)
tasks.push(
writeFile(
join(GENERATED_DIRECTORY, `${service.id}.latest.sections.json`),
JSON.stringify(selfHostedSectionTree)
)
)
const flattenedSelfHostedSections = flattenCommonClientLibSections(selfHostedSectionTree)
tasks.push(
writeFile(
join(GENERATED_DIRECTORY, `${service.id}.latest.flat.json`),
JSON.stringify(flattenedSelfHostedSections)
)
)
const selfHostedSectionsBySlug = keyBy(
flattenedSelfHostedSections.filter(({ slug }) => !!slug),
(section) => section.slug
)
tasks.push(
writeFile(
join(GENERATED_DIRECTORY, `${service.id}.latest.bySlug.json`),
JSON.stringify(selfHostedSectionsBySlug)
)
)
return tasks
})
)
}
async function run() {
try {
await mkdir(GENERATED_DIRECTORY, { recursive: true })
@@ -243,6 +346,7 @@ async function run() {
writeSdkReferenceSections(),
writeCliReferenceSections(),
writeApiReferenceSections(),
writeSelfHostingReferenceSections(),
])
} catch (err) {
console.error(err)
@@ -62,6 +62,20 @@ export async function getApiEndpointById(id: string) {
return apiEndpointsById.get(id)
}
let selfHostedEndpointsById = new Map<string, Map<string, IApiEndPoint>>()
export async function getSelfHostedApiEndpointById(servicePath: string, id: string) {
if (!selfHostedEndpointsById.has(servicePath)) {
const rawJson = await readFile(
join(process.cwd(), 'features/docs', `./generated/${servicePath}.latest.endpointsById.json`),
'utf-8'
)
selfHostedEndpointsById.set(servicePath, new Map(JSON.parse(rawJson)))
}
return selfHostedEndpointsById.get(servicePath)?.get(id)
}
const functionsList = new Map<string, Array<{ id: unknown }>>()
export async function getFunctionsList(sdkId: string, version: string) {
+53 -22
View File
@@ -9,6 +9,7 @@ import {
CodeBlock,
} from 'ui'
import ApiSchema from '~/components/ApiSchema'
import { REFERENCES } from '~/content/navigation.references'
import { MDXRemoteRefs, getRefMarkdown } from '~/features/docs/Reference.mdx'
import { MDXProviderReference } from '~/features/docs/Reference.mdx.client'
@@ -18,12 +19,13 @@ import {
getCliSpec,
getFlattenedSections,
getFunctionsList,
getSelfHostedApiEndpointById,
getTypeSpec,
} from '~/features/docs/Reference.generated.singleton'
import { type IApiEndPoint } from './Reference.api.utils'
import {
ApiOperationRequestBodyDetails,
ApiSchemaParamDetails,
ApiSchemaParamSubdetails,
CollapsibleDetails,
FnParameterDetails,
RefSubLayout,
@@ -34,9 +36,7 @@ import {
import type { AbbrevApiReferenceSection } from '~/features/docs/Reference.utils'
import { normalizeMarkdown } from '~/features/docs/Reference.utils'
import { RefInternalLink } from './Reference.navigation.client'
import { getTypeDisplayFromSchema } from './Reference.api.utils'
import { ApiOperationBodySchemeSelector } from './Reference.ui.client'
import ApiSchema from '~/components/ApiSchema'
type RefSectionsProps = {
libraryId: string
@@ -82,8 +82,8 @@ type SectionSwitchProps = {
}
export function SectionSwitch({ libraryId, version, section }: SectionSwitchProps) {
const libPath = REFERENCES[libraryId].libPath
const allAvailableVersions = REFERENCES[libraryId].versions
const libPath = REFERENCES[libraryId.replaceAll('-', '_')].libPath
const allAvailableVersions = REFERENCES[libraryId.replaceAll('-', '_')].versions
const isLatestVersion = allAvailableVersions.length === 0 || version === allAvailableVersions[0]
const sectionLink = `/docs/reference/${libPath}/${isLatestVersion ? '' : `${version}/`}${section.slug}`
@@ -113,6 +113,8 @@ export function SectionSwitch({ libraryId, version, section }: SectionSwitchProp
return <CliCommandSection link={sectionLink} section={section} />
case 'operation':
return <ApiEndpointSection link={sectionLink} section={section} />
case 'self-hosted-operation':
return <ApiEndpointSection servicePath={libraryId} link={sectionLink} section={section} />
default:
console.error(`Unhandled type in reference sections: ${section.type}`)
return null
@@ -261,14 +263,35 @@ async function CliCommandSection({ link, section }: CliCommandSectionProps) {
interface ApiEndpointSectionProps {
link: string
section: AbbrevApiReferenceSection
servicePath?: string
}
async function ApiEndpointSection({ link, section }: ApiEndpointSectionProps) {
const endpointDetails = await getApiEndpointById(section.id)
async function ApiEndpointSection({ link, section, servicePath }: ApiEndpointSectionProps) {
const endpointDetails = servicePath
? await getSelfHostedApiEndpointById(servicePath, section.id)
: await getApiEndpointById(section.id)
if (!endpointDetails) return null
const pathParameters = endpointDetails.parameters.filter((param) => param.in === 'path')
const queryParameters = endpointDetails.parameters.filter((param) => param.in === 'query')
const pathParameters = (endpointDetails.parameters ?? []).filter((param) => param.in === 'path')
const queryParameters = (endpointDetails.parameters ?? []).filter((param) => param.in === 'query')
const bodyParameters =
endpointDetails.requestBody ??
(endpointDetails.parameters ?? [])
.filter((param) => param.in === 'body')
.map(
(bodyParam) =>
({
content: {
'application/json': {
schema: bodyParam.schema,
},
},
}) satisfies IApiEndPoint['requestBody']
)[0]
const first2xxCode = Object.keys(endpointDetails.responses ?? {})
.filter((code) => code.startsWith('2'))
.sort()[0]
return (
<RefSubLayout.Section columns="double" link={link} {...section}>
@@ -305,27 +328,35 @@ async function ApiEndpointSection({ link, section }: ApiEndpointSectionProps) {
</ul>
</section>
)}
{endpointDetails.requestBody && (
{bodyParameters && (
<section>
<ApiOperationBodySchemeSelector
requestBody={endpointDetails.requestBody}
className="mb-3"
/>
<ApiOperationRequestBodyDetails requestBody={endpointDetails.requestBody} />
<ApiOperationBodySchemeSelector requestBody={bodyParameters} className="mb-3" />
<ApiOperationRequestBodyDetails requestBody={bodyParameters} />
</section>
)}
{endpointDetails.responses && (
<section>
<h3 className="mb-3 text-base text-foreground">Response codes</h3>
<ul>
{Object.keys(endpointDetails.responses).map((code) => (
<li key={code} className="list-['-'] ml-2 pl-2">
<span className="font-mono text-sm font-medium text-foreground">{code}</span>
</li>
))}
</ul>
</section>
)}
</div>
{Object.keys(endpointDetails.responses).filter((code) => code.startsWith('2')).length > 0 && (
{endpointDetails.responses && first2xxCode && (
<div className="overflow-auto">
<h3 className="mb-3 text-base text-foreground">Response</h3>
<h3 className="mb-3 text-base text-foreground">{`Response (${first2xxCode})`}</h3>
<ApiSchema
id={`${section.id}-2xx-response`}
schema={
endpointDetails.responses[
Object.keys(endpointDetails.responses)
.filter((code) => code.startsWith('2'))
.sort()[0]
].content?.['application/json']?.schema ?? {}
endpointDetails.responses[first2xxCode].content?.['application/json']?.schema ??
// @ts-ignore - schema is here in older versions
endpointDetails.responses[first2xxCode].schema ??
{}
}
/>
</div>
@@ -0,0 +1,79 @@
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import {
reference_self_hosting_analytics,
reference_self_hosting_auth,
reference_self_hosting_functions,
reference_self_hosting_realtime,
reference_self_hosting_storage,
} from '~/components/Navigation/NavigationMenu/NavigationMenu.constants'
import { REFERENCES } from '~/content/navigation.references'
import { ClientLibIntroduction } from '~/features/docs/Reference.introduction'
import { ReferenceNavigation } from '~/features/docs/Reference.navigation'
import { ReferenceContentScrollHandler } from '~/features/docs/Reference.navigation.client'
import { RefSections } from '~/features/docs/Reference.sections'
import { LayoutMainContent } from '~/layouts/DefaultLayout'
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
export async function SelfHostingReferencePage({
service,
servicePath,
}: {
service: string
servicePath: string
}) {
const menuId =
service === 'analytics'
? MenuId.SelfHostingAnalytics
: service === 'auth'
? MenuId.SelfHostingAuth
: service === 'functions'
? MenuId.SelfHostingFunctions
: service === 'realtime'
? MenuId.SelfHostingRealtime
: service === 'storage'
? MenuId.SelfHostingStorage
: MenuId.SelfHosting
const menuData =
service === 'analytics'
? reference_self_hosting_analytics
: service === 'auth'
? reference_self_hosting_auth
: service === 'functions'
? reference_self_hosting_functions
: service === 'realtime'
? reference_self_hosting_realtime
: reference_self_hosting_storage
const name = REFERENCES[servicePath.replaceAll('-', '_')].name
return (
<ReferenceContentScrollHandler libPath={servicePath} version="latest" isLatestVersion={true}>
<SidebarSkeleton
menuId={menuId}
NavigationMenu={
<ReferenceNavigation
libraryId={servicePath}
name={name}
menuData={menuData}
libPath={servicePath}
version="latest"
isLatestVersion={true}
/>
}
>
<LayoutMainContent>
<article className="@container/article">
<ClientLibIntroduction
libPath={servicePath}
version="latest"
isLatestVersion={true}
className="max-w-[unset]"
/>
<RefSections libraryId={servicePath} version="latest" />
</article>
</LayoutMainContent>
</SidebarSkeleton>
</ReferenceContentScrollHandler>
)
}
+25 -13
View File
@@ -344,15 +344,17 @@ export function ApiSchemaParamDetails({ param }: { param: IApiEndPoint['paramete
{param.name}
</span>
<RequiredBadge isOptional={!param.required} />
{param.schema.deprecated && <span className="text-xs text-warning-600">Deprecated</span>}
<span className="text-xs text-foreground-muted">
{getTypeDisplayFromSchema(param.schema).displayName}
</span>
{param.schema?.deprecated && <span className="text-xs text-warning-600">Deprecated</span>}
{param.schema && (
<span className="text-xs text-foreground-muted">
{getTypeDisplayFromSchema(param.schema)?.displayName ?? ''}
</span>
)}
</div>
{param.description && (
<ReactMarkdown className="prose break-words text-sm">{param.description}</ReactMarkdown>
)}
<ApiSchemaParamSubdetails schema={param.schema} />
{param.schema && <ApiSchemaParamSubdetails schema={param.schema} />}
</li>
)
}
@@ -481,14 +483,20 @@ export function ApiSchemaParamSubdetails({
const subContent =
'enum' in schema
? schema.enum
: 'type' in schema && schema.type === 'string'
? ['minLength', 'maxLength', 'pattern']
.filter((key) => key in schema)
.map((key) => ({
constraint: key,
value: schema[key],
}))
: []
: 'anyOf' in schema
? schema.anyOf
: 'oneOf' in schema
? schema.oneOf
: 'allOf' in schema
? schema.allOf
: 'type' in schema && schema.type === 'string'
? ['minLength', 'maxLength', 'pattern']
.filter((key) => key in schema)
.map((key) => ({
constraint: key,
value: schema[key],
}))
: []
return (
<Collapsible_Shadcn_>
@@ -552,6 +560,10 @@ export function ApiSchemaParamSubdetails({
</span>
{detail.value}
</span>
) : 'anyOf' in schema || 'allOf' in schema || 'oneOf' in schema ? (
<ApiSchemaParamDetails
param={{ name: '', schema: detail, required: !detail.nullable, in: 'body' }}
/>
) : null}
</li>
))}
+19 -3
View File
@@ -6,7 +6,7 @@ import type { Metadata, ResolvingMetadata } from 'next'
import { redirect } from 'next/navigation'
import { visit } from 'unist-util-visit'
import { REFERENCES, clientSdkIds } from '~/content/navigation.references'
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'
@@ -27,6 +27,7 @@ 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
@@ -57,6 +58,13 @@ export function parseReferencePath(slug: Array<string>) {
__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,
@@ -103,7 +111,11 @@ export async function generateReferenceStaticParams() {
},
]
return [...sdkPages, ...cliPages, ...apiPages]
const selfHostingPages = selfHostingServices.map((service) => ({
slug: [REFERENCES[service].libPath],
}))
return [...sdkPages, ...cliPages, ...apiPages, ...selfHostingPages]
}
export async function generateReferenceMetadata(
@@ -116,7 +128,7 @@ export async function generateReferenceMetadata(
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]
@@ -162,6 +174,10 @@ export async function generateReferenceMetadata(
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 {}
}
-17
View File
@@ -1,17 +0,0 @@
import sections from '../../spec/common-api-sections.json'
import { flattenSections } from '../helpers'
const flatSections = flattenSections(sections)
/**
* Generates the API page links for the management API reference.
* @returns {Array<{link: string}>} - An array of API page links.
*/
export function generateAPIPages() {
let apiPages = []
flatSections.map((section) => {
apiPages.push({ link: `reference/api/${section.slug}` })
})
return apiPages
}
@@ -6,6 +6,16 @@ export async function generateReferencePages() {
await Promise.all(
Object.keys(REFERENCES)
.flatMap((key) => {
if (REFERENCES[key].versions.length === 0) {
return [
{
sdkId: REFERENCES[key].libPath,
version: 'latest',
isLatestVersion: true,
libPath: REFERENCES[key].libPath,
},
]
}
return REFERENCES[key].versions.map((version) => {
return {
sdkId: key,
+1 -4
View File
@@ -8,12 +8,10 @@ import { globby } from 'globby'
import matter from 'gray-matter'
import prettier from 'prettier'
import { generateAPIPages } from './files/api'
import { generateCLIPages } from './files/cli'
import { generateReferencePages } from './files/reference-lib'
async function generate() {
const apiPages = generateAPIPages()
const cliPages = generateCLIPages()
const referencePages = await generateReferencePages()
@@ -34,8 +32,7 @@ async function generate() {
const allPages = (contentPages as Array<{ link: string; priority?: number }>).concat(
referencePages,
cliPages,
apiPages
cliPages
)
const sitemap = `
+5
View File
@@ -43,6 +43,11 @@ export function middleware(request: NextRequest) {
return NextResponse.rewrite(new URL(rewritePath, request.url))
}
if (lib.startsWith('self-hosting-')) {
const rewritePath = [REFERENCE_PATH, lib].join('/')
return NextResponse.rewrite(new URL(rewritePath, request.url))
}
if (clientSdkIds.includes(lib)) {
const version = /v\d+/.test(maybeVersion) ? maybeVersion : null
const rewritePath = [REFERENCE_PATH, lib, version].filter(Boolean).join('/')
+4 -2
View File
@@ -9,19 +9,20 @@
"prebuild": "npm run codegen:references",
"build": "next build",
"build:analyze": "ANALYZE=true next build",
"build:sitemap": "tsx ./internals/generate-sitemap.ts",
"postbuild": "npm run build:sitemap",
"start": "next start",
"lint": "next lint",
"typecheck": "tsc --noEmit",
"test": "vitest --exclude \"**/*.smoke.test.ts\"",
"test:smoke": "npm run codegen:references && vitest -t \"prod smoke test\"",
"build:sitemap": "tsx ./internals/generate-sitemap.ts",
"embeddings": "tsx scripts/search/generate-embeddings.ts",
"embeddings:refresh": "npm run embeddings -- --refresh",
"last-changed": "tsx scripts/last-changed.ts",
"last-changed:reset": "npm run last-changed -- --reset",
"codegen:references": "tsx features/docs/Reference.generated.script.ts",
"codemod:frontmatter": "node ./scripts/codemod/mdx-meta.mjs && prettier --write \"content/**/*.mdx\"",
"postbuild": "npm run build:sitemap"
"clean": "rm -rf ./docs/features/docs/generated"
},
"dependencies": {
"@code-hike/mdx": "^0.9.0",
@@ -107,6 +108,7 @@
"npm-run-all": "^4.1.5",
"openapi-types": "^12.1.3",
"simple-git": "^3.24.0",
"slugify": "^1.6.6",
"tsconfig": "*",
"tsx": "^4.6.2",
"typescript": "^5.5.0",
@@ -1,35 +0,0 @@
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { gen_v3 } from '~/lib/refGenerator/helpers'
import selfHostingAnalyticsCommonSections from '~/spec/common-self-hosting-analytics-sections.json' assert { type: 'json' }
import analyticsSpec from '~/spec/transforms/analytics_v0_openapi_deparsed.json' assert { type: 'json' }
const sections = flattenSections(selfHostingAnalyticsCommonSections)
const libraryPath = '/self-hosting-analytics'
// @ts-ignore
const spec = gen_v3(analyticsSpec, 'wat', { apiUrl: 'apiv0' })
export default function SelfHostAnalyticsReference(props) {
return (
<RefSectionHandler
menuId={MenuId.SelfHostingAnalytics}
sections={sections}
spec={spec}
pageProps={props}
type="api"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}
@@ -1,35 +0,0 @@
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { gen_v3 } from '~/lib/refGenerator/helpers'
import authSpec from '~/spec/auth_v1_openapi.json' assert { type: 'json' }
import selfHostingAuthCommonSections from '~/spec/common-self-hosting-auth-sections.json'
const sections = flattenSections(selfHostingAuthCommonSections)
const libraryPath = '/self-hosting-auth'
// @ts-ignore
const spec = gen_v3(authSpec, 'wat', { apiUrl: 'apiv0' })
export default function SelfHostAuthReference(props) {
return (
<RefSectionHandler
menuId={MenuId.SelfHostingAuth}
sections={sections}
spec={spec}
pageProps={props}
type="api"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}
@@ -1,29 +0,0 @@
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import selfHostingFunctionsCommonSections from '~/spec/common-self-hosting-functions-sections.json' assert { type: 'json' }
const sections = flattenSections(selfHostingFunctionsCommonSections)
const libraryPath = '/self-hosting-functions'
export default function SelfHostFunctionsReference(props) {
return (
<RefSectionHandler
menuId={MenuId.SelfHostingFunctions}
sections={sections}
pageProps={props}
type="api"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}
@@ -1,29 +0,0 @@
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import selfHostingRealtimeCommonSections from '~/spec/common-self-hosting-realtime-sections.json' assert { type: 'json' }
const sections = flattenSections(selfHostingRealtimeCommonSections)
const libraryPath = '/self-hosting-realtime'
export default function SelfHostRealtimeReference(props) {
return (
<RefSectionHandler
menuId={MenuId.SelfHostingRealtime}
sections={sections}
pageProps={props}
type="api"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}
@@ -1,35 +0,0 @@
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { gen_v3 } from '~/lib/refGenerator/helpers'
import storageSpec from '~/spec/storage_v0_openapi.json' assert { type: 'json' }
import selfHostingStorageCommonSections from '~/spec/common-self-hosting-storage-sections.json' assert { type: 'json' }
const sections = flattenSections(selfHostingStorageCommonSections)
const libraryPath = '/self-hosting-storage'
// @ts-ignore
const spec = gen_v3(storageSpec, 'wat', { apiUrl: 'apiv0' })
export default function SelfHostStorageReference(props) {
return (
<RefSectionHandler
menuId={MenuId.SelfHostingStorage}
sections={sections}
spec={spec}
pageProps={props}
type="api"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}
@@ -7,6 +7,7 @@
"type": "markdown"
},
{
"id": "management-api",
"type": "category",
"title": "Management API",
"items": [
@@ -14,91 +15,91 @@
"id": "list-endpoints",
"slug": "list-endpoints",
"title": "List endpoints",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "create-endpoint",
"slug": "create-endpoint",
"title": "Create endpoint",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "delete-endpoint",
"slug": "delete-endpoint",
"title": "Delete endpoint",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "fetch-endpoint",
"slug": "fetch-endpoint",
"title": "Fetch endpoint",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "update-endpoint",
"slug": "update-endpoint",
"title": "Update endpoint",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "list-sources",
"slug": "list-sources",
"title": "List sources",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "create-source",
"slug": "create-source",
"title": "Create source",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "delete-source",
"slug": "delete-source",
"title": "Delete source",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "fetch-source",
"slug": "fetch-source",
"title": "Fetch source",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "update-source",
"slug": "update-source",
"title": "Update source",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "list-teams",
"slug": "list-teams",
"title": "List teams",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "create-team",
"slug": "create-team",
"title": "Create team",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "delete-team",
"slug": "delete-team",
"title": "Delete team",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "fetch-team",
"slug": "fetch-team",
"title": "Fetch team",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "update-team",
"slug": "update-team",
"title": "Update team",
"type": "operation"
"type": "self-hosted-operation"
}
]
}
@@ -7,6 +7,7 @@
"type": "markdown"
},
{
"id": "usage",
"type": "category",
"title": "Usage",
"items": [
@@ -14,121 +15,121 @@
"id": "generates-an-email-action-link",
"slug": "generates-an-email-action-link",
"title": "Generates an email action link",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "get-a-user",
"slug": "get-a-user",
"title": "Get a user",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "update-a-user",
"slug": "update-a-user",
"title": "Update a user",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "deletes-a-user",
"slug": "deletes-a-user",
"title": "Deletes a user",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "list-all-users",
"slug": "list-all-users",
"title": "List all users",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "returns-the-created-user",
"slug": "returns-the-created-user",
"title": "Returns the created user",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "redirects-the-user-to-the-3rd-party-oauth-provider-to-start-the-oauth1-0-or-oauth2-0-authentication-process",
"slug": "redirects-the-user-to-the-3rd-party-oauth-provider-to-start-the-oauth1-0-or-oauth2-0-authentication-process",
"title": "Redirects the user to the 3rd party oauth provider to start the oauth1 0 or oauth2 0 authentication process",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "receives-the-redirect-from-an-external-provider-during-the-oauth-authentication-process-starts-the-process-of-creating-an-access-and-refresh-token",
"slug": "receives-the-redirect-from-an-external-provider-during-the-oauth-authentication-process-starts-the-process-of-creating-an-access-and-refresh-token",
"title": "Receives the redirect from an external provider during the oauth authentication process starts the process of creating an access and refresh token",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "the-healthcheck-endpoint-for-gotrue-returns-the-current-gotrue-version",
"slug": "the-healthcheck-endpoint-for-gotrue-returns-the-current-gotrue-version",
"title": "The healthcheck endpoint for gotrue returns the current gotrue version",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "sends-an-invite-link-to-the-user",
"slug": "sends-an-invite-link-to-the-user",
"title": "Sends an invite link to the user",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "logs-out-the-user",
"slug": "logs-out-the-user",
"title": "Logs out the user",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "passwordless-sign-in-method-for-email-or-phone",
"slug": "passwordless-sign-in-method-for-email-or-phone",
"title": "Passwordless sign in method for email or phone",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "sends-a-password-recovery-email-link-to-the-users-email",
"slug": "sends-a-password-recovery-email-link-to-the-users-email",
"title": "Sends a password recovery email link to the users email",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "returns-the-configuration-settings-for-the-gotrue-server",
"slug": "returns-the-configuration-settings-for-the-gotrue-server",
"title": "Returns the configuration settings for the gotrue server",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "password-based-signup-with-either-email-or-phone",
"slug": "password-based-signup-with-either-email-or-phone",
"title": "Password based signup with either email or phone",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "signs-in-a-user-with-a-password",
"slug": "signs-in-a-user-with-a-password",
"title": "Signs in a user with a password",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "refreshes-a-users-refresh-token",
"slug": "refreshes-a-users-refresh-token",
"title": "Refreshes a users refresh token",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "get-information-for-the-logged-in-user",
"slug": "get-information-for-the-logged-in-user",
"title": "Get information for the logged in user",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "returns-the-updated-user",
"slug": "returns-the-updated-user",
"title": "Returns the updated user",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "verifies-a-sign-up",
"slug": "verifies-a-sign-up",
"title": "Verifies a sign up",
"type": "operation"
"type": "self-hosted-operation"
}
]
}
@@ -7,6 +7,7 @@
"type": "markdown"
},
{
"id": "usage",
"type": "category",
"title": "Usage",
"items": [
@@ -15,147 +16,147 @@
"title": "Create a bucket",
"slug": "create-a-bucket",
"summary": "Create a bucket",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "gets-all-buckets",
"title": "Gets all buckets",
"slug": "gets-all-buckets",
"summary": "Gets all buckets",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "empty-a-bucket",
"title": "Empty a bucket",
"slug": "empty-a-bucket",
"summary": "Empty a bucket",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "get-details-of-a-bucket",
"title": "Get details of a bucket",
"slug": "get-details-of-a-bucket",
"summary": "Get details of a bucket",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "update-properties-of-a-bucket",
"title": "Update properties of a bucket",
"slug": "update-properties-of-a-bucket",
"summary": "Update properties of a bucket",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "delete-a-bucket",
"title": "Delete a bucket",
"slug": "delete-a-bucket",
"summary": "Delete a bucket",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "delete-an-object",
"title": "Delete an object",
"slug": "delete-an-object",
"summary": "Delete an object",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "get-object",
"title": "Get object",
"slug": "get-object",
"summary": "Get object",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "update-the-object-at-an-existing-key",
"title": "Update the object at an existing key",
"slug": "update-the-object-at-an-existing-key",
"summary": "Update the object at an existing key",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "upload-a-new-object",
"title": "Upload a new object",
"slug": "upload-a-new-object",
"summary": "Upload a new object",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "delete-multiple-objects",
"title": "Delete multiple objects",
"slug": "delete-multiple-objects",
"summary": "Delete multiple objects",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "retrieve-an-object",
"title": "Retrieve an object",
"slug": "retrieve-an-object",
"summary": "Retrieve an object",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "generate-a-presigned-url-to-retrieve-an-object",
"title": "Generate a presigned url to retrieve an object",
"slug": "generate-a-presigned-url-to-retrieve-an-object",
"summary": "Generate a presigned url to retrieve an object",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "retrieve-an-object-via-a-presigned-url",
"title": "Retrieve an object via a presigned URL",
"slug": "retrieve-an-object-via-a-presigned-url",
"summary": "Retrieve an object via a presigned URL",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "generate-presigned-urls-to-retrieve-objects",
"title": "Generate presigned urls to retrieve objects",
"slug": "generate-presigned-urls-to-retrieve-objects",
"summary": "Generate presigned urls to retrieve objects",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "moves-an-object",
"title": "Moves an object",
"slug": "moves-an-object",
"summary": "Moves an object",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "search-for-objects-under-a-prefix",
"title": "Search for objects under a prefix",
"slug": "search-for-objects-under-a-prefix",
"summary": "Search for objects under a prefix",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "retrieve-object-info",
"title": "Retrieve object info",
"slug": "retrieve-object-info",
"summary": "Retrieve object info",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "copies-an-object",
"title": "Copies an object",
"slug": "copies-an-object",
"summary": "Copies an object",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "retrieve-an-object-from-a-public-bucket",
"title": "Retrieve an object from a public bucket",
"slug": "retrieve-an-object-from-a-public-bucket",
"summary": "Retrieve an object from a public bucket",
"type": "operation"
"type": "self-hosted-operation"
},
{
"id": "get-object-info",
"title": "Get object info",
"slug": "get-object-info",
"summary": "Get object info",
"type": "operation"
"type": "self-hosted-operation"
}
]
}
+10
View File
@@ -932,6 +932,7 @@
"npm-run-all": "^4.1.5",
"openapi-types": "^12.1.3",
"simple-git": "^3.24.0",
"slugify": "^1.6.6",
"tsconfig": "*",
"tsx": "^4.6.2",
"typescript": "^5.5.0",
@@ -1376,6 +1377,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"apps/docs/node_modules/slugify": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
"dev": true,
"engines": {
"node": ">=8.0.0"
}
},
"apps/docs/node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",