import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import process, { cwd } from 'node:process'; import { findPackage, getMembers, type ApiItemJSON, type ApiClassJSON, type ApiFunctionJSON, type ApiInterfaceJSON, type ApiTypeAliasJSON, type ApiVariableJSON, type ApiEnumJSON, } from '@discordjs/api-extractor-utils'; import { ActionIcon, Affix, Box, LoadingOverlay, Transition } from '@mantine/core'; import { useMediaQuery, useWindowScroll } from '@mantine/hooks'; import { ApiFunction, ApiItemKind, type ApiPackage } from '@microsoft/api-extractor-model'; import Head from 'next/head'; import { useRouter } from 'next/router'; import type { GetStaticPaths, GetStaticProps } from 'next/types'; import { MDXRemote } from 'next-mdx-remote'; import { serialize } from 'next-mdx-remote/serialize'; import { VscChevronUp } from 'react-icons/vsc'; import rehypeIgnore from 'rehype-ignore'; import rehypePrettyCode from 'rehype-pretty-code'; import rehypeRaw from 'rehype-raw'; import rehypeSlug from 'rehype-slug'; import remarkGfm from 'remark-gfm'; import { SidebarLayout, type SidebarLayoutProps } from '~/components/SidebarLayout'; import { Class } from '~/components/model/Class'; import { Enum } from '~/components/model/Enum'; import { Function } from '~/components/model/Function'; import { Interface } from '~/components/model/Interface'; import { TypeAlias } from '~/components/model/TypeAlias'; import { Variable } from '~/components/model/Variable'; import { MemberProvider } from '~/contexts/member'; import { createApiModel } from '~/util/api-model.server'; import { findMember, findMemberByKey } from '~/util/model.server'; import { PACKAGES } from '~/util/packages'; export const getStaticPaths: GetStaticPaths = async () => { const pkgs = ( await Promise.all( PACKAGES.map(async (packageName) => { try { let data: any[] = []; let versions: string[] = []; if (process.env.NEXT_PUBLIC_LOCAL_DEV) { const res = await readFile(join(cwd(), '..', packageName, 'docs', 'docs.api.json'), 'utf8'); data = JSON.parse(res); } else { const response = await fetch(`https://docs.discordjs.dev/api/info?package=${packageName}`); versions = await response.json(); for (const version of versions) { const res = await fetch(`https://docs.discordjs.dev/docs/${packageName}/${version}.api.json`); data = [...data, await res.json()]; } } if (Array.isArray(data)) { const models = data.map((innerData) => createApiModel(innerData)); const pkgs = models.map((model) => findPackage(model, packageName)) as ApiPackage[]; return [ ...versions.map((version) => ({ params: { slug: ['packages', packageName, version] } })), ...pkgs.flatMap((pkg, idx) => getMembers(pkg, versions[idx]!).map((member) => { if (member.kind === ApiItemKind.Function && member.overloadIndex && member.overloadIndex > 1) { return { params: { slug: [ 'packages', packageName, versions[idx]!, `${member.name}:${member.overloadIndex}:${member.kind}`, ], }, }; } return { params: { slug: ['packages', packageName, versions[idx]!, `${member.name}:${member.kind}`], }, }; }), ), ]; } const model = createApiModel(data); const pkg = findPackage(model, packageName)!; return [ { params: { slug: ['packages', packageName, 'main'] } }, ...getMembers(pkg, 'main').map((member) => { if (member.kind === ApiItemKind.Function && member.overloadIndex && member.overloadIndex > 1) { return { params: { slug: ['packages', packageName, 'main', `${member.name}:${member.overloadIndex}:${member.kind}`], }, }; } return { params: { slug: ['packages', packageName, 'main', `${member.name}:${member.kind}`] } }; }), ]; } catch { return { params: { slug: [] } }; } }), ) ).flat(); return { paths: pkgs, fallback: true, }; }; export const getStaticProps: GetStaticProps = async ({ params }) => { const [, packageName = 'builders', branchName = 'main', member] = params!.slug as string[]; const [memberName, overloadIndex] = member?.split(':') ?? []; try { const readme = await readFile(join(cwd(), '..', packageName, 'README.md'), 'utf8'); const mdxSource = await serialize(readme, { mdxOptions: { remarkPlugins: [remarkGfm], remarkRehypeOptions: { allowDangerousHtml: true }, rehypePlugins: [rehypeRaw, rehypeIgnore, rehypeSlug, [rehypePrettyCode, { theme: 'dark-plus' }]], format: 'md', }, }); let data; if (process.env.NEXT_PUBLIC_LOCAL_DEV) { const res = await readFile(join(cwd(), '..', packageName, 'docs', 'docs.api.json'), 'utf8'); data = JSON.parse(res); } else { const res = await fetch(`https://docs.discordjs.dev/docs/${packageName}/${branchName}.api.json`); data = await res.json(); } const model = createApiModel(data); const pkg = findPackage(model, packageName); // eslint-disable-next-line prefer-const let { containerKey, name } = findMember(model, packageName, memberName, branchName) ?? {}; if (name && overloadIndex && !Number.isNaN(Number.parseInt(overloadIndex, 10))) { containerKey = ApiFunction.getContainerKey(name, Number.parseInt(overloadIndex, 10)); } return { props: { packageName, branchName, data: { members: pkg ? getMembers(pkg, branchName) : [], member: memberName && containerKey ? findMemberByKey(model, packageName, containerKey, branchName) ?? null : null, source: mdxSource, }, }, revalidate: 3_600, }; } catch (error_) { const error = error_ as Error; console.error(error); return { props: { error: error_, }, revalidate: 3_600, }; } }; const member = (props?: ApiItemJSON | undefined) => { switch (props?.kind) { case 'Class': return ; case 'Function': return ; case 'Interface': return ; case 'TypeAlias': return ; case 'Variable': return ; case 'Enum': return ; default: return Cannot render that item type; } }; export default function SlugPage(props: Partial) { const router = useRouter(); const [scroll, scrollTo] = useWindowScroll(); const matches = useMediaQuery('(max-width: 1200px)'); const name = `discord.js${props.data?.member?.name ? ` | ${props.data.member.name}` : ''}`; if (router.isFallback) { return ( ); } // Just in case // return ; return props.error ? ( {props.error} ) : ( {props.data?.member ? ( <> {name} {member(props.data.member)} ) : props.data?.source ? ( ({ a: { backgroundColor: 'transparent', color: theme.colors.blurple![0], textDecoration: 'none', }, img: { borderStyle: 'none', maxWidth: '100%', boxSizing: 'content-box', }, })} px="xl" > ) : null} 200}> {(transitionStyles) => ( scrollTo({ y: 0 })} > )} ); }