refactor(website,guide): cloudflare workers support (#11204)

This commit is contained in:
Noel
2025-10-25 02:17:59 +02:00
committed by GitHub
parent 756eac6bb1
commit 08b87d9087
62 changed files with 5143 additions and 1709 deletions

View File

@@ -16,7 +16,9 @@ pids
.env*.local
# Dist
.open-next
.next
.wrangler
.source
# Miscellaneous

View File

@@ -2,7 +2,7 @@
title: Introduction
---
import { GithubInfo } from 'fumadocs-ui/components/github-info';
import { GithubInfo } from '@/components/GitHubInfo';
<GithubInfo owner="discordjs" repo="discord.js" />

View File

@@ -23,14 +23,6 @@ export default withMDX({
fullUrl: true,
},
},
experimental: {
ppr: true,
useCache: true,
dynamicOnHover: true,
},
eslint: {
ignoreDuringBuilds: true,
},
reactCompiler: true,
typescript: {
ignoreBuildErrors: true,

View File

@@ -0,0 +1,3 @@
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
export default defineCloudflareConfig();

View File

@@ -5,11 +5,15 @@
"description": "Imagine a bot... the most popular way to build discord bots",
"private": true,
"scripts": {
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts",
"build:check": "tsc --noEmit",
"build:local": "cross-env NEXT_PUBLIC_LOCAL_DEV=true pnpm run build:prod",
"build:prod": "pnpm run build:next",
"build:next": "next build",
"build": "next build --webpack",
"preview": "next start",
"preview:cf": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"deploy:cf": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"dev": "next dev -p 3001 --turbopack",
"lint": "pnpm run build:check && prettier --check . && cross-env TIMING=1 eslint --format=pretty src ",
"format": "pnpm run build:check && prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src ",
@@ -44,30 +48,32 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@opennextjs/cloudflare": "^1.11.0",
"@react-icons/all-files": "^4.1.0",
"@vercel/analytics": "^1.5.0",
"cmdk": "^1.1.1",
"cva": "1.0.0-beta.3",
"fumadocs-core": "^15.8.4",
"fumadocs-mdx": "^12.0.3",
"fumadocs-twoslash": "^3.1.8",
"fumadocs-ui": "^15.8.4",
"fumadocs-core": "^16.0.2",
"fumadocs-mdx": "^13.0.0",
"fumadocs-twoslash": "^3.1.9",
"fumadocs-ui": "^16.0.2",
"geist": "^1.5.1",
"immer": "^10.1.3",
"jotai": "^2.15.0",
"jotai-immer": "^0.4.1",
"lucide-react": "^0.545.0",
"lucide-react": "^0.548.0",
"mermaid": "^11.12.0",
"motion": "^12.23.22",
"next": "15.6.0-canary.45",
"next-mdx-remote-client": "^2.1.6",
"motion": "^12.23.24",
"next": "^16.0.0",
"next-mdx-remote-client": "^2.1.7",
"next-themes": "^0.4.6",
"nuqs": "^2.7.1",
"nuqs": "^2.7.2",
"react": "^19.2.0",
"react-aria": "^3.44.0",
"react-aria-components": "^1.13.0",
"react-dom": "^19.2.0",
"react-error-boundary": "^6.0.0",
"safe-mdx": "^1.3.8",
"sharp": "^0.34.4",
"tailwind-merge": "^3.3.1",
"tw-animate-css": "^1.4.0",
@@ -75,15 +81,15 @@
"usehooks-ts": "^3.1.1"
},
"devDependencies": {
"@next/env": "^15.5.4",
"@next/env": "^16.0.0",
"@shikijs/rehype": "^3.13.0",
"@tailwindcss/postcss": "^4.1.14",
"@tailwindcss/postcss": "^4.1.16",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.14",
"@tailwindcss/vite": "^4.1.16",
"@types/mdx": "^2.0.13",
"@types/node": "^22.18.8",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@types/node": "^24.9.1",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"autoprefixer": "^10.4.21",
"babel-plugin-react-compiler": "19.1.0-rc.3",
"cpy-cli": "^6.0.0",
@@ -94,15 +100,16 @@
"git-describe": "^4.1.1",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"prettier-plugin-tailwindcss": "^0.7.1",
"remark-gfm": "^4.0.1",
"remark-rehype": "^11.1.2",
"shiki": "^3.13.0",
"tailwindcss": "^4.1.14",
"tailwindcss": "^4.1.16",
"tailwindcss-react-aria-components": "^2.0.1",
"turbo": "^2.5.8",
"typescript": "^5.9.3",
"vercel": "^48.2.1"
"vercel": "^48.2.1",
"wrangler": "^4.45.0"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -0,0 +1,2 @@
/_next/static/*
Cache-Control: public,max-age=31536000,immutable

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/mstile-70x70.png"/>
<square150x150logo src="/mstile-150x150.png"/>
<square310x310logo src="/mstile-310x310.png"/>
<wide310x150logo src="/mstile-310x150.png"/>
<TileColor>#090a16</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="180" height="180" viewBox="0 0 180 180"><image width="180" height="180" xlink:href="data:image/png;base64,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"></image><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
@media (prefers-color-scheme: dark) { :root { filter: none; } }
</style></svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="400.000000pt" height="400.000000pt" viewBox="0 0 400.000000 400.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,400.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M0 2000 l0 -2000 2000 0 2000 0 0 2000 0 2000 -2000 0 -2000 0 0
-2000z m1305 795 c219 -47 373 -197 421 -411 24 -109 16 -315 -16 -399 l-23
-61 -59 -11 c-110 -21 -182 -92 -203 -202 -11 -56 -11 -56 -68 -79 -76 -30
-201 -42 -464 -42 l-223 0 0 610 0 610 283 0 c210 0 300 -4 352 -15z m890
-585 c0 -543 -2 -601 -18 -659 -56 -198 -190 -334 -365 -370 -176 -37 -349 5
-471 115 -48 44 -111 134 -111 161 0 6 19 14 43 18 23 4 73 18 110 32 l68 24
50 -45 c56 -50 98 -66 175 -66 100 0 207 81 234 178 6 23 10 251 10 625 l0
588 138 -3 137 -3 0 -595z m930 570 c93 -29 197 -84 252 -134 l44 -41 -61 -75
c-34 -41 -67 -81 -74 -88 -10 -10 -25 -4 -78 32 -95 63 -154 81 -268 81 -79 0
-103 -4 -142 -23 -62 -31 -90 -71 -96 -137 -8 -94 28 -137 168 -202 157 -73
172 -80 240 -108 236 -97 341 -215 356 -400 16 -193 -82 -363 -258 -449 -91
-45 -187 -66 -303 -66 -191 0 -390 74 -524 195 l-40 37 80 93 80 94 73 -53
c168 -121 353 -155 491 -92 72 33 108 82 113 155 9 122 -37 162 -323 286 -258
111 -350 179 -407 300 -69 148 -37 343 75 463 61 65 195 133 300 152 79 14
222 4 302 -20z m-1367 -977 c45 -34 65 -69 70 -125 4 -61 -19 -106 -77 -145
-90 -60 -226 3 -238 111 -3 24 -3 56 1 70 8 34 54 86 91 102 40 19 120 12 153
-13z"/>
<path d="M967 2543 c-4 -3 -7 -159 -7 -346 l0 -339 128 4 c101 3 136 8 172 24
106 48 162 145 168 292 10 214 -69 334 -238 361 -73 12 -213 15 -223 4z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -3,14 +3,16 @@
"short_name": "discord.js",
"icons": [
{
"src": "/android-chrome-192x192.png",
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png"
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/android-chrome-384x384.png",
"sizes": "384x384",
"type": "image/png"
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#1a1b1e",

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,6 +1,6 @@
import { Analytics } from '@vercel/analytics/react';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { RootProvider } from 'fumadocs-ui/provider';
import { RootProvider } from 'fumadocs-ui/provider/next';
import { GeistMono } from 'geist/font/mono';
import { GeistSans } from 'geist/font/sans';
import type { Metadata, Viewport } from 'next';
@@ -29,33 +29,16 @@ export const metadata: Metadata = {
icons: {
other: [
{
url: '/favicon-32x32.png',
sizes: '32x32',
type: 'image/png',
},
{
url: '/favicon-16x16.png',
sizes: '16x16',
url: '/favicon-96x96.png',
sizes: '96x96',
type: 'image/png',
},
],
apple: [
'/apple-touch-icon.png',
{
url: '/safari-pinned-tab.svg',
rel: 'mask-icon',
},
],
apple: ['/apple-touch-icon.png'],
},
manifest: '/site.webmanifest',
appleWebApp: {
title: 'discord.js',
},
applicationName: 'discord.js',
openGraph: {
siteName: 'discord.js',
type: 'website',
@@ -67,10 +50,6 @@ export const metadata: Metadata = {
card: 'summary_large_image',
creator: '@iCrawlToGo',
},
other: {
'msapplication-TileColor': '#1a1a1e',
},
};
export default async function RootLayout({ children }: PropsWithChildren) {

View File

@@ -9,12 +9,25 @@ export function generateStaticParams() {
}));
}
async function loadGoogleFont(font: string, text: string) {
const url = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`;
const css = await (await fetch(url)).text();
// eslint-disable-next-line prefer-named-capture-group
const resource = /src: url\((.+)\) format\('(opentype|truetype)'\)/.exec(css);
if (resource) {
const response = await fetch(resource[1]!);
if (response.status === 200) {
return response.arrayBuffer();
}
}
throw new Error('failed to load font data');
}
export async function GET(_req: Request, { params }: { params: Promise<{ slug: string[] }> }) {
const { slug } = await params;
const page = source.getPage(slug.slice(0, -1));
// const fontData = await fetch(new URL('../../assets/Geist-Regular.ttf', import.meta.url), {
// next: { revalidate: 604_800 },
// }).then(async (res) => res.arrayBuffer());
if (!page) {
notFound();
@@ -24,13 +37,13 @@ export async function GET(_req: Request, { params }: { params: Promise<{ slug: s
title: page.data.title,
description: page.data.description,
site: 'discord.js Guide',
// fonts: [
// {
// name: 'Geist',
// data: fontData,
// weight: 900,
// style: 'normal',
// },
// ],
fonts: [
{
name: 'Geist',
data: await loadGoogleFont('Geist:wght@400', page.data.title),
weight: 400,
style: 'normal',
},
],
});
}

View File

@@ -0,0 +1,109 @@
// https://github.com/fuma-nama/fumadocs/blob/dev/packages/ui/src/components/github-info.tsx
// https://github.com/fuma-nama/fumadocs/blob/dev/LICENSE
import { Star } from 'lucide-react';
import { type AnchorHTMLAttributes } from 'react';
import { twMerge as cn } from 'tailwind-merge';
async function getRepoStarsAndForks(
owner: string,
repo: string,
token?: string,
): Promise<{
forks: number;
stars: number;
}> {
const endpoint = `https://api.github.com/repos/${owner}/${repo}`;
const headers = new Headers({
'Content-Type': 'application/json',
'User-Agent': 'discordjs-guide',
});
if (token) headers.set('Authorization', `Bearer ${token}`);
const response = await fetch(endpoint, {
headers,
next: {
revalidate: 60,
},
} as RequestInit);
if (!response.ok) {
const message = await response.text();
throw new Error(`Failed to fetch repository data: ${message}`);
}
const data = await response.json();
return {
stars: data.stargazers_count,
forks: data.forks_count,
};
}
export async function GithubInfo({
repo,
owner,
token,
...props
}: AnchorHTMLAttributes<HTMLAnchorElement> & {
readonly owner: string;
readonly repo: string;
readonly token?: string;
}) {
const { stars } = await getRepoStarsAndForks(owner, repo, token);
const humanizedStars = humanizeNumber(stars);
return (
<a
href={`https://github.com/${owner}/${repo}`}
rel="noreferrer noopener"
target="_blank"
{...props}
className={cn(
'text-fd-foreground/80 hover:text-fd-accent-foreground hover:bg-fd-accent flex flex-col gap-1.5 rounded-lg p-2 text-sm transition-colors lg:flex-row lg:items-center',
props.className,
)}
>
<p className="flex items-center gap-2 truncate">
<svg className="size-3.5" fill="currentColor" viewBox="0 0 24 24">
<title>GitHub</title>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>
{owner}/{repo}
</p>
<p className="text-fd-muted-foreground flex items-center gap-1 text-xs">
<Star className="size-3" />
{humanizedStars}
</p>
</a>
);
}
/**
* Converts a number to a human-readable string with K suffix for thousands
*
* @example 1500 -> "1.5K", 1000000 -> "1000000"
*/
function humanizeNumber(num: number): string {
if (num < 1_000) {
return num.toString();
}
if (num < 100_000) {
// For numbers between 1,000 and 99,999, show with one decimal (e.g., 1.5K)
const value = (num / 1_000).toFixed(1);
// Remove trailing .0 if present
const formattedValue = value.endsWith('.0') ? value.slice(0, -2) : value;
return `${formattedValue}K`;
}
if (num < 1_000_000) {
// For numbers between 10,000 and 999,999, show as whole K (e.g., 10K, 999K)
return `${Math.floor(num / 1_000)}K`;
}
// For 1,000,000 and above, just return the number
return num.toString();
}

19
apps/guide/wrangler.jsonc Normal file
View File

@@ -0,0 +1,19 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"main": ".open-next/worker.js",
"name": "discordjs-guide",
"keep_names": false,
"compatibility_date": "2025-10-04",
"compatibility_flags": ["nodejs_compat"],
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS",
},
"observability": {
"logs": {
"enabled": true,
"head_sampling_rate": 1,
"invocation_logs": true,
},
},
}

View File

@@ -16,8 +16,11 @@ pids
.env*.local
# Dist
.open-next
.next
.wrangler
public/searchIndex
public/readme
src/assets/readme
src/styles/unocss.css

View File

@@ -19,13 +19,6 @@ export default {
fullUrl: true,
},
},
experimental: {
ppr: true,
dynamicOnHover: true,
},
eslint: {
ignoreDuringBuilds: true,
},
reactCompiler: true,
typescript: {
ignoreBuildErrors: true,

View File

@@ -0,0 +1,3 @@
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
export default defineCloudflareConfig();

View File

@@ -5,14 +5,18 @@
"description": "Imagine a bot... the most popular way to build discord bots",
"private": true,
"scripts": {
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts",
"build:copy_readme": "cpy \"../../packages/(discord.js|brokers|builders|collection|core|formatters|next|proxy|rest|structures|util|voice|ws)/README.md\" \"src/assets/readme\" --rename='home-{{basename}}'",
"build:check": "tsc --noEmit",
"build:local": "cross-env NEXT_PUBLIC_LOCAL_DEV=true pnpm run build:prod",
"build:prod": "pnpm run build:copy_readme && pnpm run build:next",
"build:next": "next build",
"build": "pnpm run build:copy_readme && next build --webpack",
"build:search_indices": "pnpm node scripts/generateAllIndices.js",
"build:analyze": "turbo run docs --filter='@discordjs/*' --concurrency=4 && cross-env ANALYZE=true NEXT_PUBLIC_LOCAL_DEV=true pnpm run build:prod",
"preview": "next start",
"preview:cf": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"deploy:cf": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"dev": "next dev --turbopack",
"lint": "pnpm run build:check && prettier --check . && cross-env TIMING=1 eslint --format=pretty src ",
"format": "pnpm run build:check && prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src ",
@@ -47,11 +51,12 @@
"homepage": "https://discord.js.org",
"funding": "https://github.com/discordjs/discord.js?sponsor",
"dependencies": {
"@opennextjs/cloudflare": "^1.11.0",
"@radix-ui/react-collapsible": "^1.1.12",
"@react-icons/all-files": "^4.1.0",
"@tanstack/react-query": "^5.90.2",
"@tanstack/react-query": "^5.90.5",
"@vercel/analytics": "^1.5.0",
"@vercel/edge-config": "^1.4.0",
"@vercel/edge-config": "^1.4.3",
"@vercel/postgres": "^0.10.0",
"cloudflare": "^5.2.0",
"cmdk": "^1.1.1",
@@ -60,13 +65,13 @@
"immer": "^10.1.3",
"jotai": "^2.15.0",
"jotai-immer": "^0.4.1",
"lucide-react": "^0.545.0",
"meilisearch": "^0.50.0",
"motion": "^12.23.22",
"next": "15.6.0-canary.45",
"next-mdx-remote-client": "^2.1.6",
"lucide-react": "^0.548.0",
"meilisearch": "^0.53.0",
"motion": "^12.23.24",
"next": "^16.0.0",
"next-mdx-remote-client": "^2.1.7",
"next-themes": "^0.4.6",
"nuqs": "^2.7.1",
"nuqs": "^2.7.2",
"overlayscrollbars": "^2.12.0",
"overlayscrollbars-react": "^0.5.6",
"react": "^19.2.0",
@@ -74,20 +79,21 @@
"react-aria-components": "^1.13.0",
"react-dom": "^19.2.0",
"react-error-boundary": "^6.0.0",
"safe-mdx": "^1.3.8",
"sharp": "^0.34.4",
"tailwind-merge": "^3.3.1",
"tw-animate-css": "^1.4.0",
"usehooks-ts": "^3.1.1"
},
"devDependencies": {
"@next/env": "^15.5.4",
"@next/env": "^16.0.0",
"@shikijs/rehype": "^3.13.0",
"@tailwindcss/postcss": "^4.1.14",
"@tailwindcss/postcss": "^4.1.16",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.14",
"@types/node": "^22.18.8",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@tailwindcss/vite": "^4.1.16",
"@types/node": "^24.9.1",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"autoprefixer": "^10.4.21",
"babel-plugin-react-compiler": "19.1.0-rc.3",
"cpy-cli": "^6.0.0",
@@ -98,15 +104,16 @@
"git-describe": "^4.1.1",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"prettier-plugin-tailwindcss": "^0.7.1",
"remark-gfm": "^4.0.1",
"remark-rehype": "^11.1.2",
"shiki": "^3.13.0",
"tailwindcss": "^4.1.14",
"tailwindcss": "^4.1.16",
"tailwindcss-react-aria-components": "^2.0.1",
"turbo": "^2.5.8",
"typescript": "^5.9.3",
"vercel": "^48.2.1"
"vercel": "^48.2.1",
"wrangler": "^4.45.0"
},
"engines": {
"node": ">=22.12.0"

View File

@@ -0,0 +1,2 @@
/_next/static/*
Cache-Control: public,max-age=31536000,immutable

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -3,8 +3,6 @@
import { ImageResponse } from 'next/og';
import { resolveKind } from '@/util/resolveNodeKind';
export const runtime = 'edge';
export const size = {
width: 1_200,
height: 630,
@@ -12,6 +10,22 @@ export const size = {
export const contentType = 'image/png';
async function loadGoogleFont(font: string, text: string) {
const url = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`;
const css = await (await fetch(url)).text();
// eslint-disable-next-line prefer-named-capture-group
const resource = /src: url\((.+)\) format\('(opentype|truetype)'\)/.exec(css);
if (resource) {
const response = await fetch(resource[1]!);
if (response.status === 200) {
return response.arrayBuffer();
}
}
throw new Error('failed to load font data');
}
export default async function Image({
params,
}: {
@@ -19,14 +33,6 @@ export default async function Image({
}) {
const { item, packageName, version } = await params;
const [fontDataBold, fontDataBlack] = await Promise.all([
fetch(new URL('../../../../../../assets/Geist-Bold.ttf', import.meta.url), {
next: { revalidate: 604_800 },
}).then(async (res) => res.arrayBuffer()),
fetch(new URL('../../../../../../assets/Geist-Black.ttf', import.meta.url), {
next: { revalidate: 604_800 },
}).then(async (res) => res.arrayBuffer()),
]);
const normalizeItem = item.split(encodeURIComponent(':')).join('.').toLowerCase();
const isMain = version === 'main';
@@ -107,13 +113,13 @@ export default async function Image({
fonts: [
{
name: 'Geist',
data: fontDataBold,
data: await loadGoogleFont('Geist:wght@700', node.displayName),
weight: 700,
style: 'normal',
},
{
name: 'Geist',
data: fontDataBlack,
data: await loadGoogleFont('Geist:wght@900', node.displayName),
weight: 900,
style: 'normal',
},

View File

@@ -1,15 +1,12 @@
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import rehypeShikiFromHighlighter from '@shikijs/rehype/core';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { MDXRemote } from 'next-mdx-remote-client/rsc';
import remarkGfm from 'remark-gfm';
import { SafeMdxRenderer } from 'safe-mdx';
import { mdxParse } from 'safe-mdx/parse';
import { DocItem } from '@/components/DocItem';
import { PACKAGES_WITH_ENTRY_POINTS } from '@/util/constants';
import { SyntaxHighlighter } from '@/components/SyntaxHighlighter';
// import { PACKAGES_WITH_ENTRY_POINTS } from '@/util/constants';
import { fetchNode } from '@/util/fetchNode';
import { parseDocsPathParams } from '@/util/parseDocsPathParams';
import { getSingletonHighlighter } from '@/util/shiki.bundle';
export async function generateMetadata({
params,
@@ -52,48 +49,38 @@ export default async function Page({
const { entryPoints: parsedEntrypoints, foundItem } = parseDocsPathParams(item);
if (!foundItem) {
const hasEntryPoint = PACKAGES_WITH_ENTRY_POINTS.includes(packageName);
// const hasEntryPoint = PACKAGES_WITH_ENTRY_POINTS.includes(packageName);
if (hasEntryPoint) {
return <>Placeholder</>;
}
// if (hasEntryPoint) {
// return <>Placeholder</>;
// }
let fileContent: string;
try {
fileContent = await readFile(join(process.cwd(), `src/assets/readme/${packageName}/home-README.md`), 'utf8');
} catch (error: any) {
if ('code' in error && error.code === 'ENOENT') {
notFound();
}
throw error;
fileContent = await fetch(`${process.env.CF_R2_README_BUCKET_URL}/${packageName}/home-README.md`).then(
async (res) => res.text(),
);
} catch {
notFound();
}
const mdast = mdxParse(fileContent);
return (
<div className="prose prose-neutral dark:prose-invert prose-a:[&>img]:inline-block prose-a:[&>img]:m-0 prose-a:[&>img[height='44']]:h-11 prose-p:my-2 prose-pre:py-3 prose-pre:rounded-sm prose-pre:px-0 prose-pre:border prose-pre:border-[#d4d4d4] dark:prose-pre:border-[#404040] prose-code:font-normal prose-a:text-[#5865F2] prose-a:no-underline prose-a:hover:text-[#3d48c3] dark:prose-a:hover:text-[#7782fa] mx-auto max-w-screen-xl px-6 py-6 break-words [&_code_span:last-of-type:empty]:hidden [&_div[align='center']_p_a+a]:ml-2">
<MDXRemote
options={{
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
[
rehypeShikiFromHighlighter,
await getSingletonHighlighter({
langs: ['typescript', 'javascript', 'shellscript'],
themes: ['github-light', 'github-dark-dimmed'],
}),
{
themes: {
light: 'github-light',
dark: 'github-dark-dimmed',
},
},
],
],
},
<SafeMdxRenderer
markdown={fileContent}
mdast={mdast}
renderNode={(node) => {
if (node.type === 'code') {
const language = node.lang ?? 'text';
return <SyntaxHighlighter code={node.value} lang={language} />;
}
return undefined;
}}
source={fileContent}
/>
</div>
);

View File

@@ -2,8 +2,6 @@
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export const size = {
width: 1_200,
height: 630,
@@ -11,11 +9,23 @@ export const size = {
export const contentType = 'image/png';
export default async function Image() {
const fontData = await fetch(new URL('../assets/Geist-Black.ttf', import.meta.url), { cache: 'force-cache' }).then(
async (res) => res.arrayBuffer(),
);
async function loadGoogleFont(font: string, text: string) {
const url = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`;
const css = await (await fetch(url)).text();
// eslint-disable-next-line prefer-named-capture-group
const resource = /src: url\((.+)\) format\('(opentype|truetype)'\)/.exec(css);
if (resource) {
const response = await fetch(resource[1]!);
if (response.status === 200) {
return response.arrayBuffer();
}
}
throw new Error('failed to load font data');
}
export default async function Image() {
return new ImageResponse(
(
<div tw="flex bg-[#121214] h-full w-full">
@@ -39,7 +49,7 @@ export default async function Image() {
fonts: [
{
name: 'Geist',
data: fontData,
data: await loadGoogleFont('Geist:wght@900', 'The most popular way to build Discord bots.'),
weight: 900,
style: 'normal',
},

View File

@@ -43,12 +43,12 @@ export function CmdK({ dependencies }: { readonly dependencies: string[] }) {
value={item.id}
>
{resolveKind(item.kind)}
<div className="flex flex-grow flex-col">
<div className="flex grow flex-col">
<span className="font-semibold wrap-anywhere">{item.name}</span>
<span className={cx('truncate text-sm', isMobile ? 'max-w-[30ch]' : 'max-w-[40ch]')}>{item.summary}</span>
<span className={cx('truncate text-xs', isMobile ? 'max-w-[30ch]' : 'max-w-[40ch]')}>{item.path}</span>
</div>
<ArrowRight aria-hidden className="flex-shrink-0" />
<ArrowRight aria-hidden className="shrink-0" />
</Command.Item>
)) ?? [];

View File

@@ -1,9 +1,9 @@
import Cloudflare from 'cloudflare';
// import Cloudflare from 'cloudflare';
import { ENV } from './env';
const client = new Cloudflare({
apiToken: process.env.CF_D1_DOCS_API_KEY,
});
// const client = new Cloudflare({
// apiToken: process.env.CF_D1_DOCS_API_KEY,
// });
export async function fetchVersions(packageName: string) {
if (ENV.IS_LOCAL_DEV) {
@@ -11,13 +11,32 @@ export async function fetchVersions(packageName: string) {
}
try {
const { result } = await client.d1.database.query(process.env.CF_D1_DOCS_ID!, {
account_id: process.env.CF_ACCOUNT_ID!,
sql: `select version from documentation where name = ? order by version desc;`,
params: [packageName],
});
// const { result } = await client.d1.database.query(process.env.CF_D1_DOCS_ID!, {
// account_id: process.env.CF_ACCOUNT_ID!,
// sql: `select version from documentation where name = ? order by version desc;`,
// params: [packageName],
// });
return (result[0]?.results as { version: string }[] | undefined) ?? [];
// return (result[0]?.results as { version: string }[] | undefined) ?? [];
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${process.env.CF_ACCOUNT_ID}/d1/database/${process.env.CF_D1_DOCS_ID}/query`,
{
headers: {
Authorization: `Bearer ${process.env.CF_D1_DOCS_API_KEY}`,
'Content-Type': 'application/json',
},
method: 'POST',
body: JSON.stringify({
sql: `select version from documentation where name = ? order by version desc;`,
params: [packageName],
}),
},
);
const data = await response.json();
return data.result[0]?.results;
} catch {
return [];
}

View File

@@ -0,0 +1,19 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"main": ".open-next/worker.js",
"name": "discordjs-website",
"keep_names": false,
"compatibility_date": "2025-10-04",
"compatibility_flags": ["nodejs_compat"],
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS",
},
"observability": {
"logs": {
"enabled": true,
"head_sampling_rate": 1,
"invocation_logs": true,
},
},
}