mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
refactor(website,guide): cloudflare workers support (#11204)
This commit is contained in:
3
apps/website/.gitignore
vendored
3
apps/website/.gitignore
vendored
@@ -16,8 +16,11 @@ pids
|
||||
.env*.local
|
||||
|
||||
# Dist
|
||||
.open-next
|
||||
.next
|
||||
.wrangler
|
||||
public/searchIndex
|
||||
public/readme
|
||||
src/assets/readme
|
||||
src/styles/unocss.css
|
||||
|
||||
|
||||
@@ -19,13 +19,6 @@ export default {
|
||||
fullUrl: true,
|
||||
},
|
||||
},
|
||||
experimental: {
|
||||
ppr: true,
|
||||
dynamicOnHover: true,
|
||||
},
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
reactCompiler: true,
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
|
||||
3
apps/website/open-next.config.ts
Normal file
3
apps/website/open-next.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
|
||||
|
||||
export default defineCloudflareConfig();
|
||||
@@ -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"
|
||||
|
||||
2
apps/website/public/_headers
Normal file
2
apps/website/public/_headers
Normal file
@@ -0,0 +1,2 @@
|
||||
/_next/static/*
|
||||
Cache-Control: public,max-age=31536000,immutable
|
||||
BIN
apps/website/public/web-app-manifest-192x192.png
Normal file
BIN
apps/website/public/web-app-manifest-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
apps/website/public/web-app-manifest-512x512.png
Normal file
BIN
apps/website/public/web-app-manifest-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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>
|
||||
)) ?? [];
|
||||
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
19
apps/website/wrangler.jsonc
Normal file
19
apps/website/wrangler.jsonc
Normal 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user