The Node.js® Website

feat: api-data route for orama

Changed files
+93 -15
app
[locale]
next-data
api-data
page-data
components
Common
Downloads
ChangelogModal
types
util
+63
app/[locale]/next-data/api-data/route.ts
··· 1 + import { deflateSync } from 'node:zlib'; 2 + 3 + import { VERCEL_REVALIDATE } from '@/next.constants.mjs'; 4 + import { defaultLocale } from '@/next.locales.mjs'; 5 + import type { GitHubApiFile } from '@/types'; 6 + import { getGitHubApiDocsUrl } from '@/util/gitHubUtils'; 7 + import { parseRichTextIntoPlainText } from '@/util/stringUtils'; 8 + 9 + const getPathnameForApiFile = (name: string) => 10 + `api/${name.replace('.md', '.html')}`; 11 + 12 + // This is the Route Handler for the `GET` method which handles the request 13 + // for a digest and metadata of all API pages from the Node.js Website 14 + // @see https://nextjs.org/docs/app/building-your-application/routing/router-handlers 15 + export const GET = async () => { 16 + const gitHubApiResponse = await fetch(getGitHubApiDocsUrl('main')); 17 + 18 + return gitHubApiResponse.json().then((apiDocsFiles: Array<GitHubApiFile>) => { 19 + // maps over each api file and get the download_url, fetch the content and deflates it 20 + const mappedApiFiles = apiDocsFiles.map( 21 + async ({ name, path: filename, download_url }) => { 22 + const apiFileResponse = await fetch(download_url); 23 + 24 + // Retrieves the content as a raw text string 25 + const source = await apiFileResponse.text(); 26 + 27 + // Removes empty/blank lines or lines just with spaces and trims each line 28 + // from leading and trailing paddings/spaces 29 + const cleanedContent = parseRichTextIntoPlainText(source); 30 + 31 + const deflatedSource = deflateSync(cleanedContent).toString('base64'); 32 + 33 + return { 34 + filename, 35 + pathname: getPathnameForApiFile(name), 36 + content: deflatedSource, 37 + }; 38 + } 39 + ); 40 + 41 + return Promise.all(mappedApiFiles).then(Response.json); 42 + }); 43 + }; 44 + 45 + // This function generates the static paths that come from the dynamic segments 46 + // `[locale]/next-data/api-data/` and returns an array of all available static paths 47 + // This is used for ISR static validation and generation 48 + export const generateStaticParams = async () => [ 49 + { locale: defaultLocale.code }, 50 + ]; 51 + 52 + // Enforces that only the paths from `generateStaticParams` are allowed, giving 404 on the contrary 53 + // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams 54 + export const dynamicParams = false; 55 + 56 + // Enforces that this route is used as static rendering 57 + // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic 58 + export const dynamic = 'force-static'; 59 + 60 + // Ensures that this endpoint is invalidated and re-executed every X minutes 61 + // so that when new deployments happen, the data is refreshed 62 + // @see https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate 63 + export const revalidate = VERCEL_REVALIDATE;
+2 -2
app/[locale]/next-data/page-data/route.ts
··· 37 37 const deflatedSource = deflateSync(cleanedContent).toString('base64'); 38 38 39 39 // Returns metadata of each page available on the Website 40 - return { pathname, filename, title, description, content: deflatedSource }; 40 + return { filename, pathname, title, description, content: deflatedSource }; 41 41 }); 42 42 43 43 return Response.json(await Promise.all(availablePagesMetadata)); 44 44 }; 45 45 46 46 // This function generates the static paths that come from the dynamic segments 47 - // `[locale]/next-data/release-data/` and returns an array of all available static paths 47 + // `[locale]/next-data/page-data/` and returns an array of all available static paths 48 48 // This is used for ISR static validation and generation 49 49 export const generateStaticParams = async () => [ 50 50 { locale: defaultLocale.code },
+2 -2
components/Common/AvatarGroup/Avatar/index.stories.tsx
··· 1 1 import type { Meta as MetaObj, StoryObj } from '@storybook/react'; 2 2 3 3 import Avatar from '@/components/Common/AvatarGroup/Avatar'; 4 - import { githubProfileAvatarUrl } from '@/util/gitHubUtils'; 4 + import { getGitHubAvatarUrl } from '@/util/gitHubUtils'; 5 5 6 6 type Story = StoryObj<typeof Avatar>; 7 7 type Meta = MetaObj<typeof Avatar>; 8 8 9 9 export const Default: Story = { 10 10 args: { 11 - src: githubProfileAvatarUrl('ovflowd'), 11 + src: getGitHubAvatarUrl('ovflowd'), 12 12 alt: 'ovflowd', 13 13 }, 14 14 };
+2 -2
components/Common/AvatarGroup/index.stories.tsx
··· 1 1 import type { Meta as MetaObj, StoryObj } from '@storybook/react'; 2 2 3 3 import AvatarGroup from '@/components/Common/AvatarGroup'; 4 - import { githubProfileAvatarUrl } from '@/util/gitHubUtils'; 4 + import { getGitHubAvatarUrl } from '@/util/gitHubUtils'; 5 5 6 6 type Story = StoryObj<typeof AvatarGroup>; 7 7 type Meta = MetaObj<typeof AvatarGroup>; ··· 31 31 const defaultProps = { 32 32 avatars: [ 33 33 unknownAvatar, 34 - ...names.map(name => ({ src: githubProfileAvatarUrl(name), alt: name })), 34 + ...names.map(name => ({ src: getGitHubAvatarUrl(name), alt: name })), 35 35 ], 36 36 }; 37 37
+2 -2
components/Downloads/ChangelogModal/index.stories.tsx
··· 5 5 import ChangelogModal from '@/components/Downloads/ChangelogModal'; 6 6 import { MDXRenderer } from '@/components/mdxRenderer'; 7 7 import { compileMDX } from '@/next.mdx.compiler.mjs'; 8 - import { githubProfileAvatarUrl } from '@/util/gitHubUtils'; 8 + import { getGitHubAvatarUrl } from '@/util/gitHubUtils'; 9 9 10 10 type Story = StoryObj<typeof ChangelogModal>; 11 11 type Meta = MetaObj<typeof ChangelogModal>; ··· 182 182 heading: 'Node v18.17.0', 183 183 subheading: "2023-07-18, Version 18.17.0 'Hydrogen' (LTS), @danielleadams", 184 184 avatars: names.map(name => ({ 185 - src: githubProfileAvatarUrl(name), 185 + src: getGitHubAvatarUrl(name), 186 186 alt: name, 187 187 })), 188 188 children,
+2 -2
components/withMetaBar.tsx
··· 5 5 import GitHub from '@/components/Icons/Social/GitHub'; 6 6 import Link from '@/components/Link'; 7 7 import { useClientContext } from '@/hooks/server'; 8 - import { getGitHubEditPageUrl } from '@/util/gitHubUtils'; 8 + import { getGitHubBlobUrl } from '@/util/gitHubUtils'; 9 9 10 10 const DATE_FORMAT = { 11 11 month: 'short', ··· 29 29 'components.metabar.contribute': ( 30 30 <> 31 31 <GitHub className="fill-neutral-700 dark:fill-neutral-100" /> 32 - <Link href={getGitHubEditPageUrl(filename)}>Edit this page</Link> 32 + <Link href={getGitHubBlobUrl(filename)}>Edit this page</Link> 33 33 </> 34 34 ), 35 35 }}
+2 -2
next.mdx.compiler.mjs
··· 5 5 import { matter } from 'vfile-matter'; 6 6 7 7 import { NEXT_REHYPE_PLUGINS, NEXT_REMARK_PLUGINS } from './next.mdx.mjs'; 8 - import { createGitHubSlug } from './util/gitHubUtils'; 8 + import { createGitHubSlugger } from './util/gitHubUtils'; 9 9 10 10 // Defines the React Runtime Components 11 11 const reactRuntime = { Fragment, jsx, jsxs }; ··· 28 28 // cleaning the frontmatter to the source that is going to be parsed by the MDX Compiler 29 29 matter(source, { strip: true }); 30 30 31 - const slugger = createGitHubSlug(); 31 + const slugger = createGitHubSlugger(); 32 32 33 33 // This is a minimal MDX Compiler that is lightweight and only parses the MDX 34 34 const { default: MDXContent } = await evaluate(source, {
+11
types/github.ts
··· 1 + export interface GitHubApiFile { 2 + name: string; 3 + path: string; 4 + sha: string; 5 + size: number; 6 + url: string; 7 + html_url: string; 8 + git_url: string; 9 + download_url: string; 10 + type: 'file' | 'dir'; 11 + }
+1
types/index.ts
··· 8 8 export * from './releases'; 9 9 export * from './redirects'; 10 10 export * from './server'; 11 + export * from './github';
+6 -3
util/gitHubUtils.ts
··· 1 1 import GitHubSlugger from 'github-slugger'; 2 2 3 - export const githubProfileAvatarUrl = (username: string): string => 3 + export const getGitHubAvatarUrl = (username: string): string => 4 4 `https://avatars.githubusercontent.com/${username}`; 5 5 6 - export const createGitHubSlug = () => { 6 + export const createGitHubSlugger = () => { 7 7 const githubSlugger = new GitHubSlugger(); 8 8 9 9 return (text: string) => githubSlugger.slug(text); 10 10 }; 11 11 12 - export const getGitHubEditPageUrl = (filename: string) => 12 + export const getGitHubBlobUrl = (filename: string) => 13 13 `https://github.com/nodejs/nodejs.org/blob/main/pages/en/${filename}`; 14 + 15 + export const getGitHubApiDocsUrl = (ref: string) => 16 + `https://api.github.com/repos/nodejs/node/contents/doc/api?ref=${ref}`;