From 2030ab60cbb278a3c820f4aa6d997e506ba731de Mon Sep 17 00:00:00 2001
From: Claudio Wunder
Date: Mon, 12 Jun 2023 23:47:42 +0200
Subject: [PATCH 1/6] feat: introduce dynamic rendering of pages
---
.github/ISSUE_TEMPLATE/01-bug-report.yml | 16 +-
.github/ISSUE_TEMPLATE/02-feature-request.yml | 2 +-
.github/ISSUE_TEMPLATE/config.yml | 4 +-
.github/workflows/pull-request.yml | 4 +-
COLLABORATOR_GUIDE.md | 2 +-
components/Blog/BlogCard/index.stories.tsx | 2 +-
layouts/BlogIndexLayout.tsx | 2 +-
layouts/CategoryIndexLayout.tsx | 2 +-
middleware.ts | 1 -
.../generateNodeReleasesJson.mjs | 12 +-
.../generateWebsiteFeeds.mjs | 37 +-
.../next-data => next-data}/getBlogData.mjs | 76 ++--
next-data/getBlogIndexPages.mjs | 17 +
next-data/helpers.mjs | 65 ++++
next-data/index.mjs | 15 +
next.config.mjs | 5 +-
next.data.mjs | 13 +-
next.dynamic.mjs | 155 ++++++++
next.locales.mjs | 8 +-
package-lock.json | 357 ++++++++++++------
package.json | 6 +-
pages/[locale]/[...pathname].tsx | 58 +++
pages/[locale]/blog/[year].tsx | 66 ++++
providers/layoutProvider.tsx | 1 -
scripts/next-data/_helpers.mjs | 16 -
scripts/next-data/index.mjs | 3 -
theme.dynamic.tsx | 56 +++
theme.tsx | 19 +-
turbo.json | 29 +-
types/blog.ts | 2 +-
types/frontmatter.ts | 2 +-
types/index.ts | 1 -
types/middlewares.ts | 2 +-
33 files changed, 801 insertions(+), 255 deletions(-)
rename {scripts/next-data => next-data}/generateNodeReleasesJson.mjs (80%)
rename scripts/next-data/generatePreBuildFiles.mjs => next-data/generateWebsiteFeeds.mjs (53%)
rename {scripts/next-data => next-data}/getBlogData.mjs (74%)
create mode 100644 next-data/getBlogIndexPages.mjs
create mode 100644 next-data/helpers.mjs
create mode 100644 next-data/index.mjs
create mode 100644 next.dynamic.mjs
create mode 100644 pages/[locale]/[...pathname].tsx
create mode 100644 pages/[locale]/blog/[year].tsx
delete mode 100644 scripts/next-data/_helpers.mjs
delete mode 100644 scripts/next-data/index.mjs
create mode 100644 theme.dynamic.tsx
diff --git a/.github/ISSUE_TEMPLATE/01-bug-report.yml b/.github/ISSUE_TEMPLATE/01-bug-report.yml
index ac6f79f1f1870..281e5e0159968 100644
--- a/.github/ISSUE_TEMPLATE/01-bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/01-bug-report.yml
@@ -1,5 +1,5 @@
name: Report a Technical/Visual Issue on the Node.js Website
-description: "Is something not working as expected? Did you encounter a glitch or a bug with the Website?"
+description: 'Is something not working as expected? Did you encounter a glitch or a bug with the Website?'
labels: [bug]
body:
- type: markdown
@@ -11,36 +11,36 @@ body:
for us to fix it when you attach a screenshot as well.
- type: input
attributes:
- label: "URL:"
+ label: 'URL:'
description: The URL of the page you are reporting an issue on.
placeholder: https://nodejs.org/en/
validations:
required: true
- type: input
attributes:
- label: "Browser Name:"
+ label: 'Browser Name:'
description: What kind of browser are you using?
placeholder: Chrome
validations:
required: true
- type: input
attributes:
- label: "Browser Version:"
+ label: 'Browser Version:'
description: What version of browser are you using?
- placeholder: "103.0.5060.134"
+ placeholder: '103.0.5060.134'
validations:
required: true
- type: input
attributes:
- label: "Operating System:"
+ label: 'Operating System:'
description: What kind of operation system are you using
(Write it in full, with version number)?
- placeholder: "Windows 10, 21H2, 19044.1826"
+ placeholder: 'Windows 10, 21H2, 19044.1826'
validations:
required: true
- type: textarea
attributes:
- label: "How to reproduce the issue:"
+ label: 'How to reproduce the issue:'
placeholder: |
1. What I did.
2. What I expected to happen.
diff --git a/.github/ISSUE_TEMPLATE/02-feature-request.yml b/.github/ISSUE_TEMPLATE/02-feature-request.yml
index 4d6261251c17e..da59bf9dbc09b 100644
--- a/.github/ISSUE_TEMPLATE/02-feature-request.yml
+++ b/.github/ISSUE_TEMPLATE/02-feature-request.yml
@@ -1,5 +1,5 @@
name: Suggest a new feature or improvement for the Node.js Website
-description: "Do you have an idea or a suggestion and you want to share?"
+description: 'Do you have an idea or a suggestion and you want to share?'
labels: [feature request]
body:
- type: markdown
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 43d3c987a1ecf..5470b5cc09c8f 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -2,10 +2,10 @@ blank_issues_enabled: true
contact_links:
- name: Report an API Docs Issue on the Node.js Website
url: https://github.com/nodejs/node/issues/new?assignees=&labels=doc&template=3-api-ref-docs-problem.yml
- about: "Is something wrong with the API Docs? Did you face a bug with the API Docs?"
+ about: 'Is something wrong with the API Docs? Did you face a bug with the API Docs?'
- name: Report a Translation Issue on the Node.js Website
url: https://crowdin.com/project/nodejs-website
- about: "Is something wrong in a specific translation? Do you believe a language can get improved? Do you have suggestions?"
+ about: 'Is something wrong in a specific translation? Do you believe a language can get improved? Do you have suggestions?'
- name: Need help with Node.js?
url: https://github.com/nodejs/help/issues/
about: "Struggling with Node.js? You're not sure how to code?"
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index 6653ce82ea3da..fb71a7cacf97a 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -60,7 +60,7 @@ jobs:
os: [ubuntu-latest, windows-latest]
steps:
- - name: "Use GNU tar instead BSD tar"
+ - name: 'Use GNU tar instead BSD tar'
if: matrix.os == 'windows-latest'
shell: cmd
run: echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%"
@@ -122,7 +122,7 @@ jobs:
os: [ubuntu-latest, windows-latest]
steps:
- - name: "Use GNU tar instead BSD tar"
+ - name: 'Use GNU tar instead BSD tar'
if: matrix.os == 'windows-latest'
shell: cmd
run: echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%"
diff --git a/COLLABORATOR_GUIDE.md b/COLLABORATOR_GUIDE.md
index 1ac82b980077a..dbf94ad62a154 100644
--- a/COLLABORATOR_GUIDE.md
+++ b/COLLABORATOR_GUIDE.md
@@ -235,4 +235,4 @@ export default { component: NameOfComponent } as Meta;
[`react-intl`]: https://formatjs.io/docs/react-intl/
[Next.js]: https://nextjs.org/
[MDX]: https://mdxjs.com/
-[SCSS]: https://sass-lang.com/
\ No newline at end of file
+[SCSS]: https://sass-lang.com/
diff --git a/components/Blog/BlogCard/index.stories.tsx b/components/Blog/BlogCard/index.stories.tsx
index 5a7f848ab6eee..c8206347c964c 100644
--- a/components/Blog/BlogCard/index.stories.tsx
+++ b/components/Blog/BlogCard/index.stories.tsx
@@ -8,7 +8,7 @@ export const Default: Story = {
args: {
author: 'Bat Man',
category: 'category-mock',
- date: '2023-04-21 23:40:56.77',
+ date: new Date('2023-04-21 23:40:56.77'),
slug: '/blog/category-mock/sample-blog',
title: 'Sample Test Blog',
readingTime: '1 min read',
diff --git a/layouts/BlogIndexLayout.tsx b/layouts/BlogIndexLayout.tsx
index 2d09576d4c7bc..cb2acb65c463d 100644
--- a/layouts/BlogIndexLayout.tsx
+++ b/layouts/BlogIndexLayout.tsx
@@ -23,7 +23,7 @@ const BlogIndexLayout: FC = ({ children }) => {
{blogData?.posts.map(post => (
- {getTimeComponent(post.date, '%d %b')}
+ {getTimeComponent(post.date.toString(), '%d %b')}
{post.title}
))}
diff --git a/layouts/CategoryIndexLayout.tsx b/layouts/CategoryIndexLayout.tsx
index d35b9d95ee785..51d2d5f9e3e92 100644
--- a/layouts/CategoryIndexLayout.tsx
+++ b/layouts/CategoryIndexLayout.tsx
@@ -15,7 +15,7 @@ const CategoryIndexLayout: FC = ({ children }) => {
{blogData?.posts.map(post => (
- {getTimeComponent(post.date, '%d %b %y')}
+ {getTimeComponent(post.date.toString(), '%d %b %y')}
{post.title}
))}
diff --git a/middleware.ts b/middleware.ts
index f40ccfd00a123..0b8b543428e80 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -4,7 +4,6 @@ import createMiddleware from './next.middleware';
const nextMiddleware = createMiddleware(NextResponse);
const { middleware, matcher } = nextMiddleware([
- // eslint-disable-next-line @typescript-eslint/no-var-requires
require('./middlewares/detectLanguage').default,
]);
diff --git a/scripts/next-data/generateNodeReleasesJson.mjs b/next-data/generateNodeReleasesJson.mjs
similarity index 80%
rename from scripts/next-data/generateNodeReleasesJson.mjs
rename to next-data/generateNodeReleasesJson.mjs
index 676477498c28c..5a29a544bf9f0 100644
--- a/scripts/next-data/generateNodeReleasesJson.mjs
+++ b/next-data/generateNodeReleasesJson.mjs
@@ -1,13 +1,15 @@
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import nodevu from '@nodevu/core';
+import * as helpers from './helpers.mjs';
-import { getRelativePath } from './_helpers.mjs';
+// this allows us to get the current module working directory
+const __dirname = helpers.getRelativePath(import.meta.url);
-const __dirname = getRelativePath(import.meta.url);
-const jsonFilePath = join(__dirname, '../../public/node-releases-data.json');
+// this is the destination path for where the JSON file will be written
+const jsonFilePath = join(__dirname, '../public/node-releases-data.json');
-export const generateNodeReleasesJson = async () => {
+const generateNodeReleasesJson = async () => {
const nodevuOutput = await nodevu();
// Filter out those without documented support
@@ -49,3 +51,5 @@ export const generateNodeReleasesJson = async () => {
)
);
};
+
+export default generateNodeReleasesJson;
diff --git a/scripts/next-data/generatePreBuildFiles.mjs b/next-data/generateWebsiteFeeds.mjs
similarity index 53%
rename from scripts/next-data/generatePreBuildFiles.mjs
rename to next-data/generateWebsiteFeeds.mjs
index e3163a4f4f9c8..00ce909e251e5 100644
--- a/scripts/next-data/generatePreBuildFiles.mjs
+++ b/next-data/generateWebsiteFeeds.mjs
@@ -1,37 +1,28 @@
'use strict';
-// @TODO: This is a temporary hack until we migrate to the `nodejs/nodejs.dev` codebase
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { Feed } from 'feed';
-
-import { getRelativePath } from './_helpers.mjs';
+import * as helpers from './helpers.mjs';
// imports the global site config
-import siteConfig from '../../site.json' assert { type: 'json' };
+import siteConfig from '../site.json' assert { type: 'json' };
// this allows us to get the current module working directory
-const __dirname = getRelativePath(import.meta.url);
+const __dirname = helpers.getRelativePath(import.meta.url);
// gets the current blog path based on local module path
-const blogPath = join(__dirname, '../../pages/en/blog');
-
-const publicFeedPath = join(__dirname, '../../public/en/feed');
+const blogPath = join(__dirname, '../pages/en/blog');
-// creates a markdown file for the blog year
-const createBlogYearFile = year =>
- writeFile(
- join(blogPath, `year-${year}.md`),
- `---\nlayout: blog-index.hbs\ntitle: News from ${year}\npaginate: blog\n---\n`
- );
+const publicFeedPath = join(__dirname, '../public/en/feed');
-export const generateBlogYearPages = cachedBlogData =>
- cachedBlogData('', true)
- .then(({ blogData }) => blogData.posts.map(p => p.date.getFullYear()))
- .then(data => [...new Set(data)])
- .then(data => data.forEach(createBlogYearFile));
-
-export const generateWebsiteFeeds = cachedBlogData =>
+/**
+ * This method generates RSS website feeds based on the current website configuration
+ * and the current blog data that is available
+ *
+ * @param {ReturnType} cachedBlogData
+ */
+const generateWebsiteFeeds = cachedBlogData =>
siteConfig.rssFeeds.forEach(metadata => {
const feed = new Feed({
title: metadata.title,
@@ -54,7 +45,9 @@ export const generateWebsiteFeeds = cachedBlogData =>
date: new Date(post.date),
});
- cachedBlogData(blogCategoryOrAll)
+ cachedBlogData(blogCategoryOrAll, !metadata.blogCategory)
.then(({ blogData }) => blogData.posts.forEach(mapBlogPostToFeed))
.then(() => writeFile(join(publicFeedPath, metadata.file), feed.rss2()));
});
+
+export default generateWebsiteFeeds;
diff --git a/scripts/next-data/getBlogData.mjs b/next-data/getBlogData.mjs
similarity index 74%
rename from scripts/next-data/getBlogData.mjs
rename to next-data/getBlogData.mjs
index 5d4e56e64789a..b15a3b3b1c4d6 100644
--- a/scripts/next-data/getBlogData.mjs
+++ b/next-data/getBlogData.mjs
@@ -3,18 +3,16 @@
import { readFile, readdir } from 'node:fs/promises';
import { basename, extname, join } from 'node:path';
import graymatter from 'gray-matter';
-
-import {
- getDirectories,
- getMatchingRoutes,
- getRelativePath,
-} from './_helpers.mjs';
+import * as helpers from './helpers.mjs';
// this allows us to get the current module working directory
-const __dirname = getRelativePath(import.meta.url);
+const __dirname = helpers.getRelativePath(import.meta.url);
// gets the current blog path based on local module path
-const blogPath = join(__dirname, '../../pages/en/blog');
+const blogPath = join(__dirname, '../pages/en/blog');
+
+// retrieves the current year based on when the build happens
+const currentYear = new Date().getFullYear();
// gathers only the frontmatter fields that are relevant to us
const getMatter = name => content => {
@@ -33,23 +31,39 @@ const getPost = category => post =>
const getPosts = (category, posts) =>
posts.then(posts => Promise.all(posts.map(getPost(category))));
-const currentYear = new Date().getFullYear();
-
// Note.: This current structure is coupled to the current way how we do pagination and categories
// This will definitely change over time once we start migrating to the `nodejs/nodejs.dev` codebase
-export const getBlogData = () => {
- const blogCategories = getDirectories(blogPath).then(c =>
- c.map(s => [s, readdir(join(blogPath, s))])
- );
+// @deprecated - this will be removed once we migrate to the new blog structure from `nodejs.dev` codebase
+const getBlogData = () => {
+ const blogCategories = helpers
+ .getDirectories(__dirname, blogPath)
+ .then(c => c.map(s => [s, readdir(join(blogPath, s))]));
const categoriesPosts = blogCategories.then(c =>
c.map(([s, f]) => [s, getPosts(s, f)])
);
- return (route = '/', getAllAvailablePosts = false) => {
+ /**
+ * This method generates the blog post data based on route
+ * or if needed based on the category and pagination
+ *
+ * We also allow an override to get all available posts
+ * via the `getAllAvailablePosts` parameter
+ *
+ * @param {string} route the current next route
+ * @param {boolean} getAllAvailablePosts whether to get all available posts or not
+ * @return {Promise<{ blogData: import('../types').BlogData }>} the generated blog data
+ */
+ return async (route = '/', getAllAvailablePosts = false) => {
const [, , subDirectory, category = `year-${currentYear}`, blogPostSlug] =
route.split('/');
+ /**
+ * @param {import('../types').BlogPost[]} posts
+ * @param {boolean} hasNext if it has a next page
+ * @param {boolean} hasPrev if it has a previous page
+ * @return {{ blogData: import('../types').BlogData }} the generated blog data
+ */
const generatedBlogData = (posts, hasNext, hasPrev) => ({
blogData: {
posts: posts.sort((a, b) => b.date - a.date),
@@ -58,12 +72,26 @@ export const getBlogData = () => {
},
});
+ if (getAllAvailablePosts) {
+ return categoriesPosts.then(categories => {
+ // get only the post ingredient from the category array
+ const allPosts = categories.map(([, f]) => f);
+
+ const generatedBlog = posts => generatedBlogData(posts);
+
+ return Promise.all(allPosts)
+ .then(s => Promise.all(s.flat()))
+ .then(s => s.filter(p => p.date && p.file !== 'index.md'))
+ .then(generatedBlog);
+ });
+ }
+
// we don't want to generate blog data within a blog post
if (blogPostSlug && blogPostSlug.length > 0) {
return {};
}
- if (getMatchingRoutes(subDirectory, ['blog'])) {
+ if (helpers.getMatchingRoutes(subDirectory, ['blog'])) {
return categoriesPosts.then(categories => {
// yearly pagination posts (doesn't accept category pagination)
if (category && category.startsWith('year-')) {
@@ -108,20 +136,8 @@ export const getBlogData = () => {
});
}
- if (getAllAvailablePosts) {
- return categoriesPosts.then(categories => {
- // get only the post ingredient from the category array
- const allPosts = categories.map(([, f]) => f);
-
- const generatedBlog = posts => generatedBlogData(posts);
-
- return Promise.all(allPosts)
- .then(s => Promise.all(s.flat()))
- .then(s => s.filter(p => p.date && p.file !== 'index.md'))
- .then(generatedBlog);
- });
- }
-
return {};
};
};
+
+export default getBlogData;
diff --git a/next-data/getBlogIndexPages.mjs b/next-data/getBlogIndexPages.mjs
new file mode 100644
index 0000000000000..06d66fec4ad51
--- /dev/null
+++ b/next-data/getBlogIndexPages.mjs
@@ -0,0 +1,17 @@
+'use strict';
+
+/**
+ * This method gets all available blog posts and generates an index of
+ * all years that have blog posts. This approach uses the file system
+ * for checking all available posts.
+ *
+ * @param {ReturnType} cachedBlogData
+ * @return {Promise} the list of years that have blog posts
+ */
+const getBlogIndexPages = cachedBlogData =>
+ cachedBlogData('', true)
+ .then(({ blogData }) => blogData.posts.map(p => p.date.getFullYear()))
+ .then(data => data.map(year => year.toString()))
+ .then(data => [...new Set(data)]);
+
+export default getBlogIndexPages;
diff --git a/next-data/helpers.mjs b/next-data/helpers.mjs
new file mode 100644
index 0000000000000..1626db40a6a56
--- /dev/null
+++ b/next-data/helpers.mjs
@@ -0,0 +1,65 @@
+'use strict';
+
+import { existsSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { glob } from 'glob';
+
+export const getMatchingRoutes = (route = '', matches = []) =>
+ matches.some(match => route === match);
+
+/**
+ * This method is responsible for reading all immediate subdirectories of a directory
+ *
+ * @param {string} root the root directory to search from
+ * @param {string} cwd the current working directory
+ * @returns {Promise} a promise containing an array of directories
+ */
+export const getDirectories = async (root, cwd) => {
+ return glob('*', { root, cwd, withFileTypes: true })
+ .then(d => d.filter(e => e.isDirectory()))
+ .then(d => d.map(e => e.name));
+};
+
+/**
+ * This gets the relative path from `import.meta.url`
+ *
+ * @param {string} path the current import path
+ * @returns {string} the relative path from import
+ */
+export const getRelativePath = path => fileURLToPath(new URL('.', path));
+
+/**
+ * This method is responsible for retrieving a glob of all files that exist
+ * within a given language directory
+ *
+ * Note that we ignore the blog directory for static builds as otherwise generating
+ * that many pages would be too much for the build process to handle.
+ *
+ * @param {string} root the root directory to search from
+ * @param {string} cwd the given locale code
+ * @param {string[]} ignore an array of glob patterns to ignore
+ * @returns {Promise} a promise containing an array of paths
+ */
+export const getMarkdownFiles = async (root, cwd, ignore = []) => {
+ return glob('**/*.{md,mdx}', { root, cwd, ignore })
+ .then(files => files.map(file => file.replace(/(\/index)?\.mdx?$/, '')))
+ .then(files => files.filter(file => file.length));
+};
+
+/**
+ * This method is responsible for checking a combination
+ * of extensions in a given list and tests a list of extensions
+ * that could match that file.
+ *
+ * If no matching extension is found an empty string is returned
+ * which signals that the find was not found
+ *
+ * @param {string} filename the filename without extension/suffixes
+ * @param {string[]} extensions an array of suffixes to be tested
+ * @returns {string} the filename with the first matching extension
+ */
+export const checkFileExists = (filename, extensions) => {
+ const extension = extensions.find(e => existsSync(`${filename}${e}`));
+
+ return extension ? `${filename}${extension}` : '';
+};
diff --git a/next-data/index.mjs b/next-data/index.mjs
new file mode 100644
index 0000000000000..8c83c72cf5f10
--- /dev/null
+++ b/next-data/index.mjs
@@ -0,0 +1,15 @@
+'use strict';
+
+import getBlogData from './getBlogData.mjs';
+import getBlogIndexPages from './getBlogIndexPages.mjs';
+import generateWebsiteFeeds from './generateWebsiteFeeds.mjs';
+import generateNodeReleasesJson from './generateNodeReleasesJson.mjs';
+import * as helpers from './helpers.mjs';
+
+export {
+ getBlogData,
+ getBlogIndexPages,
+ generateWebsiteFeeds,
+ generateNodeReleasesJson,
+ helpers,
+};
diff --git a/next.config.mjs b/next.config.mjs
index 2d1c39a82a68b..8a67b3973e8c6 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -21,12 +21,12 @@ const withNextra = nextra({
// This is used for static/without a Node.js server hosting, such as on our
// legacy Website Build Environment on Node.js's DigitalOcean Droplet.
// Note.: Image optimization is also disabled through this process
-const enableStaticExport = process.env.NEXT_STATIC_EXPORT === 'true';
+export const enableStaticExport = process.env.NEXT_STATIC_EXPORT === 'true';
// Supports a manuall override of the base path of the website
// This is useful when running the deployment on a subdirectory
// of a domain, such as when hosted on GitHub Pages.
-const basePath = String(process.env.NEXT_BASE_PATH || '');
+export const basePath = String(process.env.NEXT_BASE_PATH || '');
export default withNextra({
basePath,
@@ -34,6 +34,7 @@ export default withNextra({
outputFileTracing: false,
distDir: enableStaticExport ? 'build' : '.next',
output: enableStaticExport ? 'export' : undefined,
+ experimental: { swcPlugins: [['next-superjson-plugin', {}]] },
images: { unoptimized: enableStaticExport },
eslint: { dirs: ['.'] },
i18n: null,
diff --git a/next.data.mjs b/next.data.mjs
index 1bec5e188d479..ba84945f6c042 100644
--- a/next.data.mjs
+++ b/next.data.mjs
@@ -1,24 +1,25 @@
-import * as nextData from './scripts/next-data/index.mjs';
+import * as nextData from './next-data/index.mjs';
+// gather blog data and caches it
const cachedBlogData = nextData.getBlogData();
+// generate the node.js releases json file
nextData.generateNodeReleasesJson();
-// generates pre-build files for blog year pages (pagination)
-nextData.generateBlogYearPages(cachedBlogData);
+// generate the website RSS feeds XML files
nextData.generateWebsiteFeeds(cachedBlogData);
const getNextData = async (content, { route }) => {
+ // retrieves a per-route set of blog data
+ // which should only include blog-related routes
const blogData = await cachedBlogData(route);
- const staticProps = { ...blogData };
-
return `
// add the mdx file content
${content}
export const getStaticProps = () => {
- return { props: ${JSON.stringify(staticProps)} };
+ return { props: ${JSON.stringify({ ...blogData })} };
}
`;
};
diff --git a/next.dynamic.mjs b/next.dynamic.mjs
new file mode 100644
index 0000000000000..8c1ef36b109b4
--- /dev/null
+++ b/next.dynamic.mjs
@@ -0,0 +1,155 @@
+import { join } from 'node:path';
+import { readFileSync } from 'node:fs';
+import remarkGfm from 'remark-gfm';
+import { serialize } from 'next-mdx-remote/serialize';
+import * as nextConfig from './next.config.mjs';
+import * as nextLocale from './next.locales.mjs';
+import * as nextData from './next-data/index.mjs';
+
+// this allows us to get the current module working directory
+const __dirname = nextData.helpers.getRelativePath(import.meta.url);
+
+// generates the current blog data
+const cachedBlogData = nextData.getBlogData();
+
+// during full static build we don't want to cover blog posts
+// as otherwise they will all get built as static pages during build time
+const ignoredPaths = nextConfig.enableStaticExport ? ['blog/**'] : [];
+
+// this retrieves all pages that are under the english locale directory
+// besides blog posts since they should not be localised at all
+const sourcePages = await nextData.helpers.getMarkdownFiles(
+ __dirname,
+ `pages/${nextLocale.defaultLocale.code}`,
+ ignoredPaths
+);
+
+/**
+ * This method is responsible for generating a spanning tree of all locales that
+ * do not have translated pages by checking the source default language and comparing
+ * with the other languages to determine which pages do not exist.
+ *
+ * The shape below is an example of a tuple entry from the resulting array
+ * as you can see the paths are split by (/) meaning pathnames such as /docs/guides
+ * become ["docs", "guides"] this is important as Next.js [...pathname] routing param
+ * requires the pathname to be an array of the constituitng parts.
+ * ['de', [["docs", "guides"], ["about"], ["download", "current"]]]
+ *
+ * @returns {Promise<[string, string[][]][]>} a promise containing an array of tuples
+ */
+export const getDynamicLocalizedPaths = async () => {
+ // this compares all available languages and which pages are missing
+ // by filtering out languages that exist on each localisation
+ // keeping only the non translated pages as the result
+ // the final result is converted to a Map by language code
+ const missingPagesByLanguage = nextLocale.nonDefaultLanguages.map(locale =>
+ nextData.helpers
+ .getMarkdownFiles(__dirname, `pages/${locale.code}`)
+ .then(files => sourcePages.filter(file => !files.includes(file)))
+ .then(files => files.map(file => file.split('/')))
+ .then(files => [locale.code, files])
+ );
+
+ return Promise.all(missingPagesByLanguage);
+};
+
+/**
+ * This method attempts to find a matching file in the fileystem provided originally
+ * by `getStaticPaths` and returns the file source and filename.
+ *
+ * Note that this method is safe as it is always provided by paths determined by the server
+ * that are non-localized pages that exist on the English locale.
+ *
+ * Hence we don't fallback for non-existing pages as it should never fall into this scenario.
+ * As a security measure we also check against `validFallbackFolders` to ensure that at the last scenario
+ * the base path comes from a valid base folder. Next.js will already protect against common attack vectors
+ * such as `/../../` on the URL pathname and other methodologies
+ *
+ * @param {string[]} paths the pathname string as an array (split by /)
+ * @param {string} locale the locale code to be used for the source file
+ * @returns {{ source: string, filename: string, pathname: string }} the source and filename
+ * @throws {Error} if the file does not exist, which should never happen
+ */
+export const getMarkdownFileSource = (
+ paths = [],
+ locale = nextLocale.defaultLocale.code
+) => {
+ // we want to transform the array of pieces of path into a string
+ const pathname = paths.join('/');
+
+ // gets the full pathname for the file (absolute path)
+ const filename = join(__dirname, 'pages', locale, pathname);
+
+ // We verify if the file exists within the list of allowed pages
+ // which prevents any malicious attempts to access non-allowed pages
+ // or other files that do not belong to the `sourcePages`
+ if (pathname.length && sourcePages.includes(pathname)) {
+ const filenameWithExtension = nextData.helpers.checkFileExists(filename, [
+ '/index.md',
+ '/index.mdx',
+ '.md',
+ '.mdx',
+ ]);
+
+ // Since we always will only read files that we know exist
+ // we don't need to handle a possibility of an error being thrown
+ // as any other case is if we don't have file system access and that should
+ // then be thrown and reported
+ const source = readFileSync(filenameWithExtension, 'utf8');
+
+ return { source, filename: filenameWithExtension, pathname };
+ }
+
+ return { source: '', filename: '', pathname };
+};
+
+/**
+ * This Method gathers the Markdown Source and the source filename
+ * and processes the data (parses the markdown) and generate props
+ * for the application to consume (`getStaticProps`)
+ *
+ * @param {string} source the Markdown source file
+ * @param {string} pathname the full path name
+ * @param {string} filename the filename
+ * @returns {Promise<{ notFound: boolean, props: any }>} the props for the page
+ */
+export const serializeDynamicPage = async (
+ source = '',
+ pathname = '',
+ filename = ''
+) => {
+ // by default a page is not found if there's no source or filename
+ const staticProps = { notFound: true, props: {} };
+
+ // We only attempt to serialize data if the `source` has content and `filename` has content
+ // otherwise we return a 404 since this means that it is not a valid file or a file we should care about
+ if (source.length && pathname.length) {
+ // This act as a MDX "compiler" but, lightweight. It parses the Markdown
+ // string source into a React Component tree, and then it serializes it
+ // it also supports Remark plugins, and MDX components
+ // Note.: We use the filename extension to define the mode of execution
+ // Note.: This functionality is unrelated to Nextra
+ const content = await serialize(source, {
+ parseFrontmatter: true,
+ mdxOptions: {
+ remarkPlugins: [remarkGfm],
+ format: filename.includes('.mdx') ? 'mdx' : 'md',
+ },
+ });
+
+ // this defines the basic props that should be passed back to the `DynamicPage` component
+ staticProps.props = { content };
+ staticProps.notFound = false;
+
+ // if the basePath is a blog page then we need extra static blog contextual data
+ // this will be gathered on demand as needed by the `cachedBlogData` function
+ if (pathname.startsWith('blog')) {
+ const blogProps = await cachedBlogData(`//${pathname}`);
+
+ // we add extra blog props to blog pages calculated by the `cachedBlogData`
+ staticProps.props = { ...staticProps.props, ...blogProps };
+ }
+ }
+
+ return staticProps;
+};
diff --git a/next.locales.mjs b/next.locales.mjs
index d00923a169408..8b0e670e4bdb7 100644
--- a/next.locales.mjs
+++ b/next.locales.mjs
@@ -14,6 +14,11 @@ const availableLocales = localeConfig.filter(locale => locale.enabled);
/** @type {import('./types').LocaleConfig} */
const defaultLocale = availableLocales.find(locale => locale.default);
+// This provides all available languages besides the default language
+const nonDefaultLanguages = availableLocales.filter(
+ locale => locale.code !== defaultLocale.code
+);
+
/**
* Retrieves the Current Locale from the given route or URL Query
*
@@ -46,8 +51,9 @@ const getCurrentTranslations = locale => ({
});
export {
- availableLocales,
defaultLocale,
+ availableLocales,
+ nonDefaultLanguages,
getCurrentLocale,
getCurrentTranslations,
};
diff --git a/package-lock.json b/package-lock.json
index 3980dfce7e4a1..ac925598fb681 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,9 +18,12 @@
"classnames": "^2.3.2",
"cross-env": "^7.0.3",
"framer-motion": "^10.12.16",
+ "glob": "^10.2.7",
"highlight.js": "^11.8.0",
"isomorphic-dompurify": "^1.5.0",
"next": "^13.4.3",
+ "next-mdx-remote": "^4.4.1",
+ "next-superjson-plugin": "^0.5.8",
"next-themes": "^0.2.1",
"nextra": "^2.6.1",
"prismjs": "^1.29.0",
@@ -33,8 +36,9 @@
"semver": "^7.5.1",
"sharp": "^0.32.1",
"strftime": "^0.10.1",
+ "superjson": "^1.12.3",
"swr": "^2.1.5",
- "turbo": "^1.9.9"
+ "turbo": "^1.10.3"
},
"devDependencies": {
"@storybook/addon-controls": "^7.0.17",
@@ -3320,7 +3324,6 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
@@ -3337,7 +3340,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
- "dev": true,
"engines": {
"node": ">=12"
},
@@ -3349,7 +3351,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
"engines": {
"node": ">=12"
},
@@ -3361,7 +3362,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
@@ -3378,7 +3378,6 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -3393,7 +3392,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
@@ -5335,44 +5333,6 @@
"balanced-match": "^1.0.0"
}
},
- "node_modules/@npmcli/map-workspaces/node_modules/foreground-child": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
- "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
- "dev": true,
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@npmcli/map-workspaces/node_modules/glob": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.0.tgz",
- "integrity": "sha512-AQ1/SB9HH0yCx1jXAT4vmCbTOPe5RQ+kCurjbel5xSCGhebumUv+GJZfa1rEqor3XIViqwSEmlkZCQD43RWrBg==",
- "dev": true,
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^2.0.3",
- "minimatch": "^9.0.1",
- "minipass": "^5.0.0 || ^6.0.2",
- "path-scurry": "^1.7.0"
- },
- "bin": {
- "glob": "dist/cjs/src/bin.js"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/@npmcli/map-workspaces/node_modules/minimatch": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.2.tgz",
@@ -5388,18 +5348,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/@npmcli/map-workspaces/node_modules/signal-exit": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz",
- "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==",
- "dev": true,
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/@npmcli/name-from-folder": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz",
@@ -5413,7 +5361,6 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
"optional": true,
"engines": {
"node": ">=14"
@@ -6348,6 +6295,65 @@
"integrity": "sha512-8egDX8dE50XyXWH6C6PRCNkTP106DuUrvdrednFouDSmCi7IOvrqr0frznfZaHifHH/3aq/7a7v9N4wdXMqhBQ==",
"dev": true
},
+ "node_modules/@storybook/core-common/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@storybook/core-common/node_modules/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@storybook/core-common/node_modules/glob-promise": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-6.0.3.tgz",
+ "integrity": "sha512-m+kxywR5j/2Z2V9zvHKfwwL5Gp7gIFEBX+deTB9w2lJB+wSuw9kcS43VfvTAMk8TXL5JCl/cCjsR+tgNVspGyA==",
+ "dev": true,
+ "dependencies": {
+ "@types/glob": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/ahmadnassri"
+ },
+ "peerDependencies": {
+ "glob": "^8.0.3"
+ }
+ },
+ "node_modules/@storybook/core-common/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@storybook/core-events": {
"version": "7.0.23",
"resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.0.23.tgz",
@@ -7310,6 +7316,15 @@
"@babel/core": "^7.0.0"
}
},
+ "node_modules/@storybook/test-runner/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
"node_modules/@storybook/test-runner/node_modules/camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
@@ -7347,6 +7362,37 @@
"node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
}
},
+ "node_modules/@storybook/test-runner/node_modules/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@storybook/test-runner/node_modules/glob/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@storybook/test-runner/node_modules/jest": {
"version": "28.1.3",
"resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz",
@@ -9257,7 +9303,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -9271,7 +9316,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -9883,8 +9927,7 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base64-js": {
"version": "1.5.1",
@@ -11224,6 +11267,20 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"dev": true
},
+ "node_modules/copy-anything": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
+ "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/core-js-compat": {
"version": "3.31.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz",
@@ -12802,8 +12859,7 @@
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/ee-first": {
"version": "1.1.1",
@@ -12873,8 +12929,7 @@
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/emojis-list": {
"version": "3.0.0",
@@ -15108,19 +15163,21 @@
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
},
"node_modules/glob": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
- "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
- "dev": true,
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.0.tgz",
+ "integrity": "sha512-AQ1/SB9HH0yCx1jXAT4vmCbTOPe5RQ+kCurjbel5xSCGhebumUv+GJZfa1rEqor3XIViqwSEmlkZCQD43RWrBg==",
"dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^5.0.1",
- "once": "^1.3.0"
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^2.0.3",
+ "minimatch": "^9.0.1",
+ "minipass": "^5.0.0 || ^6.0.2",
+ "path-scurry": "^1.7.0"
+ },
+ "bin": {
+ "glob": "dist/cjs/src/bin.js"
},
"engines": {
- "node": ">=12"
+ "node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -15138,25 +15195,6 @@
"node": ">=10.13.0"
}
},
- "node_modules/glob-promise": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-6.0.3.tgz",
- "integrity": "sha512-m+kxywR5j/2Z2V9zvHKfwwL5Gp7gIFEBX+deTB9w2lJB+wSuw9kcS43VfvTAMk8TXL5JCl/cCjsR+tgNVspGyA==",
- "dev": true,
- "dependencies": {
- "@types/glob": "^8.0.0"
- },
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "type": "individual",
- "url": "https://github.com/sponsors/ahmadnassri"
- },
- "peerDependencies": {
- "glob": "^8.0.3"
- }
- },
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
@@ -15166,21 +15204,48 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
+ "node_modules/glob/node_modules/foreground-child": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+ "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/glob/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
- "dev": true,
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.2.tgz",
+ "integrity": "sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob/node_modules/signal-exit": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz",
+ "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/global-modules": {
@@ -16484,7 +16549,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -16857,6 +16921,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-what": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.15.tgz",
+ "integrity": "sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==",
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
@@ -17056,7 +17131,6 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz",
"integrity": "sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==",
- "dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
@@ -22428,7 +22502,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -22639,6 +22712,18 @@
"next": "*"
}
},
+ "node_modules/next-superjson-plugin": {
+ "version": "0.5.8",
+ "resolved": "https://registry.npmjs.org/next-superjson-plugin/-/next-superjson-plugin-0.5.8.tgz",
+ "integrity": "sha512-a6znMUzHWRAaxmQNHkgANJORdI0gjSC0JvsxRRKba6vb4BJRs/i2KpwEg/s91JYDhHsnZS+tkeb3MDCJWXJY4w==",
+ "dependencies": {
+ "hoist-non-react-statics": "^3.3.2"
+ },
+ "peerDependencies": {
+ "next": "^13",
+ "superjson": "^1"
+ }
+ },
"node_modules/next-themes": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz",
@@ -23728,7 +23813,6 @@
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.2.tgz",
"integrity": "sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==",
- "dev": true,
"dependencies": {
"lru-cache": "^9.1.1",
"minipass": "^5.0.0 || ^6.0.2"
@@ -23744,7 +23828,6 @@
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz",
"integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==",
- "dev": true,
"engines": {
"node": "14 || >=16.14"
}
@@ -27618,7 +27701,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -27633,7 +27715,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -27646,14 +27727,12 @@
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/string-width/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/string.prototype.matchall": {
"version": "4.0.8",
@@ -27745,7 +27824,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -27758,7 +27836,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -28070,6 +28147,17 @@
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
},
+ "node_modules/superjson": {
+ "version": "1.12.4",
+ "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.4.tgz",
+ "integrity": "sha512-vkpPQAxdCg9SLfPv5GPC5fnGrui/WryktoN9O5+Zif/14QIMjw+RITf/5LbBh+9QpBFb3KNvJth+puz2H8o6GQ==",
+ "dependencies": {
+ "copy-anything": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -29238,6 +29326,15 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/unified-engine/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
"node_modules/unified-engine/node_modules/concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
@@ -29253,6 +29350,25 @@
"typedarray": "^0.0.6"
}
},
+ "node_modules/unified-engine/node_modules/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/unified-engine/node_modules/is-plain-obj": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
@@ -29274,6 +29390,18 @@
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
+ "node_modules/unified-engine/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/unified-engine/node_modules/parse-json": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-6.0.2.tgz",
@@ -30471,7 +30599,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
diff --git a/package.json b/package.json
index 5bc8c68d9ed5f..5242dad2d21f9 100644
--- a/package.json
+++ b/package.json
@@ -45,9 +45,12 @@
"classnames": "^2.3.2",
"cross-env": "^7.0.3",
"framer-motion": "^10.12.16",
+ "glob": "^10.2.7",
"highlight.js": "^11.8.0",
"isomorphic-dompurify": "^1.5.0",
"next": "^13.4.3",
+ "next-mdx-remote": "^4.4.1",
+ "next-superjson-plugin": "^0.5.8",
"next-themes": "^0.2.1",
"nextra": "^2.6.1",
"prismjs": "^1.29.0",
@@ -60,8 +63,9 @@
"semver": "^7.5.1",
"sharp": "^0.32.1",
"strftime": "^0.10.1",
+ "superjson": "^1.12.3",
"swr": "^2.1.5",
- "turbo": "^1.9.9"
+ "turbo": "^1.10.3"
},
"devDependencies": {
"@storybook/addon-controls": "^7.0.17",
diff --git a/pages/[locale]/[...pathname].tsx b/pages/[locale]/[...pathname].tsx
new file mode 100644
index 0000000000000..716820a6aa227
--- /dev/null
+++ b/pages/[locale]/[...pathname].tsx
@@ -0,0 +1,58 @@
+import DynamicTheme from '../../theme.dynamic';
+import * as nextConfig from '../../next.config.mjs';
+import * as nextDynamic from '../../next.dynamic.mjs';
+import type { GetStaticPaths, GetStaticProps } from 'next';
+import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
+
+type StaticParams = { pathname: string[]; locale: string };
+type StaticProps = { content: MDXRemoteSerializeResult };
+
+// This method receives the props from `getStaticProps` and renders/builds the Markdown
+// content on demand by loading the file on the server-side and serializing the Markdown/MDX content
+export const getStaticProps: GetStaticProps<
+ StaticProps,
+ StaticParams
+> = async ({ params = { pathname: [], locale: 'en' } }) => {
+ // We retrieve the source of the Markdown file by doing an educated guess
+ // of what possible files could be the source of the page, since the extension
+ // context is lost from `getStaticProps` as a limitation of Next.js itself
+ const { source, filename, pathname } = nextDynamic.getMarkdownFileSource(
+ params.pathname
+ );
+
+ // This parses the actual Markdown content and returns a full set of props
+ // to be passed to the base page (`DynamicPage`) which will render the Markdown
+ const staticProps = await nextDynamic.serializeDynamicPage(
+ source,
+ pathname,
+ filename
+ );
+
+ // We add the extra `params` to the props as they're used within the `DynamicPage`
+ return staticProps;
+};
+
+// This method is called once during build-time and retrieves a full path
+// of all pages on each locale that were not translated yet.
+// allowing us to generate localized pages with only the content fallbacking to English
+// Note.: during full ISR blog pages are also supported as they do not increase weight on the build time
+// but on static-mode the blog pages will not be generated/included on the build.
+export const getStaticPaths: GetStaticPaths = async () => {
+ // During full-static builds we want to generate all minimal required pages
+ // as static exports cannot leverage from ISR (Incremental Static Builds)
+ // whereas on non-static exports we don't need to build these paths at build-time
+ // and we can fully leverage from ISR builds without the need of pre-building these static pages
+ if (nextConfig.enableStaticExport) {
+ const nonLocalizedPages = await nextDynamic.getDynamicLocalizedPaths();
+
+ const dynamicPathsByLocale = nonLocalizedPages.map(([locale, pages]) =>
+ pages.map(pathname => ({ params: { locale, pathname } }))
+ );
+
+ return { paths: dynamicPathsByLocale.flat(), fallback: 'blocking' };
+ }
+
+ return { paths: [], fallback: 'blocking' };
+};
+
+export default DynamicTheme;
diff --git a/pages/[locale]/blog/[year].tsx b/pages/[locale]/blog/[year].tsx
new file mode 100644
index 0000000000000..e15648e426e79
--- /dev/null
+++ b/pages/[locale]/blog/[year].tsx
@@ -0,0 +1,66 @@
+import DynamicTheme from '../../../theme.dynamic';
+import * as nextConfig from '../../../next.config.mjs';
+import * as nextDynamic from '../../../next.dynamic.mjs';
+import * as nextLocales from '../../../next.locales.mjs';
+import * as nextData from '../../../next-data/index.mjs';
+import type { GetStaticPaths, GetStaticProps } from 'next';
+import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
+
+// generates the current blog data
+const cachedBlogData = nextData.getBlogData();
+
+// get all the available years from the blog data
+const availableYears = await nextData.getBlogIndexPages(cachedBlogData);
+
+type StaticParams = { year: string; locale: string };
+type StaticProps = { content: MDXRemoteSerializeResult };
+
+// This method generates the Markdown page for the blog index for a given year
+// this should be used only for getting the index page for a specific year of the blog
+// Note.: this logic is heavily based on the current way how pagination on the blog is done
+// and might be removed at any given moment if the pagination logic changes
+export const getStaticProps: GetStaticProps<
+ StaticProps,
+ StaticParams
+> = async ({ params = { year: '' } }) => {
+ // We replace the `year-` to get the actual year number that we need
+ const yearNumber = params.year.replace('year-', '');
+
+ // We generate the source of the Markdown file based on how a "year" page should look like
+ const source = availableYears.includes(yearNumber)
+ ? `---\nlayout: blog-index.hbs\ntitle: News from ${yearNumber}\npaginate: blog\n---\n`
+ : '';
+
+ // This parses the actual Markdown content and returns a full set of props
+ // to be passed to the base page (`DynamicPage`) which will render the Markdown
+ const staticProps = await nextDynamic.serializeDynamicPage(
+ source,
+ `blog/${params.year}`,
+ `blog/${params.year}.md`
+ );
+
+ // We add the extra `params` to the props as they're used within the `DynamicPage`
+ return staticProps;
+};
+
+// This method generates the blog year paths to be generated at build time
+// Note that this logic is actually locale independent and we do not care about
+// the value for the locale prop as it's not used anywhere
+export const getStaticPaths: GetStaticPaths = async () => {
+ if (nextConfig.enableStaticExport) {
+ // This maps all available blog index pages by each available locale
+ // since we need to provide the locale piece from the URL. We need to do this
+ // for the full-static version of the website
+ const dynamicPaths = nextLocales.availableLocales.map(locale =>
+ availableYears.map(year => ({
+ params: { year: `year-${year}`, locale: locale.code },
+ }))
+ );
+
+ return { paths: dynamicPaths.flat(), fallback: 'blocking' };
+ }
+
+ return { paths: [], fallback: 'blocking' };
+};
+
+export default DynamicTheme;
diff --git a/providers/layoutProvider.tsx b/providers/layoutProvider.tsx
index 777c557e78548..b2704bc93c32f 100644
--- a/providers/layoutProvider.tsx
+++ b/providers/layoutProvider.tsx
@@ -12,7 +12,6 @@ import DownloadReleasesLayout from '../layouts/DownloadReleasesLayout';
import IndexLayout from '../layouts/IndexLayout';
import type { PageOpts } from 'nextra';
import type { PropsWithChildren } from 'react';
-
import type { LegacyLayouts, NextraAppProps } from '../types';
type LayoutProviderProps = PropsWithChildren<{
diff --git a/scripts/next-data/_helpers.mjs b/scripts/next-data/_helpers.mjs
deleted file mode 100644
index 0ac6649dc51d2..0000000000000
--- a/scripts/next-data/_helpers.mjs
+++ /dev/null
@@ -1,16 +0,0 @@
-'use strict';
-
-import { readdir } from 'node:fs/promises';
-import { fileURLToPath } from 'url';
-
-export const getMatchingRoutes = (route = '', matches = []) =>
- matches.some(match => route === match);
-
-// reads all immediate subdirectories of a directory
-export const getDirectories = source => {
- return readdir(source, { withFileTypes: true }).then(d =>
- d.filter(e => e.isDirectory()).map(e => e.name)
- );
-};
-
-export const getRelativePath = path => fileURLToPath(new URL('.', path));
diff --git a/scripts/next-data/index.mjs b/scripts/next-data/index.mjs
deleted file mode 100644
index c3e02087715ea..0000000000000
--- a/scripts/next-data/index.mjs
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './generatePreBuildFiles.mjs';
-export * from './generateNodeReleasesJson.mjs';
-export * from './getBlogData.mjs';
diff --git a/theme.dynamic.tsx b/theme.dynamic.tsx
new file mode 100644
index 0000000000000..d6ecbdfe597d3
--- /dev/null
+++ b/theme.dynamic.tsx
@@ -0,0 +1,56 @@
+import { useMemo } from 'react';
+import { MDXRemote } from 'next-mdx-remote';
+import NextraTheme from './theme';
+import type { FC } from 'react';
+import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
+import type { AppProps, LegacyFrontMatter } from './types';
+
+// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+interface DynamicThemeProps extends AppProps {
+ content: MDXRemoteSerializeResult;
+}
+
+// This is the engine for Incremental Generated Static Pages
+// this component supports rendering remote-Markdown content
+// loaded from the server-side/static-build
+// we simulate the props that the NextraTheme receives as much as possible
+// the _page.mdx is already wrapped surrounding the tree of children components
+// extra props might come from extra context hence why we spread the props
+const DynamicTheme: FC = ({ content, ...props }) => {
+ const frontMatter = content.frontmatter as LegacyFrontMatter;
+
+ // this configures the pageOpts passed down to the NextraTheme which are used
+ // for resolving the layout and other intrinsic configuration of the Theme based on Frontmatter
+ const nextraThemeProps = useMemo(
+ () => ({
+ // We're not sure what needs to be forwarded here, but afaik since we use a Custom Theme,
+ // we don't really have any Theme Config to forward. This seems to be used only for native
+ // (bundled) themes from Nextra to allow the end-user to customize the theme.
+ // but this option seems to be unused for custom themes. It seems like we could use it if we wanted
+ // to customize things directly on `theme.tsx` as this themeConfig comes from `withNextra` on `next.config.mjs`
+ themeConfig: null,
+ // We simply forward the `pageProps` we receive from here to NextraTheme
+ pageProps: props,
+ // We create a replica of what Nextra would be passing down to the Theme's `pageOpts`
+ // Sadly for dynamic pages we're unable (yet) to compute the headings.
+ // `pageMap`, `filePath` and `route` are unused by us and are only used by Nextra's native themes
+ pageOpts: {
+ frontMatter,
+ headings: [],
+ pageMap: [],
+ title: frontMatter.title || 'Node.js',
+ filePath: '',
+ route: '',
+ },
+ }),
+ [frontMatter, props]
+ );
+
+ return (
+
+
+
+ );
+};
+
+export default DynamicTheme;
diff --git a/theme.tsx b/theme.tsx
index 4235eee19c1c2..fdf2b25c75636 100644
--- a/theme.tsx
+++ b/theme.tsx
@@ -7,14 +7,10 @@ import NodeApiVersionLinks from './components/Docs/NodeApiVersionLinks';
import { LayoutProvider } from './providers/layoutProvider';
import { useRouter } from './hooks/useRouter';
import type { FC, PropsWithChildren } from 'react';
-import type { NextraThemeLayoutProps } from 'nextra';
+import type { NextraThemeLayoutProps, PageOpts } from 'nextra';
import type { MDXComponents } from 'mdx/types';
import type { LegacyFrontMatter } from './types';
-type LayoutProps = PropsWithChildren<{
- pageOpts: NextraThemeLayoutProps['pageOpts'];
-}>;
-
const mdxComponents: MDXComponents = {
NodeApiVersionLinks: NodeApiVersionLinks,
h1: props => ,
@@ -26,7 +22,7 @@ const mdxComponents: MDXComponents = {
blockquote: ({ children }) => {children}
,
};
-const Content: FC = ({ children }) => {
+const Content: FC = ({ children }) => {
const { asPath } = useRouter();
// Re-highlights the pages on route change
@@ -42,17 +38,16 @@ const Content: FC = ({ children }) => {
};
// @TODO: Nextra should provide better customization to FrontMatter Props
-type ThemeProps = {
- pageOpts: Omit & {
- frontMatter: LegacyFrontMatter;
- };
-} & NextraThemeLayoutProps;
+// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+interface ThemeProps extends NextraThemeLayoutProps {
+ pageOpts: PageOpts;
+}
const Theme: FC = ({ pageOpts, pageProps, children }) => (
<>
- {children}
+ {children}
>
);
diff --git a/turbo.json b/turbo.json
index bd8e4ec424e8b..acb39686b00f7 100644
--- a/turbo.json
+++ b/turbo.json
@@ -17,11 +17,11 @@
},
"lint": {
"inputs": [
- "{components,hooks,layouts,providers,types,util}/**/*.{ts,tsx}",
+ "{components,hooks,layouts,providers,types,util,middlewares}/**/*.{ts,tsx}",
"{components,layouts,pages,styles}/**/*.{css,sass,scss}",
+ "{scripts,next-data}/**/*.{js,mjs}",
"*.{md,mdx,json,ts,tsx,mjs,js}",
"pages/**/*.{tsx,ts,mdx,md}",
- "scripts/**/*.{js,mjs}",
"i18n/**/.json",
".eslintrc",
".eslintignore",
@@ -49,16 +49,11 @@
},
"build": {
"inputs": [
- "{components,hooks,layouts,providers,types,util}/**/*.{ts,tsx}",
+ "{components,hooks,layouts,providers,types,util,middlewares}/**/*.{ts,tsx}",
"{components,layouts,pages,styles}/**/*.{css,sass,scss}",
- "pages/**/*.{tsx,ts,mdx,md}",
- "scripts/**/*.{js,mjs}",
- "tsconfig.json",
- "next.config.mjs",
- "next.data.mjs",
- "next-sitemap.config.mjs",
- "theme.tsx",
- "middleware.ts"
+ "{scripts,next-data}/**/*.{js,mjs}",
+ "*.{md,mdx,json,ts,tsx,mjs,js}",
+ "pages/**/*.{tsx,ts,mdx,md}"
],
"outputs": [".next/**", "!.next/cache/**"]
},
@@ -67,16 +62,10 @@
},
"deploy": {
"inputs": [
- "{components,hooks,layouts,providers,types,util}/**/*.{ts,tsx}",
+ "{components,hooks,layouts,providers,types,util,middlewares}/**/*.{ts,tsx}",
"{components,layouts,pages,styles}/**/*.{css,sass,scss}",
- "pages/**/*.{tsx,ts,mdx,md}",
- "scripts/**/*.{js,mjs}",
- "tsconfig.json",
- "next.config.mjs",
- "next.data.mjs",
- "next-sitemap.config.mjs",
- "theme.tsx",
- "middleware.ts"
+ "*.{md,mdx,json,ts,tsx,mjs,js}",
+ "pages/**/*.{tsx,ts,mdx,md}"
],
"outputs": [".next/**", "!.next/cache/**"]
},
diff --git a/types/blog.ts b/types/blog.ts
index 7ab79b7b49482..85847e5b21042 100644
--- a/types/blog.ts
+++ b/types/blog.ts
@@ -1,7 +1,7 @@
export interface BlogPost {
title: string;
author?: string;
- date: string;
+ date: Date;
category: string;
slug: string;
readingTime?: string; // TODO: verify this works when implementing blog
diff --git a/types/frontmatter.ts b/types/frontmatter.ts
index 818e41c817fc8..e2a53b4935a8a 100644
--- a/types/frontmatter.ts
+++ b/types/frontmatter.ts
@@ -4,7 +4,7 @@ import type { LegacyLayouts } from './layouts';
// this is going to be done via a script that replaces layouts
// Note.: The current legacy pages have other frontmatter entries but they're irrelevant
export interface LegacyFrontMatter {
- layout: LegacyLayouts;
+ layout?: LegacyLayouts;
title?: string;
robots?: string;
labels?: Record;
diff --git a/types/index.ts b/types/index.ts
index 9dd3239306795..45f98c9333080 100644
--- a/types/index.ts
+++ b/types/index.ts
@@ -17,7 +17,6 @@ export * from './middlewares';
export interface AppProps {
blogData?: BlogData;
- statusCode?: number;
}
export type NextraAppProps = DefaultAppProps;
diff --git a/types/middlewares.ts b/types/middlewares.ts
index 3d3734d5fedd3..e29786c5a40a1 100644
--- a/types/middlewares.ts
+++ b/types/middlewares.ts
@@ -13,5 +13,5 @@ export type CustomMiddleware = {
locale: NextMiddlewareLocale
) => Promise;
matcher: (request: NextRequest, locale: NextMiddlewareLocale) => boolean;
- routes: string[];
+ routes: (string | RegExp)[];
};
From 6fda9dc16a56b9b77627bc585a33510466c0f807 Mon Sep 17 00:00:00 2001
From: Claudio Wunder
Date: Mon, 26 Jun 2023 21:21:59 +0200
Subject: [PATCH 2/6] major: full dynamic pages and plain next.js (#2)
---
.gitignore | 1 -
.husky/pre-commit | 6 +
.storybook/preview.tsx | 19 +-
CONTRIBUTING.md | 110 +-
README.md | 4 +-
app/en/feed/[feed]/route.ts | 21 +
components/Blog/BlogCard/index.stories.tsx | 2 +-
components/Blog/BlogCard/index.tsx | 2 +-
.../Downloads/PrimaryDownloadMatrix.tsx | 6 +-
.../Downloads/SecondaryDownloadMatrix.tsx | 7 +-
components/Home/HomeDownloadButton.tsx | 17 +-
components/Pagination.tsx | 6 +-
global.d.ts | 3 -
hooks/useBlogData.ts | 42 +
hooks/useLayoutContext.ts | 4 +
hooks/useNavigation.tsx | 10 +-
hooks/useNextraContext.ts | 8 -
layouts/BlogIndexLayout.tsx | 30 +-
layouts/BlogPostLayout.tsx | 7 +-
layouts/CategoryIndexLayout.tsx | 15 +-
layouts/DownloadCurrentLayout.tsx | 6 +-
layouts/DownloadLayout.tsx | 6 +-
layouts/DownloadReleasesLayout.tsx | 7 +-
layouts/IndexLayout.tsx | 11 +-
layouts/New/.gitkeep | 1 -
next-data/generateBlogPostsData.mjs | 80 +
next-data/generateNodeReleasesJson.mjs | 8 +-
next-data/generateWebsiteFeeds.mjs | 73 +-
next-data/getBlogData.mjs | 143 --
next-data/getBlogIndexPages.mjs | 17 -
next-data/helpers.mjs | 33 +-
next-data/index.mjs | 6 +-
next-env.d.ts | 1 +
next-sitemap.config.mjs | 12 +-
next.app.tsx | 14 +-
next.config.mjs | 58 +-
next.constants.mjs | 102 +
next.data.mjs | 27 -
next.dynamic.mjs | 184 +-
next.json.mjs | 15 +
next.locales.mjs | 13 +-
package-lock.json | 1642 +----------------
package.json | 6 +-
pages/404.mdx | 14 -
pages/404.tsx | 15 +
pages/[...path].tsx | 89 +
pages/[locale]/[...pathname].tsx | 58 -
pages/[locale]/blog/[year].tsx | 66 -
pages/_app.mdx | 21 -
pages/_app.tsx | 17 +
pages/_document.tsx | 11 +-
pages/en/blog/pagination.md | 5 +
providers/blogDataProvider.tsx | 16 +
providers/layoutProvider.tsx | 39 +-
providers/mdxProvider.tsx | 34 +
providers/siteProvider.tsx | 8 +-
public/blog-posts-data.json | 1 +
public/en/feed/.gitkeep | 1 -
site.json | 4 +-
theme.dynamic.tsx | 56 -
theme.tsx | 62 +-
tsconfig.json | 9 +-
turbo.json | 14 +-
types/blog.ts | 10 +-
types/index.ts | 10 -
types/middlewares.ts | 7 +
types/navigation.ts | 12 +-
util/openSans.ts | 9 -
68 files changed, 917 insertions(+), 2456 deletions(-)
create mode 100644 app/en/feed/[feed]/route.ts
create mode 100644 hooks/useBlogData.ts
create mode 100644 hooks/useLayoutContext.ts
delete mode 100644 hooks/useNextraContext.ts
delete mode 100644 layouts/New/.gitkeep
create mode 100644 next-data/generateBlogPostsData.mjs
delete mode 100644 next-data/getBlogData.mjs
delete mode 100644 next-data/getBlogIndexPages.mjs
create mode 100644 next.constants.mjs
delete mode 100644 next.data.mjs
create mode 100644 next.json.mjs
delete mode 100644 pages/404.mdx
create mode 100644 pages/404.tsx
create mode 100644 pages/[...path].tsx
delete mode 100644 pages/[locale]/[...pathname].tsx
delete mode 100644 pages/[locale]/blog/[year].tsx
delete mode 100644 pages/_app.mdx
create mode 100644 pages/_app.tsx
create mode 100644 pages/en/blog/pagination.md
create mode 100644 providers/blogDataProvider.tsx
create mode 100644 providers/mdxProvider.tsx
create mode 100644 public/blog-posts-data.json
delete mode 100644 public/en/feed/.gitkeep
delete mode 100644 theme.dynamic.tsx
delete mode 100644 util/openSans.ts
diff --git a/.gitignore b/.gitignore
index aa56284814f58..f8f1a72df5ba7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,7 +16,6 @@ build
public/robots.txt
public/sitemap.xml
public/en/feed/*.xml
-pages/en/blog/year-[0-9][0-9][0-9][0-9].md
# Jest
coverage
diff --git a/.husky/pre-commit b/.husky/pre-commit
index c306023e82ea8..eedf3761036e3 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -3,6 +3,12 @@
DIR=$(cd `dirname $0` && pwd -P)
+# resets the node release data file
echo "[]" > $DIR/../public/node-releases-data.json
+# resets the blog post data file
+echo "{\"pagination\": [],\"categories\": [],\"posts\": []}" > $DIR/../public/blog-posts-data.json
+
+# adds these changes to be part of the current commit
git add --sparse $DIR/../public/node-releases-data.json
+git add --sparse $DIR/../public/blog-posts-data.json
diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
index 914290f2a32df..056e6441eee63 100644
--- a/.storybook/preview.tsx
+++ b/.storybook/preview.tsx
@@ -1,10 +1,7 @@
import type { Preview } from '@storybook/react';
import NextImage from 'next/image';
-import { ThemeProvider } from 'next-themes';
-import { NodeReleasesProvider } from '../providers/nodeReleasesProvider';
-import { LocaleProvider } from '../providers/localeProvider';
import { openSans } from '../util/nextFonts';
-import BaseApp, { setAppFont } from '../next.app';
+import BaseApp, { setAppFonts } from '../next.app';
import '../styles/index.scss';
@@ -26,20 +23,14 @@ const preview: Preview = {
},
};
-setAppFont(openSans.style.fontFamily);
+setAppFonts([openSans.style.fontFamily]);
export const decorators = [
Story => (
-
-
-
-
-
-
-
-
-
+
+
+
),
];
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5113880bebc76..a7bf655135ad2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -32,24 +32,22 @@ Note that regular contributors do not need to become "Collaborators". Any contri
is a formality that comes with obligations.
If you're an active contributor seeking to become a member we recommend reaching out to one of the existing Team Members for guidance.
-
+
What's the process for becoming a Collaborator?
- - You must be actively contributing to this repository.
- - Contributions must include significant code reviews or code contributions.
- - A nomination must be done by an existing Team Member of the Website Team with an Issue
- - The Issue must explain and describe why the nominated person is a good addition to the team
- - The Issue must contain links to relevant contributions through:
- - Code Reviews
- - Comments on Issues and PRs
- - Authoring of PRs or Issues
- - Comments or Authoring of Discussions
- - The nomination must have at least 3 existing members of the Website Team to be in agreement with the nomination.
- - This can be done through commenting with "agreement" (showing support) or reacting to the Issue with a :+1: (Thumbs-up Emoji)
- - The Issue must be open for at least 72 hours without an objection from an existing member of the Website Team
- - The nomination cannot pass until all open discordances/objections are resolved.
- - Objections coming from the TSC or Core Collaborators are also counted as valid objections.
+- You must be actively contributing to this repository.
+- Contributions must include significant code reviews or code contributions.
+- A nomination must be done by an existing Team Member of the Website Team with an Issue
+ - The Issue must explain and describe why the nominated person is a good addition to the team
+ - The Issue must contain links to relevant contributions through:
+ - Code Reviews
+ - Comments on Issues and PRs
+ - Authoring of PRs or Issues
+ - Comments or Authoring of Discussions
+- The nomination must have at least 3 existing members of the Website Team to be in agreement with the nomination.
+ - This can be done through commenting with "agreement" (showing support) or reacting to the Issue with a :+1: (Thumbs-up Emoji)
+- The Issue must be open for at least 72 hours without an objection from an existing member of the Website Team - The nomination cannot pass until all open discordances/objections are resolved. - Objections coming from the TSC or Core Collaborators are also counted as valid objections.
# Getting started
@@ -60,60 +58,60 @@ for getting things done and landing your contribution.
1. Click the fork button in the top right to clone the [nodejs.org repository](https://github.com/nodejs/nodejs.org/fork)
2. Clone your fork using SSH, GitHub CLI, or HTTPS.
- ```bash
- git clone git@github.com:/nodejs.org.git # SSH
- git clone https://github.com//nodejs.org.git # HTTPS
- gh repo clone /nodejs.org # GitHub CLI
- ```
+ ```bash
+ git clone git@github.com:/nodejs.org.git # SSH
+ git clone https://github.com//nodejs.org.git # HTTPS
+ gh repo clone /nodejs.org # GitHub CLI
+ ```
3. Change into the nodejs.org directory.
- ```bash
- cd nodejs.org
- ```
+ ```bash
+ cd nodejs.org
+ ```
4. Create a remote for keeping your fork as well as your local clone up-to-date.
- ```bash
- git remote add upstream git@github.com:nodejs/nodejs.org.git # SSH
- git remote add upstream https://github.com/nodejs/nodejs.org.git # HTTPS
- gh repo sync nodejs/nodejs.org # GitHub CLI
- ```
+ ```bash
+ git remote add upstream git@github.com:nodejs/nodejs.org.git # SSH
+ git remote add upstream https://github.com/nodejs/nodejs.org.git # HTTPS
+ gh repo sync nodejs/nodejs.org # GitHub CLI
+ ```
5. Create a new branch for your work.
- ```bash
- git checkout -b name-of-your-branch
- ```
+ ```bash
+ git checkout -b name-of-your-branch
+ ```
6. Run the following to install the dependencies and start a local preview of your work.
- ```bash
- npm ci # installs this project's dependencies
- npx turbo serve # starts a preview of your local changes
- ```
+ ```bash
+ npm ci # installs this project's dependencies
+ npx turbo serve # starts a preview of your local changes
+ ```
7. Perform a merge to sync your current branch with the upstream branch.
- ```bash
- git fetch upstream
- git merge upstream/main
- ```
+ ```bash
+ git fetch upstream
+ git merge upstream/main
+ ```
8. Run `npx turbo format` to confirm that linting, and formatting are passing.
- ```bash
- npx turbo format
- ```
+ ```bash
+ npx turbo format
+ ```
-9. Once you're happy with your changes, add and commit them to your branch, then push the branch to your fork.
+9. Once you're happy with your changes, add and commit them to your branch, then push the branch to your fork.
- ```bash
- cd ~/nodejs.org
- git add .
- git commit -m "some message"
- git push -u origin name-of-your-branch
- ```
+ ```bash
+ cd ~/nodejs.org
+ git add .
+ git commit -m "some message"
+ git push -u origin name-of-your-branch
+ ```
10. Create a Pull Request.
@@ -126,12 +124,12 @@ This repository contains several scripts and commands for performing numerous ta
Commands for Running & Building the Website
- - `npx turbo serve` runs Next.js's Local Development Server, listening by default on `http://localhost:3000/`.
- - `npx turbo build` builds the Application on Production mode. The output is by default within `.next` folder.
- - This is used for the Node.js Vercel Deployments (Preview & Production)
- - `npx turbo deploy` builds the Application on Export Production Mode. The output is by default within `build` folder.
- - This is used for the Node.js Legacy Website Server (DigitalOcean)
- - `npx turbo start` starts a web server running serving the built content from `npx turbo build`
+- `npx turbo serve` runs Next.js's Local Development Server, listening by default on `http://localhost:3000/`.
+- `npx turbo build` builds the Application on Production mode. The output is by default within `.next` folder.
+ - This is used for the Node.js Vercel Deployments (Preview & Production)
+- `npx turbo deploy` builds the Application on Export Production Mode. The output is by default within `build` folder.
+ - This is used for the Node.js Legacy Website Server (DigitalOcean)
+- `npx turbo start` starts a web server running serving the built content from `npx turbo build`
@@ -186,7 +184,7 @@ We recommend a read on our [Collaborator Guide](COLLABORATOR_GUIDE.md#accepting-
- The person that is fast-tracking the PR (adding the label) must also comment on the PR that they're requesting the PR to be fast-tracked
- The comment must mention `@nodejs/website` and must have at least one 👍 (or any other sort of approval reaction) if the person fast-tracking the PR is the author of the PR.
- Fast-tracking is only allowed for small bug fixes, small feature changes, localisation changes, or other sorts of non-critical/highly-impacting changes that are not covered by the previous rule that allows PRs to be merged immediately.
- - Fast-tracking cannot be used for updates on the `COLLABORATOR_GUIDE.md`, CONTRIBUTING.md` guide, `CODEOWNERS`, GitHub Actions or any security-impacting file or document that changes the governing policies of this repository.
+ - Fast-tracking cannot be used for updates on the `COLLABORATOR_GUIDE.md`, CONTRIBUTING.md`guide,`CODEOWNERS`, GitHub Actions or any security-impacting file or document that changes the governing policies of this repository.
- There must be no objections after a 48-hour period (Or 72 hours if the PR was authored on the weekend).
- At least one approval is required for any PR to be merged.
- Tests must be included in Pull Requests for new features or bug fixes. If any test(s) are failing, you are responsible for fixing them.
diff --git a/README.md b/README.md
index 5587599bdc9fa..ef2d60167255e 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
- nodejs.org website built using Nextra (Next.js) with TypeScript, SCSS and MDXv2
+ nodejs.org website built using Next.js with TypeScript, SCSS and MDXv2
@@ -31,7 +31,7 @@
## What is this repo?
-[Nodejs.org](https://nodejs.org/) by the [OpenJS Foundation](https://openjsf.org/) is the official website for the Node.js® JavaScript runtime. This repo is the source code for the website. It is build using [Nextra](https://nextra.site), a Next.js based static site generator.
+[Nodejs.org](https://nodejs.org/) by the [OpenJS Foundation](https://openjsf.org/) is the official website for the Node.js® JavaScript runtime. This repo is the source code for the website. It is build using [Next.js](https://nextjs.org) a React Framework.
### Quick-Start Locally
diff --git a/app/en/feed/[feed]/route.ts b/app/en/feed/[feed]/route.ts
new file mode 100644
index 0000000000000..3afb616f52c0a
--- /dev/null
+++ b/app/en/feed/[feed]/route.ts
@@ -0,0 +1,21 @@
+import { basename } from 'node:path';
+import { NextResponse } from 'next/server';
+import * as nextJson from '@/next.json.mjs';
+import * as nextData from '@/next-data/index.mjs';
+
+// loads all the data from the blog-posts-data.json file
+const websiteFeeds = nextData.generateWebsiteFeeds(nextJson.blogData);
+
+export async function GET(request: Request) {
+ const { pathname } = new URL(request.url);
+
+ const feed = basename(pathname);
+
+ if (websiteFeeds.has(feed)) {
+ return new NextResponse(websiteFeeds.get(feed)?.rss2(), {
+ headers: { 'Content-Type': 'application/xml' },
+ });
+ }
+
+ return new NextResponse(null, { status: 404 });
+}
diff --git a/components/Blog/BlogCard/index.stories.tsx b/components/Blog/BlogCard/index.stories.tsx
index c8206347c964c..5a7f848ab6eee 100644
--- a/components/Blog/BlogCard/index.stories.tsx
+++ b/components/Blog/BlogCard/index.stories.tsx
@@ -8,7 +8,7 @@ export const Default: Story = {
args: {
author: 'Bat Man',
category: 'category-mock',
- date: new Date('2023-04-21 23:40:56.77'),
+ date: '2023-04-21 23:40:56.77',
slug: '/blog/category-mock/sample-blog',
title: 'Sample Test Blog',
readingTime: '1 min read',
diff --git a/components/Blog/BlogCard/index.tsx b/components/Blog/BlogCard/index.tsx
index 147ac59b1340e..992e16145a086 100644
--- a/components/Blog/BlogCard/index.tsx
+++ b/components/Blog/BlogCard/index.tsx
@@ -8,7 +8,7 @@ import type { FC } from 'react';
const getBlogCategoryUrl = (category: string): string =>
`${navigation.blog.link}/${category}/`;
-type BlogCardProps = Omit;
+type BlogCardProps = BlogPost & { readingTime: string };
const BlogCard: FC = ({
title,
diff --git a/components/Downloads/PrimaryDownloadMatrix.tsx b/components/Downloads/PrimaryDownloadMatrix.tsx
index 9376a67064e75..46e16d4dd2a44 100644
--- a/components/Downloads/PrimaryDownloadMatrix.tsx
+++ b/components/Downloads/PrimaryDownloadMatrix.tsx
@@ -2,7 +2,7 @@ import classNames from 'classnames';
import semVer from 'semver';
import LocalizedLink from '../LocalizedLink';
import { useDetectOS } from '../../hooks/useDetectOS';
-import { useNextraContext } from '../../hooks/useNextraContext';
+import { useLayoutContext } from '../../hooks/useLayoutContext';
import type { LegacyDownloadsFrontMatter, NodeRelease } from '../../types';
import type { FC } from 'react';
@@ -14,11 +14,11 @@ const PrimaryDownloadMatrix: FC = ({
isLts,
npm,
}) => {
- const nextraContext = useNextraContext();
+ const { frontMatter } = useLayoutContext();
const { bitness } = useDetectOS();
- const { downloads } = nextraContext.frontMatter as LegacyDownloadsFrontMatter;
+ const { downloads } = frontMatter as LegacyDownloadsFrontMatter;
const hasWindowsArm64 = semVer.satisfies(version, '>= 19.9.0');
const getIsVersionClassName = (isCurrent: boolean) =>
diff --git a/components/Downloads/SecondaryDownloadMatrix.tsx b/components/Downloads/SecondaryDownloadMatrix.tsx
index e8e1d48a07fe6..2c2dd999e6d95 100644
--- a/components/Downloads/SecondaryDownloadMatrix.tsx
+++ b/components/Downloads/SecondaryDownloadMatrix.tsx
@@ -1,5 +1,5 @@
import DownloadList from './DownloadList';
-import { useNextraContext } from '../../hooks/useNextraContext';
+import { useLayoutContext } from '../../hooks/useLayoutContext';
import { WithNodeRelease } from '../../providers/withNodeRelease';
import type { LegacyDownloadsFrontMatter, NodeRelease } from '../../types';
import type { FC } from 'react';
@@ -10,10 +10,9 @@ const SecondaryDownloadMatrix: FC = ({
versionWithPrefix,
status,
}) => {
- const nextraContext = useNextraContext();
+ const { frontMatter } = useLayoutContext();
- const { additional } =
- nextraContext.frontMatter as LegacyDownloadsFrontMatter;
+ const { additional } = frontMatter as LegacyDownloadsFrontMatter;
return (
diff --git a/components/Home/HomeDownloadButton.tsx b/components/Home/HomeDownloadButton.tsx
index b32be0c5edd62..7d927a7f44d0b 100644
--- a/components/Home/HomeDownloadButton.tsx
+++ b/components/Home/HomeDownloadButton.tsx
@@ -1,6 +1,6 @@
import LocalizedLink from '../LocalizedLink';
import { useDetectOS } from '../../hooks/useDetectOS';
-import { useNextraContext } from '../../hooks/useNextraContext';
+import { useLayoutContext } from '../../hooks/useLayoutContext';
import { downloadUrlByOS } from '../../util/downloadUrlByOS';
import { getNodejsChangelog } from '../../util/getNodeJsChangelog';
import type { FC } from 'react';
@@ -14,7 +14,7 @@ const HomeDownloadButton: FC = ({
}) => {
const {
frontMatter: { labels },
- } = useNextraContext();
+ } = useLayoutContext();
const { os, bitness } = useDetectOS();
@@ -22,7 +22,8 @@ const HomeDownloadButton: FC = ({
const nodeApiLink = `https://nodejs.org/dist/latest-v${major}.x/docs/api/`;
const nodeAllDownloadsLink = `/download${isLts ? '/' : '/current'}`;
const nodeDownloadTitle =
- `${labels.download} ${version}` + ` ${labels[isLts ? 'lts' : 'current']}`;
+ `${labels?.download} ${version}` +
+ ` ${labels?.[isLts ? 'lts' : 'current']}`;
return (
@@ -32,23 +33,23 @@ const HomeDownloadButton: FC
= ({
title={nodeDownloadTitle}
data-version={versionWithPrefix}
>
- {version} {labels[isLts ? 'lts' : 'current']}
- {labels[`tagline-${isLts ? 'lts' : 'current'}`]}
+ {version} {labels?.[isLts ? 'lts' : 'current']}
+ {labels?.[`tagline-${isLts ? 'lts' : 'current'}`]}
- {labels['other-downloads']}
+ {labels?.['other-downloads']}
- {labels.changelog}
+ {labels?.changelog}
- {labels.api}
+ {labels?.api}
diff --git a/components/Pagination.tsx b/components/Pagination.tsx
index d824e3ba91f1d..989fe40a704e6 100644
--- a/components/Pagination.tsx
+++ b/components/Pagination.tsx
@@ -2,18 +2,18 @@ import { FormattedMessage } from 'react-intl';
import LocalizedLink from './LocalizedLink';
import type { FC } from 'react';
-type PaginationProps = { prevSlug?: string; nextSlug?: string };
+type PaginationProps = { prevSlug?: number; nextSlug?: number };
const Pagination: FC = ({ nextSlug, prevSlug }) => (
{nextSlug && (
-
+
<
)}
{prevSlug && (
-
+
>
)}
diff --git a/global.d.ts b/global.d.ts
index 4251b8c1fa96f..597b59a91eb46 100644
--- a/global.d.ts
+++ b/global.d.ts
@@ -1,7 +1,4 @@
declare global {
- // @TODO: Update this to use the correct type
- var __nextra_pageContext__: Record;
-
interface Window {
startLegacyApp: Function;
}
diff --git a/hooks/useBlogData.ts b/hooks/useBlogData.ts
new file mode 100644
index 0000000000000..95ba597dcf0f3
--- /dev/null
+++ b/hooks/useBlogData.ts
@@ -0,0 +1,42 @@
+import { useCallback, useContext, useMemo } from 'react';
+import { useRouter } from './useRouter';
+import { BlogDataContext } from '../providers/blogDataProvider';
+
+export const useBlogData = () => {
+ const { asPath } = useRouter();
+
+ const { posts, pagination, categories } = useContext(BlogDataContext);
+
+ const getPostsByCategory = useCallback(
+ (category: string) => posts.filter(post => post.category === category),
+ [posts]
+ );
+
+ const getPostsByYear = useCallback(
+ (year: number) =>
+ posts.filter(post => new Date(post.date).getFullYear() === year),
+ [posts]
+ );
+
+ const getPagination = useCallback(
+ (currentYear: number) => ({
+ next: pagination.includes(currentYear + 1) ? currentYear + 1 : undefined,
+ prev: pagination.includes(currentYear - 1) ? currentYear - 1 : undefined,
+ }),
+ [pagination]
+ );
+
+ const currentCategory = useMemo(
+ () => asPath.split('/')[3] || new Date().getFullYear().toString(),
+ [asPath]
+ );
+
+ return {
+ posts,
+ categories,
+ currentCategory,
+ getPostsByCategory,
+ getPostsByYear,
+ getPagination,
+ };
+};
diff --git a/hooks/useLayoutContext.ts b/hooks/useLayoutContext.ts
new file mode 100644
index 0000000000000..a6b3d8c69de6b
--- /dev/null
+++ b/hooks/useLayoutContext.ts
@@ -0,0 +1,4 @@
+import { useContext } from 'react';
+import { LayoutContext } from '../providers/layoutProvider';
+
+export const useLayoutContext = () => useContext(LayoutContext);
diff --git a/hooks/useNavigation.tsx b/hooks/useNavigation.tsx
index baaf4d17533d1..2a7bea6d1c196 100644
--- a/hooks/useNavigation.tsx
+++ b/hooks/useNavigation.tsx
@@ -1,6 +1,5 @@
import { FormattedMessage } from 'react-intl';
-import navigation from '../navigation.json';
-
+import * as nextJson from '../next.json.mjs';
import type { NavigationEntry, NavigationKeys } from '../types';
// Translation Context for FormattedMessage
@@ -33,11 +32,14 @@ export const useNavigation = () => {
};
return {
- navigationItems: mapNavigationEntries(navigation),
+ navigationItems: mapNavigationEntries(nextJson.siteNavigation),
getSideNavigation: (section: NavigationKeys, context?: Context) =>
mapNavigationEntries(
// We need the parent and their items when making a side navigation
- { [section]: navigation[section], ...navigation[section].items },
+ {
+ [section]: nextJson.siteNavigation[section],
+ ...nextJson.siteNavigation[section].items,
+ },
context
),
};
diff --git a/hooks/useNextraContext.ts b/hooks/useNextraContext.ts
deleted file mode 100644
index 19c9b5d7b3fe1..0000000000000
--- a/hooks/useNextraContext.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { useContext } from 'react';
-import { LayoutContext } from '../providers/layoutProvider';
-
-export const useNextraContext = () => {
- const { pageOpts, pageProps } = useContext(LayoutContext);
-
- return { ...pageOpts, ...pageProps };
-};
diff --git a/layouts/BlogIndexLayout.tsx b/layouts/BlogIndexLayout.tsx
index cb2acb65c463d..e365400d3aa68 100644
--- a/layouts/BlogIndexLayout.tsx
+++ b/layouts/BlogIndexLayout.tsx
@@ -1,15 +1,32 @@
+import { useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import BaseLayout from './BaseLayout';
import Pagination from '../components/Pagination';
import LocalizedLink from '../components/LocalizedLink';
-import { useNextraContext } from '../hooks/useNextraContext';
+import { useBlogData } from '../hooks/useBlogData';
import { getTimeComponent } from '../util/getTimeComponent';
import type { FC, PropsWithChildren } from 'react';
+import type { BlogPost } from '../types';
const BlogIndexLayout: FC = ({ children }) => {
- const { blogData } = useNextraContext();
+ const { getPagination, getPostsByYear, currentCategory } = useBlogData();
- const currentYear = blogData?.currentCategory.replace('year-', '');
+ const currentYear = useMemo(
+ () =>
+ Number(
+ currentCategory.startsWith('year-')
+ ? currentCategory.replace('year-', '')
+ : new Date().getFullYear()
+ ),
+ [currentCategory]
+ );
+
+ const { posts, pagination } = useMemo(() => {
+ return {
+ posts: getPostsByYear(currentYear),
+ pagination: getPagination(currentYear),
+ };
+ }, [currentYear, getPagination, getPostsByYear]);
return (
@@ -21,7 +38,7 @@ const BlogIndexLayout: FC = ({ children }) => {
/>
- {blogData?.posts.map(post => (
+ {posts.map((post: BlogPost) => (
{getTimeComponent(post.date.toString(), '%d %b')}
{post.title}
@@ -29,10 +46,7 @@ const BlogIndexLayout: FC = ({ children }) => {
))}
-
+
{children}
diff --git a/layouts/BlogPostLayout.tsx b/layouts/BlogPostLayout.tsx
index 9ed9ae65778f1..b57de50ed4dbf 100644
--- a/layouts/BlogPostLayout.tsx
+++ b/layouts/BlogPostLayout.tsx
@@ -1,15 +1,14 @@
import { FormattedMessage } from 'react-intl';
import BaseLayout from './BaseLayout';
-import { useNextraContext } from '../hooks/useNextraContext';
+import { useLayoutContext } from '../hooks/useLayoutContext';
import { getTimeComponent } from '../util/getTimeComponent';
import type { FC, PropsWithChildren } from 'react';
import type { LegacyBlogFrontMatter } from '../types';
const BlogPostLayout: FC = ({ children }) => {
- const nextraContext = useNextraContext();
+ const { frontMatter } = useLayoutContext();
- const { title, author, date } =
- nextraContext.frontMatter as LegacyBlogFrontMatter;
+ const { title, author, date } = frontMatter as LegacyBlogFrontMatter;
return (
diff --git a/layouts/CategoryIndexLayout.tsx b/layouts/CategoryIndexLayout.tsx
index 51d2d5f9e3e92..c1340c4d13c50 100644
--- a/layouts/CategoryIndexLayout.tsx
+++ b/layouts/CategoryIndexLayout.tsx
@@ -1,11 +1,20 @@
+import { useMemo } from 'react';
import BaseLayout from './BaseLayout';
import LocalizedLink from '../components/LocalizedLink';
-import { useNextraContext } from '../hooks/useNextraContext';
+import { useLayoutContext } from '../hooks/useLayoutContext';
+import { useBlogData } from '../hooks/useBlogData';
import { getTimeComponent } from '../util/getTimeComponent';
import type { FC, PropsWithChildren } from 'react';
+import type { BlogPost } from '../types';
const CategoryIndexLayout: FC = ({ children }) => {
- const { blogData, frontMatter } = useNextraContext();
+ const { frontMatter } = useLayoutContext();
+ const { getPostsByCategory, currentCategory } = useBlogData();
+
+ const posts = useMemo(
+ () => getPostsByCategory(currentCategory),
+ [currentCategory, getPostsByCategory]
+ );
return (
@@ -13,7 +22,7 @@ const CategoryIndexLayout: FC = ({ children }) => {
{frontMatter.title}
- {blogData?.posts.map(post => (
+ {posts.map((post: BlogPost) => (
{getTimeComponent(post.date.toString(), '%d %b %y')}
{post.title}
diff --git a/layouts/DownloadCurrentLayout.tsx b/layouts/DownloadCurrentLayout.tsx
index 174015020685d..3dbbf447a502c 100644
--- a/layouts/DownloadCurrentLayout.tsx
+++ b/layouts/DownloadCurrentLayout.tsx
@@ -1,15 +1,15 @@
import BaseLayout from './BaseLayout';
import PrimaryDownloadMatrix from '../components/Downloads/PrimaryDownloadMatrix';
import SecondaryDownloadMatrix from '../components/Downloads/SecondaryDownloadMatrix';
-import { useNextraContext } from '../hooks/useNextraContext';
+import { useLayoutContext } from '../hooks/useLayoutContext';
import { WithNodeRelease } from '../providers/withNodeRelease';
import type { FC, PropsWithChildren } from 'react';
import type { LegacyDownloadsFrontMatter } from '../types';
const DownloadCurrentLayout: FC = ({ children }) => {
- const nextraContext = useNextraContext();
+ const { frontMatter } = useLayoutContext();
- const { downloads } = nextraContext.frontMatter as LegacyDownloadsFrontMatter;
+ const { downloads } = frontMatter as LegacyDownloadsFrontMatter;
return (
diff --git a/layouts/DownloadLayout.tsx b/layouts/DownloadLayout.tsx
index 16b910fd3a05a..b3531f6b45585 100644
--- a/layouts/DownloadLayout.tsx
+++ b/layouts/DownloadLayout.tsx
@@ -1,15 +1,15 @@
import BaseLayout from './BaseLayout';
import PrimaryDownloadMatrix from '../components/Downloads/PrimaryDownloadMatrix';
import SecondaryDownloadMatrix from '../components/Downloads/SecondaryDownloadMatrix';
-import { useNextraContext } from '../hooks/useNextraContext';
+import { useLayoutContext } from '../hooks/useLayoutContext';
import { WithNodeRelease } from '../providers/withNodeRelease';
import type { FC, PropsWithChildren } from 'react';
import type { LegacyDownloadsFrontMatter } from '../types';
const DownloadLayout: FC = ({ children }) => {
- const nextraContext = useNextraContext();
+ const { frontMatter } = useLayoutContext();
- const { downloads } = nextraContext.frontMatter as LegacyDownloadsFrontMatter;
+ const { downloads } = frontMatter as LegacyDownloadsFrontMatter;
return (
diff --git a/layouts/DownloadReleasesLayout.tsx b/layouts/DownloadReleasesLayout.tsx
index 2f7b2406d8d06..5716678801cef 100644
--- a/layouts/DownloadReleasesLayout.tsx
+++ b/layouts/DownloadReleasesLayout.tsx
@@ -1,16 +1,15 @@
import { useMemo } from 'react';
import { sanitize } from 'isomorphic-dompurify';
import BaseLayout from './BaseLayout';
-import { useNextraContext } from '../hooks/useNextraContext';
+import { useLayoutContext } from '../hooks/useLayoutContext';
import DownloadReleasesTable from '../components/Downloads/DownloadReleasesTable';
import type { FC, PropsWithChildren } from 'react';
import type { LegacyDownloadsReleasesFrontMatter } from '../types';
const DownloadReleasesLayout: FC = ({ children }) => {
- const nextraContext = useNextraContext();
+ const { frontMatter } = useLayoutContext();
- const { modules, title } =
- nextraContext.frontMatter as LegacyDownloadsReleasesFrontMatter;
+ const { modules, title } = frontMatter as LegacyDownloadsReleasesFrontMatter;
// @TODO: Remove this once we migrate to `nodejs/nodejs.dev` codebase as this is unsafe
// And completely not recommended
diff --git a/layouts/IndexLayout.tsx b/layouts/IndexLayout.tsx
index 142f11a90e9b9..8ecce5db58b07 100644
--- a/layouts/IndexLayout.tsx
+++ b/layouts/IndexLayout.tsx
@@ -2,7 +2,7 @@ import BaseLayout from './BaseLayout';
import Banner from '../components/Home/Banner';
import HomeDownloadButton from '../components/Home/HomeDownloadButton';
import { useDetectOS } from '../hooks/useDetectOS';
-import { useNextraContext } from '../hooks/useNextraContext';
+import { useLayoutContext } from '../hooks/useLayoutContext';
import { WithNodeRelease } from '../providers/withNodeRelease';
import type { FC, PropsWithChildren } from 'react';
import type { UserOS } from '../types/userOS';
@@ -23,12 +23,13 @@ const getDownloadHeadTextOS = (os: UserOS, bitness: number) => {
const IndexLayout: FC = ({ children }) => {
const {
frontMatter: { labels },
- } = useNextraContext();
+ } = useLayoutContext();
const { os, bitness } = useDetectOS();
const downloadHeadTextPrefix =
- os === 'OTHER' ? labels['download'] : labels['download-for'];
+ os === 'OTHER' ? labels?.['download'] : labels?.['download-for'];
+
const downloadHeadText = `${downloadHeadTextPrefix}${getDownloadHeadTextOS(
os,
bitness
@@ -53,9 +54,9 @@ const IndexLayout: FC = ({ children }) => {
- {labels['version-schedule-prompt']}{' '}
+ {labels?.['version-schedule-prompt']}{' '}
- {labels['version-schedule-prompt-link-text']}
+ {labels?.['version-schedule-prompt-link-text']}
.
diff --git a/layouts/New/.gitkeep b/layouts/New/.gitkeep
deleted file mode 100644
index f935021a8f8a7..0000000000000
--- a/layouts/New/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-!.gitignore
diff --git a/next-data/generateBlogPostsData.mjs b/next-data/generateBlogPostsData.mjs
new file mode 100644
index 0000000000000..8dce356abcdae
--- /dev/null
+++ b/next-data/generateBlogPostsData.mjs
@@ -0,0 +1,80 @@
+'use strict';
+
+import { readFile, writeFile } from 'node:fs/promises';
+import { basename, dirname, extname, join } from 'node:path';
+import graymatter from 'gray-matter';
+import * as helpers from './helpers.mjs';
+
+// gets the current blog path based on local module path
+const blogPath = join(process.cwd(), 'pages/en/blog');
+
+// this is the destination path for where the JSON file will be written
+const jsonFilePath = join(process.cwd(), 'public/blog-posts-data.json');
+
+/**
+ * This contains the metadata of all available blog categories and
+ * available pagination entries (years)
+ *
+ * @type {{ pagination: Set; categories: Set}}
+ */
+const blogMetadata = { pagination: new Set(), categories: new Set() };
+
+/**
+ * This method parses the source (raw) Markdown content into Frontmatter
+ * and returns basic information for blog posts
+ *
+ * @param {string} filename the filename related to the blogpost
+ * @param {string} source the source markdown content of the blog post
+ */
+const getFrontMatter = (filename, source) => {
+ const {
+ title = 'Untitled',
+ author = 'The Node.js Project',
+ date = new Date(),
+ category = 'uncategorized',
+ } = graymatter(source).data;
+
+ // we add the year to the pagination set
+ blogMetadata.pagination.add(new Date(date).getFullYear());
+
+ // we add the category to the categories set
+ blogMetadata.categories.add(category);
+
+ // this is the url used for the blog post it based on the category and filename
+ const slug = `/blog/${category}/${basename(filename, extname(filename))}`;
+
+ return { title, author, date, category, slug };
+};
+
+/**
+ * This method is used to generate the JSON file
+ */
+const generateBlogPostsData = async () => {
+ // we retrieve all the filenames of all blog posts
+ const filenames = await helpers.getMarkdownFiles(
+ process.cwd(),
+ 'pages/en/blog',
+ ['**/index.md', '**/pagination.md']
+ );
+
+ // we gather all the information of all the blog posts by reading each individual file
+ // and then parsing the frontmatter and source content and returning a minified object
+ const postsPromise = filenames
+ .map(name => ({ name, file: readFile(join(blogPath, name)) }))
+ .map(({ name, file }) => file.then(source => getFrontMatter(name, source)))
+ .sort();
+
+ // we await for all the work to be conclued and return a nice blog posts object
+ const posts = await Promise.all(postsPromise);
+
+ return writeFile(
+ jsonFilePath,
+ JSON.stringify({
+ pagination: [...blogMetadata.pagination].sort(),
+ categories: [...blogMetadata.categories].sort(),
+ posts,
+ })
+ );
+};
+
+export default generateBlogPostsData;
diff --git a/next-data/generateNodeReleasesJson.mjs b/next-data/generateNodeReleasesJson.mjs
index 5a29a544bf9f0..74b2b34167d1e 100644
--- a/next-data/generateNodeReleasesJson.mjs
+++ b/next-data/generateNodeReleasesJson.mjs
@@ -1,16 +1,12 @@
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import nodevu from '@nodevu/core';
-import * as helpers from './helpers.mjs';
-
-// this allows us to get the current module working directory
-const __dirname = helpers.getRelativePath(import.meta.url);
// this is the destination path for where the JSON file will be written
-const jsonFilePath = join(__dirname, '../public/node-releases-data.json');
+const jsonFilePath = join(process.cwd(), 'public/node-releases-data.json');
const generateNodeReleasesJson = async () => {
- const nodevuOutput = await nodevu();
+ const nodevuOutput = await nodevu({ fetch: fetch });
// Filter out those without documented support
// Basically those not in schedule.json
diff --git a/next-data/generateWebsiteFeeds.mjs b/next-data/generateWebsiteFeeds.mjs
index 00ce909e251e5..4cd077bc99e47 100644
--- a/next-data/generateWebsiteFeeds.mjs
+++ b/next-data/generateWebsiteFeeds.mjs
@@ -1,53 +1,48 @@
'use strict';
-import { writeFile } from 'node:fs/promises';
-import { join } from 'node:path';
import { Feed } from 'feed';
-import * as helpers from './helpers.mjs';
-
-// imports the global site config
-import siteConfig from '../site.json' assert { type: 'json' };
-
-// this allows us to get the current module working directory
-const __dirname = helpers.getRelativePath(import.meta.url);
-
-// gets the current blog path based on local module path
-const blogPath = join(__dirname, '../pages/en/blog');
-
-const publicFeedPath = join(__dirname, '../public/en/feed');
+import * as nextJson from '../next.json.mjs';
+import * as nextConstants from '../next.constants.mjs';
/**
* This method generates RSS website feeds based on the current website configuration
* and the current blog data that is available
*
- * @param {ReturnType} cachedBlogData
+ * @param {import('../types').BlogData} blogData
*/
-const generateWebsiteFeeds = cachedBlogData =>
- siteConfig.rssFeeds.forEach(metadata => {
- const feed = new Feed({
- title: metadata.title,
- description: metadata.description || siteConfig.description,
- id: metadata.link,
- link: metadata.link,
- language: 'en',
- });
+const generateWebsiteFeeds = ({ posts }) => {
+ /**
+ * This generates all the Website RSS Feeds that are used for the website
+ *
+ * @type {[string, Feed][]}
+ */
+ const websiteFeeds = nextJson.siteConfig.rssFeeds.map(
+ ({ category, title, description, file }) => {
+ const feed = new Feed({
+ id: file,
+ title: title,
+ language: 'en',
+ link: `${nextConstants.BASE_PATH}/en/feed/${file}`,
+ description: description || nextJson.siteConfig.description,
+ });
- const blogCategoryOrAll = metadata.blogCategory
- ? `/en/blog/${metadata.blogCategory}`
- : '/en/blog';
+ const blogFeedEntries = posts
+ .filter(post => !category || post.category === category)
+ .map(post => ({
+ id: post.slug,
+ title: post.title,
+ author: post.author,
+ date: new Date(post.date),
+ link: `${nextConstants.BASE_PATH}/en${post.slug}`,
+ }));
- const mapBlogPostToFeed = post =>
- feed.addItem({
- title: post.title,
- id: `https://nodejs.org/en${post.slug}`,
- link: `https://nodejs.org/en${post.slug}`,
- author: post.author,
- date: new Date(post.date),
- });
+ blogFeedEntries.forEach(entry => feed.addItem(entry));
+
+ return [file, feed];
+ }
+ );
- cachedBlogData(blogCategoryOrAll, !metadata.blogCategory)
- .then(({ blogData }) => blogData.posts.forEach(mapBlogPostToFeed))
- .then(() => writeFile(join(publicFeedPath, metadata.file), feed.rss2()));
- });
+ return new Map(websiteFeeds);
+};
export default generateWebsiteFeeds;
diff --git a/next-data/getBlogData.mjs b/next-data/getBlogData.mjs
deleted file mode 100644
index b15a3b3b1c4d6..0000000000000
--- a/next-data/getBlogData.mjs
+++ /dev/null
@@ -1,143 +0,0 @@
-'use strict';
-
-import { readFile, readdir } from 'node:fs/promises';
-import { basename, extname, join } from 'node:path';
-import graymatter from 'gray-matter';
-import * as helpers from './helpers.mjs';
-
-// this allows us to get the current module working directory
-const __dirname = helpers.getRelativePath(import.meta.url);
-
-// gets the current blog path based on local module path
-const blogPath = join(__dirname, '../pages/en/blog');
-
-// retrieves the current year based on when the build happens
-const currentYear = new Date().getFullYear();
-
-// gathers only the frontmatter fields that are relevant to us
-const getMatter = name => content => {
- const { title, author, date, category } = graymatter(content).data;
-
- // we only want the actual name of the file without the extension
- // and then prepend the category name to it
- const slug = `/blog/${category}/${basename(name, extname(name))}`;
-
- return { title, author, date, category, slug, file: basename(name) };
-};
-
-const getPost = category => post =>
- readFile(join(blogPath, category, post)).then(getMatter(post));
-
-const getPosts = (category, posts) =>
- posts.then(posts => Promise.all(posts.map(getPost(category))));
-
-// Note.: This current structure is coupled to the current way how we do pagination and categories
-// This will definitely change over time once we start migrating to the `nodejs/nodejs.dev` codebase
-// @deprecated - this will be removed once we migrate to the new blog structure from `nodejs.dev` codebase
-const getBlogData = () => {
- const blogCategories = helpers
- .getDirectories(__dirname, blogPath)
- .then(c => c.map(s => [s, readdir(join(blogPath, s))]));
-
- const categoriesPosts = blogCategories.then(c =>
- c.map(([s, f]) => [s, getPosts(s, f)])
- );
-
- /**
- * This method generates the blog post data based on route
- * or if needed based on the category and pagination
- *
- * We also allow an override to get all available posts
- * via the `getAllAvailablePosts` parameter
- *
- * @param {string} route the current next route
- * @param {boolean} getAllAvailablePosts whether to get all available posts or not
- * @return {Promise<{ blogData: import('../types').BlogData }>} the generated blog data
- */
- return async (route = '/', getAllAvailablePosts = false) => {
- const [, , subDirectory, category = `year-${currentYear}`, blogPostSlug] =
- route.split('/');
-
- /**
- * @param {import('../types').BlogPost[]} posts
- * @param {boolean} hasNext if it has a next page
- * @param {boolean} hasPrev if it has a previous page
- * @return {{ blogData: import('../types').BlogData }} the generated blog data
- */
- const generatedBlogData = (posts, hasNext, hasPrev) => ({
- blogData: {
- posts: posts.sort((a, b) => b.date - a.date),
- currentCategory: category,
- pagination: { next: hasNext || null, prev: hasPrev || null },
- },
- });
-
- if (getAllAvailablePosts) {
- return categoriesPosts.then(categories => {
- // get only the post ingredient from the category array
- const allPosts = categories.map(([, f]) => f);
-
- const generatedBlog = posts => generatedBlogData(posts);
-
- return Promise.all(allPosts)
- .then(s => Promise.all(s.flat()))
- .then(s => s.filter(p => p.date && p.file !== 'index.md'))
- .then(generatedBlog);
- });
- }
-
- // we don't want to generate blog data within a blog post
- if (blogPostSlug && blogPostSlug.length > 0) {
- return {};
- }
-
- if (helpers.getMatchingRoutes(subDirectory, ['blog'])) {
- return categoriesPosts.then(categories => {
- // yearly pagination posts (doesn't accept category pagination)
- if (category && category.startsWith('year-')) {
- const selectedYear = Number(category.replace('year-', ''));
-
- // get only the post ingredient from the category array
- const allPosts = categories.map(([, f]) => f);
-
- const generatedBlog = posts =>
- generatedBlogData(
- posts.filter(p => p.date.getFullYear() === selectedYear),
- posts.some(p => p.date.getFullYear() === selectedYear + 1) &&
- `/blog/year-${selectedYear + 1}`,
- posts.some(p => p.date.getFullYear() === selectedYear - 1) &&
- `/blog/year-${selectedYear - 1}`
- );
-
- return Promise.all(allPosts)
- .then(s => Promise.all(s.flat()))
- .then(s => s.filter(p => p.date && p.file !== 'index.md'))
- .then(generatedBlog);
- }
-
- // attempts to find a category that matches the current route category
- const currentCategory = categories.find(([c]) => c === category);
-
- if (category && currentCategory) {
- // extract the posts part of the current category
- const [, categoryPosts] = currentCategory;
-
- const generatedBlog = posts => generatedBlogData(posts);
-
- // get all posts from current category instead of traversing all categories
- return categoryPosts
- .then(posts => posts.flat())
- .then(posts => posts.filter(p => p.file !== 'index.md'))
- .then(generatedBlog);
- }
-
- // this should not happen or means the category is non-existent
- return {};
- });
- }
-
- return {};
- };
-};
-
-export default getBlogData;
diff --git a/next-data/getBlogIndexPages.mjs b/next-data/getBlogIndexPages.mjs
deleted file mode 100644
index 06d66fec4ad51..0000000000000
--- a/next-data/getBlogIndexPages.mjs
+++ /dev/null
@@ -1,17 +0,0 @@
-'use strict';
-
-/**
- * This method gets all available blog posts and generates an index of
- * all years that have blog posts. This approach uses the file system
- * for checking all available posts.
- *
- * @param {ReturnType} cachedBlogData
- * @return {Promise} the list of years that have blog posts
- */
-const getBlogIndexPages = cachedBlogData =>
- cachedBlogData('', true)
- .then(({ blogData }) => blogData.posts.map(p => p.date.getFullYear()))
- .then(data => data.map(year => year.toString()))
- .then(data => [...new Set(data)]);
-
-export default getBlogIndexPages;
diff --git a/next-data/helpers.mjs b/next-data/helpers.mjs
index 1626db40a6a56..a49765980cf72 100644
--- a/next-data/helpers.mjs
+++ b/next-data/helpers.mjs
@@ -4,6 +4,15 @@ import { existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { glob } from 'glob';
+/**
+ * We create a locale cache of Glob Promises
+ * to avoid reading the file system multiple times
+ * this is done since we don't need to constantly re-run the glob
+ * query as it is only needed once
+ *
+ * @type {Map>} */
+const globCacheByPath = new Map();
+
export const getMatchingRoutes = (route = '', matches = []) =>
matches.some(match => route === match);
@@ -41,25 +50,11 @@ export const getRelativePath = path => fileURLToPath(new URL('.', path));
* @returns {Promise} a promise containing an array of paths
*/
export const getMarkdownFiles = async (root, cwd, ignore = []) => {
- return glob('**/*.{md,mdx}', { root, cwd, ignore })
- .then(files => files.map(file => file.replace(/(\/index)?\.mdx?$/, '')))
- .then(files => files.filter(file => file.length));
-};
+ const cacheKey = `${root}${cwd}${ignore.join('')}`;
-/**
- * This method is responsible for checking a combination
- * of extensions in a given list and tests a list of extensions
- * that could match that file.
- *
- * If no matching extension is found an empty string is returned
- * which signals that the find was not found
- *
- * @param {string} filename the filename without extension/suffixes
- * @param {string[]} extensions an array of suffixes to be tested
- * @returns {string} the filename with the first matching extension
- */
-export const checkFileExists = (filename, extensions) => {
- const extension = extensions.find(e => existsSync(`${filename}${e}`));
+ if (!globCacheByPath.has(cacheKey)) {
+ globCacheByPath.set(cacheKey, glob('**/*.{md,mdx}', { root, cwd, ignore }));
+ }
- return extension ? `${filename}${extension}` : '';
+ return globCacheByPath.get(cacheKey);
};
diff --git a/next-data/index.mjs b/next-data/index.mjs
index 8c83c72cf5f10..5f678d1231e93 100644
--- a/next-data/index.mjs
+++ b/next-data/index.mjs
@@ -1,15 +1,13 @@
'use strict';
-import getBlogData from './getBlogData.mjs';
-import getBlogIndexPages from './getBlogIndexPages.mjs';
import generateWebsiteFeeds from './generateWebsiteFeeds.mjs';
+import generateBlogPostsData from './generateBlogPostsData.mjs';
import generateNodeReleasesJson from './generateNodeReleasesJson.mjs';
import * as helpers from './helpers.mjs';
export {
- getBlogData,
- getBlogIndexPages,
generateWebsiteFeeds,
+ generateBlogPostsData,
generateNodeReleasesJson,
helpers,
};
diff --git a/next-env.d.ts b/next-env.d.ts
index 4f11a03dc6cc3..fd36f9494e2c2 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,5 +1,6 @@
///
///
+///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/next-sitemap.config.mjs b/next-sitemap.config.mjs
index 5e29acebd0ca0..e7ec86c869ed0 100644
--- a/next-sitemap.config.mjs
+++ b/next-sitemap.config.mjs
@@ -1,6 +1,6 @@
-// This is used for telling Next.js to to a Static Export Build of the Website
-// We use this within this config file to determine the output directory of this generated sitemap files
-const enableStaticExport = process.env.NEXT_STATIC_EXPORT === 'true';
+'use strict';
+
+import * as nextConstants from './next.constants.mjs';
/** @type {import('next-sitemap').IConfig} */
const sitemapConfig = {
@@ -9,9 +9,9 @@ const sitemapConfig = {
trailingSlash: false,
generateRobotsTxt: true,
generateIndexSitemap: false,
- outDir: enableStaticExport ? 'build' : 'public',
- sourceDir: enableStaticExport ? 'build' : '.next',
- output: enableStaticExport ? 'export' : undefined,
+ outDir: nextConstants.ENABLE_STATIC_EXPORT ? 'build' : 'public',
+ sourceDir: nextConstants.ENABLE_STATIC_EXPORT ? 'build' : '.next',
+ output: nextConstants.ENABLE_STATIC_EXPORT ? 'export' : undefined,
robotsTxtOptions: {
policies: [
{
diff --git a/next.app.tsx b/next.app.tsx
index ab87320301cf7..b7a7cc1c66eb3 100644
--- a/next.app.tsx
+++ b/next.app.tsx
@@ -1,6 +1,9 @@
import { MotionConfig } from 'framer-motion';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { SiteProvider } from './providers/siteProvider';
+import { LocaleProvider } from './providers/localeProvider';
+import { BlogDataProvider } from './providers/blogDataProvider';
+import { NodeReleasesProvider } from './providers/nodeReleasesProvider';
import type { FC, PropsWithChildren } from 'react';
const defaultTypography = [
@@ -16,14 +19,20 @@ const theme = createTheme({
typography: { fontFamily: defaultTypography.join(',') },
});
-export const setAppFont = (font: string) => {
- theme.typography.fontFamily = [font, ...defaultTypography].join(',');
+export const setAppFonts = (fonts: string[]) => {
+ theme.typography.fontFamily = [...fonts, ...defaultTypography].join(',');
};
const BaseApp: FC = ({ children }) => (
+
+
+ {children}
+
+
+
- {children}
diff --git a/next.config.mjs b/next.config.mjs
index 8a67b3973e8c6..e981bd9f1a6cf 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,41 +1,29 @@
-import nextra from 'nextra';
-import remarkGfm from 'remark-gfm';
-import getNextData from './next.data.mjs';
+'use strict';
-const withNextra = nextra({
- theme: 'theme.tsx',
- flexsearch: false,
- codeHighlight: false,
- mdxOptions: { format: 'detect', remarkPlugins: [remarkGfm] },
- transform: getNextData,
- transformPageOpts: pageOpts => {
- delete pageOpts.pageMap;
- delete pageOpts.headings;
- delete pageOpts.timestamp;
+import * as nextConstants from './next.constants.mjs';
+import * as nextData from './next-data/index.mjs';
- return pageOpts;
- },
-});
-
-// This is used for telling Next.js to to a Static Export Build of the Website
-// This is used for static/without a Node.js server hosting, such as on our
-// legacy Website Build Environment on Node.js's DigitalOcean Droplet.
-// Note.: Image optimization is also disabled through this process
-export const enableStaticExport = process.env.NEXT_STATIC_EXPORT === 'true';
+// generate the node.js releases json file
+await nextData.generateNodeReleasesJson();
-// Supports a manuall override of the base path of the website
-// This is useful when running the deployment on a subdirectory
-// of a domain, such as when hosted on GitHub Pages.
-export const basePath = String(process.env.NEXT_BASE_PATH || '');
+// generate the data from blog posts
+await nextData.generateBlogPostsData();
-export default withNextra({
- basePath,
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ i18n: null,
+ swcMinify: true,
trailingSlash: false,
- outputFileTracing: false,
- distDir: enableStaticExport ? 'build' : '.next',
- output: enableStaticExport ? 'export' : undefined,
- experimental: { swcPlugins: [['next-superjson-plugin', {}]] },
- images: { unoptimized: enableStaticExport },
eslint: { dirs: ['.'] },
- i18n: null,
-});
+ basePath: nextConstants.BASE_PATH,
+ images: { unoptimized: nextConstants.ENABLE_STATIC_EXPORT },
+ distDir: nextConstants.ENABLE_STATIC_EXPORT ? 'build' : '.next',
+ output: nextConstants.ENABLE_STATIC_EXPORT ? 'export' : undefined,
+ experimental: {
+ nextScriptWorkers: true,
+ largePageDataBytes: 128 * 100000,
+ swcPlugins: [['next-superjson-plugin', {}]],
+ },
+};
+
+export default nextConfig;
diff --git a/next.constants.mjs b/next.constants.mjs
new file mode 100644
index 0000000000000..a4f0d131655ac
--- /dev/null
+++ b/next.constants.mjs
@@ -0,0 +1,102 @@
+'use strict';
+
+import * as nextJson from './next.json.mjs';
+import * as nextLocales from './next.locales.mjs';
+
+/**
+ * This is used for telling Next.js to to a Static Export Build of the Website
+ *
+ * This is used for static/without a Node.js server hosting, such as on our
+ * legacy Website Build Environment on Node.js's DigitalOcean Droplet.
+ */
+export const ENABLE_STATIC_EXPORT =
+ process.env.NEXT_STATIC_EXPORT === 'true' ||
+ process.env.NEXT_STATIC_EXPORT === true;
+
+/**
+ * Supports a manuall override of the base path of the Website
+ *
+ * This is useful when running the deployment on a subdirectory
+ * of a domain, such as when hosted on GitHub Pages.
+ */
+export const BASE_PATH = String(process.env.NEXT_BASE_PATH || '');
+
+/**
+ * This ReGeX is used to remove the `index.md(x)` suffix of a name and to remove
+ * the `.md(x)` extensions of a filename.
+ *
+ * This RegEx is used to transform the file system pathnames into acceptable
+ * Route Segments for Next.js Dynamic Routes on `pages/[...path].tsx`
+ */
+export const MD_EXTENSION_REGEX = /((\/)?(index))?\.mdx?$/i;
+
+/**
+ * This is a shorthand to the Default Locale if you're only interested
+ * on the Locale Code.
+ *
+ * This should only be used outside of the Next.js Application itself
+ * as within React context the `useLocale` hook should be used instead.
+ */
+export const DEFAULT_LOCALE_CODE = nextLocales.defaultLocale.code;
+
+/**
+ * This indicates the path to the Legacy JavaScript File that is used
+ * on the legacy Website.
+ *
+ * @deprecated The Legacy Website is due to be removed soon and this file
+ * and its usages should be removed
+ */
+export const LEGACY_JAVASCRIPT_FILE = `${BASE_PATH}/static/js/legacyMain.js`;
+
+/**
+ * This is a list of all static routes or pages from the Website that we do not
+ * want to allow to be statically built on our Static Export Build.
+ *
+ * @type {((route: import('./types').RouteSegment) => boolean)[]} A list of Ignored Routes by Regular Expressions
+ */
+export const STATIC_ROUTES_IGNORES = [
+ // This is used to ignore is used to ignore all blog routes except for the English language
+ route => !route.localised && /^blog\//.test(route.pathname),
+ // This is used to ignore the blog/pagination meta route
+ route => /^blog\/pagination/.test(route.pathname),
+ // This is used to ignore all 404 localised routes
+ route => /^404/.test(route.pathname),
+];
+
+/**
+ * This is a list of all dynamic routes or pages from the Website that we do not
+ * want to allow to be dynamically access by our Dynamic Route Engine
+ *
+ * @type {RegExp[]} A list of Ignored Routes by Regular Expressions
+ */
+export const DYNAMIC_ROUTES_IGNORES = [
+ // This is used to ignore the blog/pagination route
+ /^blog\/pagination/,
+ // This is used to ignore all 404 routes
+ /^404/,
+];
+
+/**
+ * This is a list of all static routes that we want to rewrite their pathnames
+ * into something else. This is useful when you want to have the current pathname in the route
+ * but replace the actual Markdown file that is being loaded by the Dynamic Route to something else
+ *
+ * @type {[RegexExp, (pathname: string) => string][]}
+ */
+export const DYNAMIC_ROUTES_REWRITES = [
+ [/^blog\/year-/, () => 'blog/pagination'],
+];
+
+/**
+ * This is a constant that should be used during runtime by (`getStaticPaths`) on `pages/[...path].tsx`
+ *
+ * This function is used to provide an extra set of routes that are not provided by `next.dynamic.mjs`
+ * static route discovery. This can happen when we have dynamic routes that **must** be provided
+ * within the static export (static build) of the website. This constant usually would be used along
+ * with a matching pathname on `DYNAMIC_ROUTES_REWRITES`.
+ *
+ * @returns {string[]} A list of all the Dynamic Routes that are generated by the Website
+ */
+export const DYNAMIC_GENERATED_ROUTES = () => [
+ ...nextJson.blogData.pagination.map(year => `en/blog/year-${year}`),
+];
diff --git a/next.data.mjs b/next.data.mjs
deleted file mode 100644
index ba84945f6c042..0000000000000
--- a/next.data.mjs
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as nextData from './next-data/index.mjs';
-
-// gather blog data and caches it
-const cachedBlogData = nextData.getBlogData();
-
-// generate the node.js releases json file
-nextData.generateNodeReleasesJson();
-
-// generate the website RSS feeds XML files
-nextData.generateWebsiteFeeds(cachedBlogData);
-
-const getNextData = async (content, { route }) => {
- // retrieves a per-route set of blog data
- // which should only include blog-related routes
- const blogData = await cachedBlogData(route);
-
- return `
- // add the mdx file content
- ${content}
-
- export const getStaticProps = () => {
- return { props: ${JSON.stringify({ ...blogData })} };
- }
- `;
-};
-
-export default getNextData;
diff --git a/next.dynamic.mjs b/next.dynamic.mjs
index 8c1ef36b109b4..037b2aab56a47 100644
--- a/next.dynamic.mjs
+++ b/next.dynamic.mjs
@@ -1,58 +1,75 @@
+'use strict';
+
import { join } from 'node:path';
import { readFileSync } from 'node:fs';
import remarkGfm from 'remark-gfm';
import { serialize } from 'next-mdx-remote/serialize';
-import * as nextConfig from './next.config.mjs';
-import * as nextLocale from './next.locales.mjs';
+import * as nextLocales from './next.locales.mjs';
+import * as nextConstants from './next.constants.mjs';
import * as nextData from './next-data/index.mjs';
-// this allows us to get the current module working directory
-const __dirname = nextData.helpers.getRelativePath(import.meta.url);
-
-// generates the current blog data
-const cachedBlogData = nextData.getBlogData();
-
-// during full static build we don't want to cover blog posts
-// as otherwise they will all get built as static pages during build time
-const ignoredPaths = nextConfig.enableStaticExport ? ['blog/**'] : [];
-
-// this retrieves all pages that are under the english locale directory
-// besides blog posts since they should not be localised at all
-const sourcePages = await nextData.helpers.getMarkdownFiles(
- __dirname,
- `pages/${nextLocale.defaultLocale.code}`,
- ignoredPaths
-);
+// allows us to run a glob to get markdown files based on a language folder
+const getPathsByLanguage = async (
+ locale = nextConstants.DEFAULT_LOCALE_CODE,
+ ignored = []
+) =>
+ nextData.helpers.getMarkdownFiles(process.cwd(), `pages/${locale}`, ignored);
/**
- * This method is responsible for generating a spanning tree of all locales that
- * do not have translated pages by checking the source default language and comparing
- * with the other languages to determine which pages do not exist.
+ * This method is responsible for generating a Collection of all availalbe paths that
+ * are served by the Website dynamically based on the Markdown pages on `pages/` folder.
*
- * The shape below is an example of a tuple entry from the resulting array
- * as you can see the paths are split by (/) meaning pathnames such as /docs/guides
- * become ["docs", "guides"] this is important as Next.js [...pathname] routing param
- * requires the pathname to be an array of the constituitng parts.
- * ['de', [["docs", "guides"], ["about"], ["download", "current"]]]
+ * Each Collection is associated to its Locale Code and containins a subset of Dictionaries
+ * that inform which pages are provided by that language and which not.
*
- * @returns {Promise<[string, string[][]][]>} a promise containing an array of tuples
+ * The non-localised pages will still be served but our runtime Markdown loader `getMarkdownFile`
+ * will recognise that the requested route should be provided via the fallback language.
*/
-export const getDynamicLocalizedPaths = async () => {
- // this compares all available languages and which pages are missing
- // by filtering out languages that exist on each localisation
- // keeping only the non translated pages as the result
- // the final result is converted to a Map by language code
- const missingPagesByLanguage = nextLocale.nonDefaultLanguages.map(locale =>
- nextData.helpers
- .getMarkdownFiles(__dirname, `pages/${locale.code}`)
- .then(files => sourcePages.filter(file => !files.includes(file)))
- .then(files => files.map(file => file.split('/')))
- .then(files => [locale.code, files])
+const getAllPaths = async () => {
+ // during full static build we don't want to cover blog posts
+ // as otherwise they will all get built as static pages during build time
+ const sourcePages = await getPathsByLanguage(
+ nextConstants.DEFAULT_LOCALE_CODE
+ );
+
+ /**
+ * This method is used to provide the list of pages that are provided by a given locale
+ * and what pages require fallback to the default locale.
+ */
+ const mergePathsWithFallback =
+ (locale = '') =>
+ (files = []) =>
+ sourcePages.map(filename => {
+ const path = filename.replace(nextConstants.MD_EXTENSION_REGEX, '');
+
+ return {
+ pathname: path,
+ filename: filename,
+ localised: files.includes(filename),
+ routeWithLocale: `${locale}/${path}`,
+ };
+ });
+
+ /**
+ * This creates an index with the information for each language
+ * and the pages that they provide in relation to the source pages
+ * and the pages that are missing in relation to the source pages.
+ *
+ * @type {[string, import('./types').RouteSegment[]][]}
+ */
+ const allAvailableMarkdownPaths = nextLocales.availableLocales.map(
+ ({ code }) =>
+ getPathsByLanguage(code)
+ .then(mergePathsWithFallback(code))
+ .then(files => [code, files])
);
- return Promise.all(missingPagesByLanguage);
+ return Promise.all(allAvailableMarkdownPaths);
};
+// A Map containing all the dynamic paths and their information
+export const allPaths = new Map(await getAllPaths());
+
/**
* This method attempts to find a matching file in the fileystem provided originally
* by `getStaticPaths` and returns the file source and filename.
@@ -61,46 +78,52 @@ export const getDynamicLocalizedPaths = async () => {
* that are non-localized pages that exist on the English locale.
*
* Hence we don't fallback for non-existing pages as it should never fall into this scenario.
- * As a security measure we also check against `validFallbackFolders` to ensure that at the last scenario
- * the base path comes from a valid base folder. Next.js will already protect against common attack vectors
+ * Next.js will already protect against common attack vectors
* such as `/../../` on the URL pathname and other methodologies
*
- * @param {string[]} paths the pathname string as an array (split by /)
- * @param {string} locale the locale code to be used for the source file
- * @returns {{ source: string, filename: string, pathname: string }} the source and filename
+ * @param {string} locale the locale code to be used
+ * @param {string} pathname the pathname string
+ * @returns {{ source: string, filename: string }} the source and filename
* @throws {Error} if the file does not exist, which should never happen
*/
-export const getMarkdownFileSource = (
- paths = [],
- locale = nextLocale.defaultLocale.code
+export const getMarkdownFile = (
+ locale = nextConstants.DEFAULT_LOCALE_CODE,
+ pathname = ''
) => {
- // we want to transform the array of pieces of path into a string
- const pathname = paths.join('/');
+ const metadata = { source: '', filename: '' };
- // gets the full pathname for the file (absolute path)
- const filename = join(__dirname, 'pages', locale, pathname);
+ const routes = allPaths.get(locale);
// We verify if the file exists within the list of allowed pages
// which prevents any malicious attempts to access non-allowed pages
// or other files that do not belong to the `sourcePages`
- if (pathname.length && sourcePages.includes(pathname)) {
- const filenameWithExtension = nextData.helpers.checkFileExists(filename, [
- '/index.md',
- '/index.mdx',
- '.md',
- '.mdx',
- ]);
-
- // Since we always will only read files that we know exist
- // we don't need to handle a possibility of an error being thrown
- // as any other case is if we don't have file system access and that should
- // then be thrown and reported
- const source = readFileSync(filenameWithExtension, 'utf8');
-
- return { source, filename: filenameWithExtension, pathname };
+ if (routes && routes.length) {
+ const route = routes.find(route => route.pathname === pathname);
+
+ if (route && route.filename) {
+ // this determines if we should be using the fallback rendering to the default locale
+ // or if we can use the current locale
+ const localeToUse = !route.localised
+ ? nextConstants.DEFAULT_LOCALE_CODE
+ : locale;
+
+ // gets the full pathname for the file (absolute path)
+ metadata.filename = join(
+ process.cwd(),
+ 'pages',
+ localeToUse,
+ route.filename
+ );
+
+ // Since we always will only read files that we know exist
+ // we don't need to handle a possibility of an error being thrown
+ // as any other case is if we don't have file system access and that should
+ // then be thrown and reported
+ metadata.source = readFileSync(metadata.filename, 'utf8');
+ }
}
- return { source: '', filename: '', pathname };
+ return metadata;
};
/**
@@ -108,27 +131,19 @@ export const getMarkdownFileSource = (
* and processes the data (parses the markdown) and generate props
* for the application to consume (`getStaticProps`)
*
- * @param {string} source the Markdown source file
- * @param {string} pathname the full path name
- * @param {string} filename the filename
- * @returns {Promise<{ notFound: boolean, props: any }>} the props for the page
+ * @returns {Promise<{ notFound: boolean, props: any; revalidate: number | boolean }>} the props for the page
*/
-export const serializeDynamicPage = async (
- source = '',
- pathname = '',
- filename = ''
-) => {
+export const getStaticProps = async (source = '', filename = '') => {
// by default a page is not found if there's no source or filename
- const staticProps = { notFound: true, props: {} };
+ const staticProps = { notFound: true, props: {}, revalidate: false };
// We only attempt to serialize data if the `source` has content and `filename` has content
// otherwise we return a 404 since this means that it is not a valid file or a file we should care about
- if (source.length && pathname.length) {
+ if (source.length && filename.length) {
// This act as a MDX "compiler" but, lightweight. It parses the Markdown
// string source into a React Component tree, and then it serializes it
// it also supports Remark plugins, and MDX components
// Note.: We use the filename extension to define the mode of execution
- // Note.: This functionality is unrelated to Nextra
const content = await serialize(source, {
parseFrontmatter: true,
mdxOptions: {
@@ -140,15 +155,6 @@ export const serializeDynamicPage = async (
// this defines the basic props that should be passed back to the `DynamicPage` component
staticProps.props = { content };
staticProps.notFound = false;
-
- // if the basePath is a blog page then we need extra static blog contextual data
- // this will be gathered on demand as needed by the `cachedBlogData` function
- if (pathname.startsWith('blog')) {
- const blogProps = await cachedBlogData(`//${pathname}`);
-
- // we add extra blog props to blog pages calculated by the `cachedBlogData`
- staticProps.props = { ...staticProps.props, ...blogProps };
- }
}
return staticProps;
diff --git a/next.json.mjs b/next.json.mjs
new file mode 100644
index 0000000000000..f57ee0fe70348
--- /dev/null
+++ b/next.json.mjs
@@ -0,0 +1,15 @@
+'use strict';
+
+// This is the static Site Configuration
+import siteConfig from './site.json' assert { type: 'json' };
+
+// This is the static Site Navigation (legacy website)
+import siteNavigation from './navigation.json' assert { type: 'json' };
+
+// This is the Website i18n Configuration
+import localeConfig from './i18n/config.json' assert { type: 'json' };
+
+// This is the generated blog data for the Node.js Website
+import blogData from './public/blog-posts-data.json' assert { type: 'json' };
+
+export { siteConfig, siteNavigation, localeConfig, blogData };
diff --git a/next.locales.mjs b/next.locales.mjs
index 8b0e670e4bdb7..9cb821483dabc 100644
--- a/next.locales.mjs
+++ b/next.locales.mjs
@@ -1,24 +1,18 @@
-// Imports the global i18n config as a static import
-import localeConfig from './i18n/config.json' assert { type: 'json' };
+'use strict';
-// Import the full Translation manifest for the Application
+import * as nextJson from './next.json.mjs';
import translations from './i18n/locales/index.mjs';
// As set of available and enabled locales for the website
// This is used for allowing us to redirect the user to any
// of the available locales that we have enabled on the website
-const availableLocales = localeConfig.filter(locale => locale.enabled);
+const availableLocales = nextJson.localeConfig.filter(locale => locale.enabled);
// This provides the default locale information for the Next.js Application
// This is marked by the unique `locale.default` property on the `en` locale
/** @type {import('./types').LocaleConfig} */
const defaultLocale = availableLocales.find(locale => locale.default);
-// This provides all available languages besides the default language
-const nonDefaultLanguages = availableLocales.filter(
- locale => locale.code !== defaultLocale.code
-);
-
/**
* Retrieves the Current Locale from the given route or URL Query
*
@@ -53,7 +47,6 @@ const getCurrentTranslations = locale => ({
export {
defaultLocale,
availableLocales,
- nonDefaultLanguages,
getCurrentLocale,
getCurrentTranslations,
};
diff --git a/package-lock.json b/package-lock.json
index ac925598fb681..d59bad00a959a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,7 +25,6 @@
"next-mdx-remote": "^4.4.1",
"next-superjson-plugin": "^0.5.8",
"next-themes": "^0.2.1",
- "nextra": "^2.6.1",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -41,6 +40,7 @@
"turbo": "^1.10.3"
},
"devDependencies": {
+ "@builder.io/partytown": "^0.8.0",
"@storybook/addon-controls": "^7.0.17",
"@storybook/addon-interactions": "^7.0.17",
"@storybook/nextjs": "^7.0.17",
@@ -2508,10 +2508,14 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
- "node_modules/@braintree/sanitize-url": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz",
- "integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg=="
+ "node_modules/@builder.io/partytown": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@builder.io/partytown/-/partytown-0.8.0.tgz",
+ "integrity": "sha512-M6H7nSMwW2dHd1/MQ+9J1Jqdw22uhl1nKv90kIiL9G7gjFVqqouQp4qSS1oZclmtW1XjAa4Q5UnbHB4iytmxZA==",
+ "dev": true,
+ "bin": {
+ "partytown": "bin/partytown.cjs"
+ }
},
"node_modules/@colors/colors": {
"version": "1.5.0",
@@ -4860,196 +4864,6 @@
"react": "^17.0.0 || ^18.0.0"
}
},
- "node_modules/@napi-rs/simple-git": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git/-/simple-git-0.1.8.tgz",
- "integrity": "sha512-BvOMdkkofTz6lEE35itJ/laUokPhr/5ToMGlOH25YnhLD2yN1KpRAT4blW9tT8281/1aZjW3xyi73bs//IrDKA==",
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@napi-rs/simple-git-android-arm-eabi": "0.1.8",
- "@napi-rs/simple-git-android-arm64": "0.1.8",
- "@napi-rs/simple-git-darwin-arm64": "0.1.8",
- "@napi-rs/simple-git-darwin-x64": "0.1.8",
- "@napi-rs/simple-git-linux-arm-gnueabihf": "0.1.8",
- "@napi-rs/simple-git-linux-arm64-gnu": "0.1.8",
- "@napi-rs/simple-git-linux-arm64-musl": "0.1.8",
- "@napi-rs/simple-git-linux-x64-gnu": "0.1.8",
- "@napi-rs/simple-git-linux-x64-musl": "0.1.8",
- "@napi-rs/simple-git-win32-arm64-msvc": "0.1.8",
- "@napi-rs/simple-git-win32-x64-msvc": "0.1.8"
- }
- },
- "node_modules/@napi-rs/simple-git-android-arm-eabi": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm-eabi/-/simple-git-android-arm-eabi-0.1.8.tgz",
- "integrity": "sha512-JJCejHBB1G6O8nxjQLT4quWCcvLpC3oRdJJ9G3MFYSCoYS8i1bWCWeU+K7Br+xT+D6s1t9q8kNJAwJv9Ygpi0g==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/simple-git-android-arm64": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-android-arm64/-/simple-git-android-arm64-0.1.8.tgz",
- "integrity": "sha512-mraHzwWBw3tdRetNOS5KnFSjvdAbNBnjFLA8I4PwTCPJj3Q4txrigcPp2d59cJ0TC51xpnPXnZjYdNwwSI9g6g==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/simple-git-darwin-arm64": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-arm64/-/simple-git-darwin-arm64-0.1.8.tgz",
- "integrity": "sha512-ufy/36eI/j4UskEuvqSH7uXtp3oXeLDmjQCfKJz3u5Vx98KmOMKrqAm2H81AB2WOtCo5mqS6PbBeUXR8BJX8lQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/simple-git-darwin-x64": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-darwin-x64/-/simple-git-darwin-x64-0.1.8.tgz",
- "integrity": "sha512-Vb21U+v3tPJNl+8JtIHHT8HGe6WZ8o1Tq3f6p+Jx9Cz71zEbcIiB9FCEMY1knS/jwQEOuhhlI9Qk7d4HY+rprA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/simple-git-linux-arm-gnueabihf": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm-gnueabihf/-/simple-git-linux-arm-gnueabihf-0.1.8.tgz",
- "integrity": "sha512-6BPTJ7CzpSm2t54mRLVaUr3S7ORJfVJoCk2rQ8v8oDg0XAMKvmQQxOsAgqKBo9gYNHJnqrOx3AEuEgvB586BuQ==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/simple-git-linux-arm64-gnu": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-gnu/-/simple-git-linux-arm64-gnu-0.1.8.tgz",
- "integrity": "sha512-qfESqUCAA/XoQpRXHptSQ8gIFnETCQt1zY9VOkplx6tgYk9PCeaX4B1Xuzrh3eZamSCMJFn+1YB9Ut8NwyGgAA==",
- "cpu": [
- "arm64"
- ],
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/simple-git-linux-arm64-musl": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-arm64-musl/-/simple-git-linux-arm64-musl-0.1.8.tgz",
- "integrity": "sha512-G80BQPpaRmQpn8dJGHp4I2/YVhWDUNJwcCrJAtAdbKFDCMyCHJBln2ERL/+IEUlIAT05zK/c1Z5WEprvXEdXow==",
- "cpu": [
- "arm64"
- ],
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/simple-git-linux-x64-gnu": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-gnu/-/simple-git-linux-x64-gnu-0.1.8.tgz",
- "integrity": "sha512-NI6o1sZYEf6vPtNWJAm9w8BxJt+LlSFW0liSjYe3lc3e4dhMfV240f0ALeqlwdIldRPaDFwZSJX5/QbS7nMzhw==",
- "cpu": [
- "x64"
- ],
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/simple-git-linux-x64-musl": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-linux-x64-musl/-/simple-git-linux-x64-musl-0.1.8.tgz",
- "integrity": "sha512-wljGAEOW41er45VTiU8kXJmO480pQKzsgRCvPlJJSCaEVBbmo6XXbFIXnZy1a2J3Zyy2IOsRB4PVkUZaNuPkZQ==",
- "cpu": [
- "x64"
- ],
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/simple-git-win32-arm64-msvc": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-arm64-msvc/-/simple-git-win32-arm64-msvc-0.1.8.tgz",
- "integrity": "sha512-QuV4QILyKPfbWHoQKrhXqjiCClx0SxbCTVogkR89BwivekqJMd9UlMxZdoCmwLWutRx4z9KmzQqokvYI5QeepA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/simple-git-win32-x64-msvc": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/@napi-rs/simple-git-win32-x64-msvc/-/simple-git-win32-x64-msvc-0.1.8.tgz",
- "integrity": "sha512-UzNS4JtjhZhZ5hRLq7BIUq+4JOwt1ThIKv11CsF1ag2l99f0123XvfEpjczKTaa94nHtjXYc2Mv9TjccBqYOew==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/@ndelangen/get-tarball": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@ndelangen/get-tarball/-/get-tarball-3.0.9.tgz",
@@ -8069,18 +7883,6 @@
"@testing-library/dom": ">=7.21.4"
}
},
- "node_modules/@theguild/remark-mermaid": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/@theguild/remark-mermaid/-/remark-mermaid-0.0.3.tgz",
- "integrity": "sha512-fccVR6o4UPUztrBjdUhM4ahwx+X7YHhoxsUoXv2vI07vz4dq+I03Ot0SjuZzDA/H7engxcb8ZxzCUEkZgGr/2g==",
- "dependencies": {
- "mermaid": "^10.2.2",
- "unist-util-visit": "^4.1.2"
- },
- "peerDependencies": {
- "react": "^18.2.0"
- }
- },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -8417,11 +8219,6 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
- "node_modules/@types/katex": {
- "version": "0.14.0",
- "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.14.0.tgz",
- "integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA=="
- },
"node_modules/@types/lodash": {
"version": "4.14.195",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
@@ -9307,11 +9104,6 @@
"node": ">=8"
}
},
- "node_modules/ansi-sequence-parser": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz",
- "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ=="
- },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -9362,25 +9154,6 @@
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
"dev": true
},
- "node_modules/arch": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
- "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
"node_modules/archy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
@@ -9400,11 +9173,6 @@
"node": ">=10"
}
},
- "node_modules/arg": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/arg/-/arg-1.0.0.tgz",
- "integrity": "sha512-Wk7TEzl1KqvTGs/uyhmHO/3XLd3t1UeU4IstvPXVzGPM522cTjqjNZ99esCkcL52sjqjo8e8CTBcWhkxvGzoAw=="
- },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -10759,124 +10527,6 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
- "node_modules/clipboardy": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.2.tgz",
- "integrity": "sha512-16KrBOV7bHmHdxcQiCvfUFYVFyEah4FI8vYT1Fr7CGSA4G+xBWMEfUEQJS1hxeHGtI9ju1Bzs9uXSbj5HZKArw==",
- "dependencies": {
- "arch": "^2.1.0",
- "execa": "^0.8.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/clipboardy/node_modules/cross-spawn": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
- "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==",
- "dependencies": {
- "lru-cache": "^4.0.1",
- "shebang-command": "^1.2.0",
- "which": "^1.2.9"
- }
- },
- "node_modules/clipboardy/node_modules/execa": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz",
- "integrity": "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==",
- "dependencies": {
- "cross-spawn": "^5.0.1",
- "get-stream": "^3.0.0",
- "is-stream": "^1.1.0",
- "npm-run-path": "^2.0.0",
- "p-finally": "^1.0.0",
- "signal-exit": "^3.0.0",
- "strip-eof": "^1.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/clipboardy/node_modules/get-stream": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
- "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/clipboardy/node_modules/is-stream": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
- "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/clipboardy/node_modules/lru-cache": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
- "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
- "dependencies": {
- "pseudomap": "^1.0.2",
- "yallist": "^2.1.2"
- }
- },
- "node_modules/clipboardy/node_modules/npm-run-path": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
- "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==",
- "dependencies": {
- "path-key": "^2.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/clipboardy/node_modules/path-key": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
- "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/clipboardy/node_modules/shebang-command": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
- "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
- "dependencies": {
- "shebang-regex": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/clipboardy/node_modules/shebang-regex": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
- "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/clipboardy/node_modules/which": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
- "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "which": "bin/which"
- }
- },
- "node_modules/clipboardy/node_modules/yallist": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
- "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
- },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -11311,14 +10961,6 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true
},
- "node_modules/cose-base": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
- "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
- "dependencies": {
- "layout-base": "^1.0.0"
- }
- },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -11631,468 +11273,23 @@
"node": ">=0.8"
}
},
- "node_modules/cytoscape": {
- "version": "3.25.0",
- "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.25.0.tgz",
- "integrity": "sha512-7MW3Iz57mCUo6JQCho6CmPBCbTlJr7LzyEtIkutG255HLVd4XuBg2I9BkTZLI/e4HoaOB/BiAzXuQybQ95+r9Q==",
+ "node_modules/damerau-levenshtein": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
+ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
+ "dev": true
+ },
+ "node_modules/data-urls": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz",
+ "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==",
"dependencies": {
- "heap": "^0.2.6",
- "lodash": "^4.17.21"
+ "abab": "^2.0.6",
+ "whatwg-mimetype": "^3.0.0",
+ "whatwg-url": "^12.0.0"
},
"engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/cytoscape-cose-bilkent": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
- "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
- "dependencies": {
- "cose-base": "^1.0.0"
- },
- "peerDependencies": {
- "cytoscape": "^3.2.0"
- }
- },
- "node_modules/cytoscape-fcose": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
- "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
- "dependencies": {
- "cose-base": "^2.2.0"
- },
- "peerDependencies": {
- "cytoscape": "^3.2.0"
- }
- },
- "node_modules/cytoscape-fcose/node_modules/cose-base": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
- "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
- "dependencies": {
- "layout-base": "^2.0.0"
- }
- },
- "node_modules/cytoscape-fcose/node_modules/layout-base": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
- "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="
- },
- "node_modules/d3": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz",
- "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==",
- "dependencies": {
- "d3-array": "3",
- "d3-axis": "3",
- "d3-brush": "3",
- "d3-chord": "3",
- "d3-color": "3",
- "d3-contour": "4",
- "d3-delaunay": "6",
- "d3-dispatch": "3",
- "d3-drag": "3",
- "d3-dsv": "3",
- "d3-ease": "3",
- "d3-fetch": "3",
- "d3-force": "3",
- "d3-format": "3",
- "d3-geo": "3",
- "d3-hierarchy": "3",
- "d3-interpolate": "3",
- "d3-path": "3",
- "d3-polygon": "3",
- "d3-quadtree": "3",
- "d3-random": "3",
- "d3-scale": "4",
- "d3-scale-chromatic": "3",
- "d3-selection": "3",
- "d3-shape": "3",
- "d3-time": "3",
- "d3-time-format": "4",
- "d3-timer": "3",
- "d3-transition": "3",
- "d3-zoom": "3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-array": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
- "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
- "dependencies": {
- "internmap": "1 - 2"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-axis": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
- "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-brush": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
- "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
- "dependencies": {
- "d3-dispatch": "1 - 3",
- "d3-drag": "2 - 3",
- "d3-interpolate": "1 - 3",
- "d3-selection": "3",
- "d3-transition": "3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-chord": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
- "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
- "dependencies": {
- "d3-path": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-color": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
- "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-contour": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
- "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
- "dependencies": {
- "d3-array": "^3.2.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-delaunay": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
- "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
- "dependencies": {
- "delaunator": "5"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-dispatch": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
- "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-drag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
- "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
- "dependencies": {
- "d3-dispatch": "1 - 3",
- "d3-selection": "3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-dsv": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
- "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
- "dependencies": {
- "commander": "7",
- "iconv-lite": "0.6",
- "rw": "1"
- },
- "bin": {
- "csv2json": "bin/dsv2json.js",
- "csv2tsv": "bin/dsv2dsv.js",
- "dsv2dsv": "bin/dsv2dsv.js",
- "dsv2json": "bin/dsv2json.js",
- "json2csv": "bin/json2dsv.js",
- "json2dsv": "bin/json2dsv.js",
- "json2tsv": "bin/json2dsv.js",
- "tsv2csv": "bin/dsv2dsv.js",
- "tsv2json": "bin/dsv2json.js"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-dsv/node_modules/commander": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
- "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/d3-dsv/node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/d3-ease": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
- "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-fetch": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
- "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
- "dependencies": {
- "d3-dsv": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-force": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
- "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
- "dependencies": {
- "d3-dispatch": "1 - 3",
- "d3-quadtree": "1 - 3",
- "d3-timer": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-format": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
- "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-geo": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz",
- "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==",
- "dependencies": {
- "d3-array": "2.5.0 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-hierarchy": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
- "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-interpolate": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
- "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
- "dependencies": {
- "d3-color": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-path": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
- "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-polygon": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
- "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-quadtree": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
- "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-random": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
- "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-scale": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
- "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
- "dependencies": {
- "d3-array": "2.10.0 - 3",
- "d3-format": "1 - 3",
- "d3-interpolate": "1.2.0 - 3",
- "d3-time": "2.1.1 - 3",
- "d3-time-format": "2 - 4"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-scale-chromatic": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz",
- "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==",
- "dependencies": {
- "d3-color": "1 - 3",
- "d3-interpolate": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-selection": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
- "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-shape": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
- "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
- "dependencies": {
- "d3-path": "^3.1.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-time": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
- "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
- "dependencies": {
- "d3-array": "2 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-time-format": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
- "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
- "dependencies": {
- "d3-time": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-timer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
- "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-transition": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
- "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
- "dependencies": {
- "d3-color": "1 - 3",
- "d3-dispatch": "1 - 3",
- "d3-ease": "1 - 3",
- "d3-interpolate": "1 - 3",
- "d3-timer": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- },
- "peerDependencies": {
- "d3-selection": "2 - 3"
- }
- },
- "node_modules/d3-zoom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
- "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
- "dependencies": {
- "d3-dispatch": "1 - 3",
- "d3-drag": "2 - 3",
- "d3-interpolate": "1 - 3",
- "d3-selection": "2 - 3",
- "d3-transition": "2 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/dagre-d3-es": {
- "version": "7.0.10",
- "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz",
- "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==",
- "dependencies": {
- "d3": "^7.8.2",
- "lodash-es": "^4.17.21"
- }
- },
- "node_modules/damerau-levenshtein": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
- "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
- "dev": true
- },
- "node_modules/data-urls": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz",
- "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==",
- "dependencies": {
- "abab": "^2.0.6",
- "whatwg-mimetype": "^3.0.0",
- "whatwg-url": "^12.0.0"
- },
- "engines": {
- "node": ">=14"
+ "node": ">=14"
}
},
"node_modules/date-fns": {
@@ -12111,11 +11308,6 @@
"url": "https://opencollective.com/date-fns"
}
},
- "node_modules/dayjs": {
- "version": "1.11.8",
- "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.8.tgz",
- "integrity": "sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ=="
- },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -12491,14 +11683,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/delaunator": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz",
- "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==",
- "dependencies": {
- "robust-predicates": "^3.0.0"
- }
- },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -12888,11 +12072,6 @@
"integrity": "sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw==",
"dev": true
},
- "node_modules/elkjs": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz",
- "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ=="
- },
"node_modules/elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@@ -13901,6 +13080,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
@@ -14018,17 +13198,6 @@
"node": ">= 8"
}
},
- "node_modules/estree-util-value-to-estree": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-1.3.0.tgz",
- "integrity": "sha512-Y+ughcF9jSUJvncXwqRageavjrNPAI+1M/L3BI3PyLp1nmgYTGUXU6t5z1Y7OWuThoDdhPME07bQU+d5LxdJqw==",
- "dependencies": {
- "is-plain-obj": "^3.0.0"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
"node_modules/estree-util-visit": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-1.2.1.tgz",
@@ -14256,6 +13425,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+ "dev": true,
"dependencies": {
"is-extendable": "^0.1.0"
},
@@ -15157,11 +14327,6 @@
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
},
- "node_modules/github-slugger": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
- "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
- },
"node_modules/glob": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.0.tgz",
@@ -15369,6 +14534,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
+ "dev": true,
"dependencies": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
@@ -15383,6 +14549,7 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
@@ -15391,6 +14558,7 @@
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -15570,33 +14738,6 @@
"node": ">=4"
}
},
- "node_modules/hash-obj": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/hash-obj/-/hash-obj-4.0.0.tgz",
- "integrity": "sha512-FwO1BUVWkyHasWDW4S8o0ssQXjvyghLV2rfVhnN36b2bbcj45eGiuzdn9XOvOpjV3TKQD7Gm2BWNXdE9V4KKYg==",
- "dependencies": {
- "is-obj": "^3.0.0",
- "sort-keys": "^5.0.0",
- "type-fest": "^1.0.2"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/hash-obj/node_modules/type-fest": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
- "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
@@ -15632,93 +14773,6 @@
"node": ">=8"
}
},
- "node_modules/hast-util-from-dom": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-4.2.0.tgz",
- "integrity": "sha512-t1RJW/OpJbCAJQeKi3Qrj1cAOLA0+av/iPFori112+0X7R3wng+jxLA+kXec8K4szqPRGI8vPxbbpEYvvpwaeQ==",
- "dependencies": {
- "hastscript": "^7.0.0",
- "web-namespaces": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-from-html": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-1.0.2.tgz",
- "integrity": "sha512-LhrTA2gfCbLOGJq2u/asp4kwuG0y6NhWTXiPKP+n0qNukKy7hc10whqqCFfyvIA1Q5U5d0sp9HhNim9gglEH4A==",
- "dependencies": {
- "@types/hast": "^2.0.0",
- "hast-util-from-parse5": "^7.0.0",
- "parse5": "^7.0.0",
- "vfile": "^5.0.0",
- "vfile-message": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-from-html-isomorphic": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-1.0.0.tgz",
- "integrity": "sha512-Yu480AKeOEN/+l5LA674a+7BmIvtDj24GvOt7MtQWuhzUwlaaRWdEPXAh3Qm5vhuthpAipFb2vTetKXWOjmTvw==",
- "dependencies": {
- "@types/hast": "^2.0.0",
- "hast-util-from-dom": "^4.0.0",
- "hast-util-from-html": "^1.0.0",
- "unist-util-remove-position": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-from-parse5": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz",
- "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==",
- "dependencies": {
- "@types/hast": "^2.0.0",
- "@types/unist": "^2.0.0",
- "hastscript": "^7.0.0",
- "property-information": "^6.0.0",
- "vfile": "^5.0.0",
- "vfile-location": "^4.0.0",
- "web-namespaces": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-is-element": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz",
- "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==",
- "dependencies": {
- "@types/hast": "^2.0.0",
- "@types/unist": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-parse-selector": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz",
- "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==",
- "dependencies": {
- "@types/hast": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/hast-util-to-estree": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-2.3.3.tgz",
@@ -15750,21 +14804,6 @@
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
},
- "node_modules/hast-util-to-text": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-3.1.2.tgz",
- "integrity": "sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw==",
- "dependencies": {
- "@types/hast": "^2.0.0",
- "@types/unist": "^2.0.0",
- "hast-util-is-element": "^2.0.0",
- "unist-util-find-after": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/hast-util-whitespace": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz",
@@ -15774,22 +14813,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/hastscript": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz",
- "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==",
- "dependencies": {
- "@types/hast": "^2.0.0",
- "comma-separated-tokens": "^2.0.0",
- "hast-util-parse-selector": "^3.0.0",
- "property-information": "^6.0.0",
- "space-separated-tokens": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -15799,11 +14822,6 @@
"he": "bin/he"
}
},
- "node_modules/heap": {
- "version": "0.2.7",
- "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
- "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
- },
"node_modules/highlight.js": {
"version": "11.8.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
@@ -16291,14 +15309,6 @@
"node": ">= 0.4"
}
},
- "node_modules/internmap": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
- "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/interpret": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
@@ -16533,6 +15543,7 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -16709,17 +15720,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-obj": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz",
- "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/is-path-cwd": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
@@ -16738,17 +15738,6 @@
"node": ">=8"
}
},
- "node_modules/is-plain-obj": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
- "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
@@ -20068,11 +19057,6 @@
"node": ">=6"
}
},
- "node_modules/jsonc-parser": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
- "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
- },
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@@ -20098,38 +19082,11 @@
"node": ">=4.0"
}
},
- "node_modules/katex": {
- "version": "0.16.8",
- "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz",
- "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==",
- "funding": [
- "https://opencollective.com/katex",
- "https://github.com/sponsors/katex"
- ],
- "dependencies": {
- "commander": "^8.3.0"
- },
- "bin": {
- "katex": "cli.js"
- }
- },
- "node_modules/katex/node_modules/commander": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
- "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
- "engines": {
- "node": ">= 12"
- }
- },
- "node_modules/khroma": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.0.0.tgz",
- "integrity": "sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g=="
- },
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -20173,11 +19130,6 @@
"language-subtag-registry": "~0.3.2"
}
},
- "node_modules/layout-base": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
- "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="
- },
"node_modules/lazy-universal-dotenv": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-4.0.0.tgz",
@@ -20269,12 +19221,8 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
- },
- "node_modules/lodash-es": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
- "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
@@ -20288,11 +19236,6 @@
"integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
"dev": true
},
- "node_modules/lodash.get": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
- "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
- },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -20811,20 +19754,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/mdast-util-math": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-2.0.2.tgz",
- "integrity": "sha512-8gmkKVp9v6+Tgjtq6SYx9kGPpTf6FVYRa53/DLh479aldR9AyP48qeVOgNZ5X7QUK7nOy4yw7vg6mbiGcs9jWQ==",
- "dependencies": {
- "@types/mdast": "^3.0.0",
- "longest-streak": "^3.0.0",
- "mdast-util-to-markdown": "^1.3.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/mdast-util-mdx": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-2.0.1.tgz",
@@ -21496,123 +20425,18 @@
"dev": true
},
"node_modules/merge-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
- "dev": true
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/mermaid": {
- "version": "10.2.3",
- "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.2.3.tgz",
- "integrity": "sha512-cMVE5s9PlQvOwfORkyVpr5beMsLdInrycAosdr+tpZ0WFjG4RJ/bUHST7aTgHNJbujHkdBRAm+N50P3puQOfPw==",
- "dependencies": {
- "@braintree/sanitize-url": "^6.0.2",
- "cytoscape": "^3.23.0",
- "cytoscape-cose-bilkent": "^4.1.0",
- "cytoscape-fcose": "^2.1.0",
- "d3": "^7.4.0",
- "dagre-d3-es": "7.0.10",
- "dayjs": "^1.11.7",
- "dompurify": "3.0.3",
- "elkjs": "^0.8.2",
- "khroma": "^2.0.0",
- "lodash-es": "^4.17.21",
- "mdast-util-from-markdown": "^1.3.0",
- "non-layered-tidy-tree-layout": "^2.0.2",
- "stylis": "^4.1.3",
- "ts-dedent": "^2.2.0",
- "uuid": "^9.0.0",
- "web-worker": "^1.2.0"
- }
- },
- "node_modules/mermaid/node_modules/mdast-util-from-markdown": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz",
- "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==",
- "dependencies": {
- "@types/mdast": "^3.0.0",
- "@types/unist": "^2.0.0",
- "decode-named-character-reference": "^1.0.0",
- "mdast-util-to-string": "^3.1.0",
- "micromark": "^3.0.0",
- "micromark-util-decode-numeric-character-reference": "^1.0.0",
- "micromark-util-decode-string": "^1.0.0",
- "micromark-util-normalize-identifier": "^1.0.0",
- "micromark-util-symbol": "^1.0.0",
- "micromark-util-types": "^1.0.0",
- "unist-util-stringify-position": "^3.0.0",
- "uvu": "^0.5.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mermaid/node_modules/mdast-util-to-string": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz",
- "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==",
- "dependencies": {
- "@types/mdast": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mermaid/node_modules/micromark": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz",
- "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "dependencies": {
- "@types/debug": "^4.0.0",
- "debug": "^4.0.0",
- "decode-named-character-reference": "^1.0.0",
- "micromark-core-commonmark": "^1.0.1",
- "micromark-factory-space": "^1.0.0",
- "micromark-util-character": "^1.0.0",
- "micromark-util-chunked": "^1.0.0",
- "micromark-util-combine-extensions": "^1.0.0",
- "micromark-util-decode-numeric-character-reference": "^1.0.0",
- "micromark-util-encode": "^1.0.0",
- "micromark-util-normalize-identifier": "^1.0.0",
- "micromark-util-resolve-all": "^1.0.0",
- "micromark-util-sanitize-uri": "^1.0.0",
- "micromark-util-subtokenize": "^1.0.0",
- "micromark-util-symbol": "^1.0.0",
- "micromark-util-types": "^1.0.1",
- "uvu": "^0.5.0"
- }
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
},
- "node_modules/mermaid/node_modules/unist-util-stringify-position": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz",
- "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==",
- "dependencies": {
- "@types/unist": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
}
},
"node_modules/methods": {
@@ -21791,29 +20615,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/micromark-extension-math": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-2.1.2.tgz",
- "integrity": "sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==",
- "dependencies": {
- "@types/katex": "^0.16.0",
- "katex": "^0.16.0",
- "micromark-factory-space": "^1.0.0",
- "micromark-util-character": "^1.0.0",
- "micromark-util-symbol": "^1.0.0",
- "micromark-util-types": "^1.0.0",
- "uvu": "^0.5.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/micromark-extension-math/node_modules/@types/katex": {
- "version": "0.16.0",
- "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.0.tgz",
- "integrity": "sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw=="
- },
"node_modules/micromark-extension-mdx-expression": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-1.0.8.tgz",
@@ -22757,44 +21558,6 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/nextra": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/nextra/-/nextra-2.8.0.tgz",
- "integrity": "sha512-WyRNzw1IM/eF3M1H3LSsbZH97QsYYgj8upjx0f8hY6GspmPyPRAvBBscmXRt+7vye2oIYjfVwSiD1rj9amqq9Q==",
- "dependencies": {
- "@mdx-js/mdx": "^2.3.0",
- "@mdx-js/react": "^2.3.0",
- "@napi-rs/simple-git": "^0.1.8",
- "@theguild/remark-mermaid": "^0.0.3",
- "clsx": "^1.2.1",
- "github-slugger": "^2.0.0",
- "graceful-fs": "^4.2.11",
- "gray-matter": "^4.0.3",
- "katex": "^0.16.7",
- "lodash.get": "^4.4.2",
- "next-mdx-remote": "^4.2.1",
- "p-limit": "^3.1.0",
- "rehype-katex": "^6.0.3",
- "rehype-pretty-code": "0.9.9",
- "remark-gfm": "^3.0.1",
- "remark-math": "^5.1.1",
- "remark-reading-time": "^2.0.1",
- "shiki": "^0.14.2",
- "slash": "^3.0.0",
- "title": "^3.5.3",
- "unist-util-remove": "^3.1.1",
- "unist-util-visit": "^4.1.1",
- "zod": "^3.20.2"
- },
- "engines": {
- "node": ">=16"
- },
- "peerDependencies": {
- "next": ">=9.5.3",
- "react": ">=16.13.1",
- "react-dom": ">=16.13.1"
- }
- },
"node_modules/no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
@@ -22965,11 +21728,6 @@
"integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==",
"dev": true
},
- "node_modules/non-layered-tidy-tree-layout": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
- "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="
- },
"node_modules/nopt": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
@@ -23556,18 +22314,11 @@
"node": ">=0.10.0"
}
},
- "node_modules/p-finally": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
- "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
@@ -23717,11 +22468,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/parse-numeric-range": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz",
- "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ=="
- },
"node_modules/parse-passwd": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
@@ -24464,11 +23210,6 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true
},
- "node_modules/pseudomap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
- "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="
- },
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -25099,11 +23840,6 @@
"node": ">=8.10.0"
}
},
- "node_modules/reading-time": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz",
- "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg=="
- },
"node_modules/recast": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.23.2.tgz",
@@ -25259,39 +23995,6 @@
"jsesc": "bin/jsesc"
}
},
- "node_modules/rehype-katex": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-6.0.3.tgz",
- "integrity": "sha512-ByZlRwRUcWegNbF70CVRm2h/7xy7jQ3R9LaY4VVSvjnoVWwWVhNL60DiZsBpC5tSzYQOCvDbzncIpIjPZWodZA==",
- "dependencies": {
- "@types/hast": "^2.0.0",
- "@types/katex": "^0.14.0",
- "hast-util-from-html-isomorphic": "^1.0.0",
- "hast-util-to-text": "^3.1.0",
- "katex": "^0.16.0",
- "unist-util-visit": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/rehype-pretty-code": {
- "version": "0.9.9",
- "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.9.9.tgz",
- "integrity": "sha512-mlU2Qgupn9MMK31CTmWk0Ie5Vp0od+jh2vCkGDBMlPAMeAvYASn6Ois6xRmosutMT4yH/COc3R4r/PELpuUoWg==",
- "dependencies": {
- "@types/hast": "^2.0.0",
- "hash-obj": "^4.0.0",
- "parse-numeric-range": "^1.3.0"
- },
- "engines": {
- "node": ">=16"
- },
- "peerDependencies": {
- "shiki": "*"
- }
- },
"node_modules/relateurl": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
@@ -26146,21 +24849,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/remark-math": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-5.1.1.tgz",
- "integrity": "sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==",
- "dependencies": {
- "@types/mdast": "^3.0.0",
- "mdast-util-math": "^2.0.0",
- "micromark-extension-math": "^2.0.0",
- "unified": "^10.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/remark-mdx": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-2.3.0.tgz",
@@ -26373,44 +25061,6 @@
"prettier": ">=1.0.0"
}
},
- "node_modules/remark-reading-time": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/remark-reading-time/-/remark-reading-time-2.0.1.tgz",
- "integrity": "sha512-fy4BKy9SRhtYbEHvp6AItbRTnrhiDGbqLQTSYVbQPGuRCncU1ubSsh9p/W5QZSxtYcUXv8KGL0xBgPLyNJA1xw==",
- "dependencies": {
- "estree-util-is-identifier-name": "^2.0.0",
- "estree-util-value-to-estree": "^1.3.0",
- "reading-time": "^1.3.0",
- "unist-util-visit": "^3.1.0"
- }
- },
- "node_modules/remark-reading-time/node_modules/unist-util-visit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz",
- "integrity": "sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==",
- "dependencies": {
- "@types/unist": "^2.0.0",
- "unist-util-is": "^5.0.0",
- "unist-util-visit-parents": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/remark-reading-time/node_modules/unist-util-visit-parents": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz",
- "integrity": "sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==",
- "dependencies": {
- "@types/unist": "^2.0.0",
- "unist-util-is": "^5.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/remark-rehype": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz",
@@ -26799,11 +25449,6 @@
"inherits": "^2.0.1"
}
},
- "node_modules/robust-predicates": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
- "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
- },
"node_modules/rrweb-cssom": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
@@ -26847,11 +25492,6 @@
"queue-microtask": "^1.2.2"
}
},
- "node_modules/rw": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
- "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="
- },
"node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
@@ -27011,6 +25651,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
+ "dev": true,
"dependencies": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
@@ -27276,17 +25917,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/shiki": {
- "version": "0.14.3",
- "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.3.tgz",
- "integrity": "sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g==",
- "dependencies": {
- "ansi-sequence-parser": "^1.1.0",
- "jsonc-parser": "^3.2.0",
- "vscode-oniguruma": "^1.7.0",
- "vscode-textmate": "^8.0.0"
- }
- },
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -27304,7 +25934,8 @@
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
},
"node_modules/simple-concat": {
"version": "1.0.1",
@@ -27393,6 +26024,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -27420,31 +26052,6 @@
"integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==",
"dev": true
},
- "node_modules/sort-keys": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.0.0.tgz",
- "integrity": "sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==",
- "dependencies": {
- "is-plain-obj": "^4.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/sort-keys/node_modules/is-plain-obj": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
- "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -27559,7 +26166,8 @@
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
},
"node_modules/stack-utils": {
"version": "2.0.6",
@@ -27856,14 +26464,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/strip-eof": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
- "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -28677,92 +27278,6 @@
"node": ">=0.6.0"
}
},
- "node_modules/title": {
- "version": "3.5.3",
- "resolved": "https://registry.npmjs.org/title/-/title-3.5.3.tgz",
- "integrity": "sha512-20JyowYglSEeCvZv3EZ0nZ046vLarO37prvV0mbtQV7C8DJPGgN967r8SJkqd3XK3K3lD3/Iyfp3avjfil8Q2Q==",
- "dependencies": {
- "arg": "1.0.0",
- "chalk": "2.3.0",
- "clipboardy": "1.2.2",
- "titleize": "1.0.0"
- },
- "bin": {
- "title": "bin/title.js"
- }
- },
- "node_modules/title/node_modules/ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dependencies": {
- "color-convert": "^1.9.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/title/node_modules/chalk": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
- "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==",
- "dependencies": {
- "ansi-styles": "^3.1.0",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^4.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/title/node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/title/node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
- },
- "node_modules/title/node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/title/node_modules/has-flag": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
- "integrity": "sha512-P+1n3MnwjR/Epg9BBo1KT8qbye2g2Ou4sFumihwt6I4tsUX7jnLcX4BTOSKg/B1ZrIYMN9FcEnG4x5a7NB8Eng==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/title/node_modules/supports-color": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
- "integrity": "sha512-ycQR/UbvI9xIlEdQT1TQqwoXtEldExbCEAJgRo5YXlmSKjv6ThHnP9/vwGa1gr19Gfw+LkFd7KqYMhzrRC5JYw==",
- "dependencies": {
- "has-flag": "^2.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/titleize": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/titleize/-/titleize-1.0.0.tgz",
- "integrity": "sha512-TARUb7z1pGvlLxgPk++7wJ6aycXF3GJ0sNSBTAsTuJrQG5QuZlkUQP+zl+nbjAh4gMX9yDw9ZYklMd7vAfJKEw==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -28884,6 +27399,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
"integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+ "dev": true,
"engines": {
"node": ">=6.10"
}
@@ -29515,19 +28031,6 @@
"node": ">=8"
}
},
- "node_modules/unist-util-find-after": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-4.0.1.tgz",
- "integrity": "sha512-QO/PuPMm2ERxC6vFXEPtmAutOopy5PknD+Oq64gGwxKtk4xwo9Z97t9Av1obPmGU0IyTa6EKYUfTrK2QJS3Ozw==",
- "dependencies": {
- "@types/unist": "^2.0.0",
- "unist-util-is": "^5.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/unist-util-generated": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz",
@@ -29586,20 +28089,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/unist-util-remove": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-3.1.1.tgz",
- "integrity": "sha512-kfCqZK5YVY5yEa89tvpl7KnBBHu2c6CzMkqHUrlOqaRgGOMp0sMvwWOVrbAtj03KhovQB7i96Gda72v/EFE0vw==",
- "dependencies": {
- "@types/unist": "^2.0.0",
- "unist-util-is": "^5.0.0",
- "unist-util-visit-parents": "^5.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/unist-util-remove-position": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-4.0.2.tgz",
@@ -29808,6 +28297,7 @@
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+ "dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
@@ -29889,6 +28379,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz",
"integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==",
+ "dev": true,
"dependencies": {
"@types/unist": "^2.0.0",
"vfile": "^5.0.0"
@@ -30072,16 +28563,6 @@
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"dev": true
},
- "node_modules/vscode-oniguruma": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz",
- "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA=="
- },
- "node_modules/vscode-textmate": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz",
- "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg=="
- },
"node_modules/w3c-xmlserializer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
@@ -30242,20 +28723,6 @@
"defaults": "^1.0.3"
}
},
- "node_modules/web-namespaces": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
- "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/web-worker": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz",
- "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA=="
- },
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
@@ -30778,6 +29245,7 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
"engines": {
"node": ">=10"
},
diff --git a/package.json b/package.json
index 5242dad2d21f9..0365bfa888a42 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,6 @@
"next-mdx-remote": "^4.4.1",
"next-superjson-plugin": "^0.5.8",
"next-themes": "^0.2.1",
- "nextra": "^2.6.1",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -68,6 +67,7 @@
"turbo": "^1.10.3"
},
"devDependencies": {
+ "@builder.io/partytown": "^0.8.0",
"@storybook/addon-controls": "^7.0.17",
"@storybook/addon-interactions": "^7.0.17",
"@storybook/nextjs": "^7.0.17",
@@ -96,6 +96,7 @@
"feed": "^4.2.2",
"gray-matter": "^4.0.3",
"handlebars": "^4.7.7",
+ "husky": "^8.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"next-sitemap": "^4.1.3",
@@ -110,7 +111,6 @@
"stylelint-selector-bem-pattern": "^2.1.1",
"typescript": "^5.0.4",
"user-agent-data-types": "^0.3.1",
- "wait-on": "^7.0.1",
- "husky": "^8.0.0"
+ "wait-on": "^7.0.1"
}
}
diff --git a/pages/404.mdx b/pages/404.mdx
deleted file mode 100644
index 473208dabea78..0000000000000
--- a/pages/404.mdx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-export default function NotFound() {
- return (
- <>
-
-
-
-
-
-
- >
- );
-}
diff --git a/pages/404.tsx b/pages/404.tsx
new file mode 100644
index 0000000000000..f2c341e8c1363
--- /dev/null
+++ b/pages/404.tsx
@@ -0,0 +1,15 @@
+import { FormattedMessage } from 'react-intl';
+import Theme from '../theme';
+
+const NotFound = () => (
+
+
+
+
+
+
+
+
+);
+
+export default NotFound;
diff --git a/pages/[...path].tsx b/pages/[...path].tsx
new file mode 100644
index 0000000000000..9b8b0dcec12e6
--- /dev/null
+++ b/pages/[...path].tsx
@@ -0,0 +1,89 @@
+import Theme from '../theme';
+import * as nextDynamic from '../next.dynamic.mjs';
+import * as nextConstants from '../next.constants.mjs';
+import type { GetStaticPaths, GetStaticProps } from 'next';
+import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
+
+type StaticParams = { path: string[] };
+type StaticProps = { content: MDXRemoteSerializeResult };
+
+// This is a small utility that allows us to quickly separate locale from the remaning pathname
+const getLocaleAndPathname = ([locale, ...path]: string[] = []) => [
+ locale,
+ path.join('/'),
+];
+
+// This tests if the current pathname matches any expression that belongs
+// to the list of ignored routes and if it does we return `true` to indicate that
+const shouldIgnoreRoute = (pathname: string) =>
+ (pathname.length &&
+ nextConstants.DYNAMIC_ROUTES_IGNORES.some(e => e.test(pathname))) ||
+ false;
+
+// This tests if the current pathname matches any sort of rewrite rule
+// and if it does we return a the replacement expression for the pathname
+const getRouteWrite = (pathname: string) =>
+ (pathname.length &&
+ nextConstants.DYNAMIC_ROUTES_REWRITES.find(([e]) => e.test(pathname))) ||
+ [];
+
+// This maps a pathname into an actual route object that can be used
+const mapPathnameToRoute = (pathname: string) => ({
+ params: { path: pathname.split('/') },
+});
+
+// This method receives the props from `getStaticProps` and renders/builds the Markdown
+// content on demand by loading the file on the server-side and serializing the Markdown/MDX content
+export const getStaticProps: GetStaticProps<
+ StaticProps,
+ StaticParams
+> = async ({ params = { path: [] } }) => {
+ const [locale, pathname] = getLocaleAndPathname(params.path);
+
+ // Retrieves and rewriting rule if the pathname matches any rule
+ const [, rewriteRule] = getRouteWrite(pathname);
+
+ // We retrieve the source of the Markdown file by doing an educated guess
+ // of what possible files could be the source of the page, since the extension
+ // context is lost from `getStaticProps` as a limitation of Next.js itself
+ const { source, filename } = nextDynamic.getMarkdownFile(
+ locale,
+ rewriteRule ? rewriteRule(pathname) : pathname
+ );
+
+ // This parses the actual Markdown content and returns a full set of props
+ // to be passed to the base page (`DynamicPage`) which will render the Markdown
+ const staticProps = await nextDynamic.getStaticProps(source, filename);
+
+ // This checks if either we already the route does not exist or if we should ignore
+ // this route because it's on our ignored list
+ staticProps.notFound = staticProps.notFound || shouldIgnoreRoute(pathname);
+
+ // We add the extra `params` to the props as they're used within the `DynamicPage`
+ return staticProps;
+};
+
+// This method is used to retrieve all native statically supported pages (SCR) that
+// we want to provide during build-time + allow fallback for dynamic pages during (ISR)
+export const getStaticPaths: GetStaticPaths = async () => {
+ const paths = [];
+
+ // Retrieves all the dynamic generated paths
+ const dynamicRoutes = nextConstants.DYNAMIC_GENERATED_ROUTES();
+
+ // Retrieves all the static paths (from next.dynamic.mjs)
+ const staticPaths = [...nextDynamic.allPaths.values()]
+ .flat()
+ .filter(route => nextConstants.STATIC_ROUTES_IGNORES.every(e => !e(route)))
+ .map(route => route.routeWithLocale);
+
+ if (nextConstants.ENABLE_STATIC_EXPORT) {
+ paths.push(...staticPaths);
+
+ paths.push(...dynamicRoutes);
+ }
+
+ return { paths: paths.sort().map(mapPathnameToRoute), fallback: 'blocking' };
+};
+
+export default Theme;
diff --git a/pages/[locale]/[...pathname].tsx b/pages/[locale]/[...pathname].tsx
deleted file mode 100644
index 716820a6aa227..0000000000000
--- a/pages/[locale]/[...pathname].tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import DynamicTheme from '../../theme.dynamic';
-import * as nextConfig from '../../next.config.mjs';
-import * as nextDynamic from '../../next.dynamic.mjs';
-import type { GetStaticPaths, GetStaticProps } from 'next';
-import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
-
-type StaticParams = { pathname: string[]; locale: string };
-type StaticProps = { content: MDXRemoteSerializeResult };
-
-// This method receives the props from `getStaticProps` and renders/builds the Markdown
-// content on demand by loading the file on the server-side and serializing the Markdown/MDX content
-export const getStaticProps: GetStaticProps<
- StaticProps,
- StaticParams
-> = async ({ params = { pathname: [], locale: 'en' } }) => {
- // We retrieve the source of the Markdown file by doing an educated guess
- // of what possible files could be the source of the page, since the extension
- // context is lost from `getStaticProps` as a limitation of Next.js itself
- const { source, filename, pathname } = nextDynamic.getMarkdownFileSource(
- params.pathname
- );
-
- // This parses the actual Markdown content and returns a full set of props
- // to be passed to the base page (`DynamicPage`) which will render the Markdown
- const staticProps = await nextDynamic.serializeDynamicPage(
- source,
- pathname,
- filename
- );
-
- // We add the extra `params` to the props as they're used within the `DynamicPage`
- return staticProps;
-};
-
-// This method is called once during build-time and retrieves a full path
-// of all pages on each locale that were not translated yet.
-// allowing us to generate localized pages with only the content fallbacking to English
-// Note.: during full ISR blog pages are also supported as they do not increase weight on the build time
-// but on static-mode the blog pages will not be generated/included on the build.
-export const getStaticPaths: GetStaticPaths = async () => {
- // During full-static builds we want to generate all minimal required pages
- // as static exports cannot leverage from ISR (Incremental Static Builds)
- // whereas on non-static exports we don't need to build these paths at build-time
- // and we can fully leverage from ISR builds without the need of pre-building these static pages
- if (nextConfig.enableStaticExport) {
- const nonLocalizedPages = await nextDynamic.getDynamicLocalizedPaths();
-
- const dynamicPathsByLocale = nonLocalizedPages.map(([locale, pages]) =>
- pages.map(pathname => ({ params: { locale, pathname } }))
- );
-
- return { paths: dynamicPathsByLocale.flat(), fallback: 'blocking' };
- }
-
- return { paths: [], fallback: 'blocking' };
-};
-
-export default DynamicTheme;
diff --git a/pages/[locale]/blog/[year].tsx b/pages/[locale]/blog/[year].tsx
deleted file mode 100644
index e15648e426e79..0000000000000
--- a/pages/[locale]/blog/[year].tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import DynamicTheme from '../../../theme.dynamic';
-import * as nextConfig from '../../../next.config.mjs';
-import * as nextDynamic from '../../../next.dynamic.mjs';
-import * as nextLocales from '../../../next.locales.mjs';
-import * as nextData from '../../../next-data/index.mjs';
-import type { GetStaticPaths, GetStaticProps } from 'next';
-import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
-
-// generates the current blog data
-const cachedBlogData = nextData.getBlogData();
-
-// get all the available years from the blog data
-const availableYears = await nextData.getBlogIndexPages(cachedBlogData);
-
-type StaticParams = { year: string; locale: string };
-type StaticProps = { content: MDXRemoteSerializeResult };
-
-// This method generates the Markdown page for the blog index for a given year
-// this should be used only for getting the index page for a specific year of the blog
-// Note.: this logic is heavily based on the current way how pagination on the blog is done
-// and might be removed at any given moment if the pagination logic changes
-export const getStaticProps: GetStaticProps<
- StaticProps,
- StaticParams
-> = async ({ params = { year: '' } }) => {
- // We replace the `year-` to get the actual year number that we need
- const yearNumber = params.year.replace('year-', '');
-
- // We generate the source of the Markdown file based on how a "year" page should look like
- const source = availableYears.includes(yearNumber)
- ? `---\nlayout: blog-index.hbs\ntitle: News from ${yearNumber}\npaginate: blog\n---\n`
- : '';
-
- // This parses the actual Markdown content and returns a full set of props
- // to be passed to the base page (`DynamicPage`) which will render the Markdown
- const staticProps = await nextDynamic.serializeDynamicPage(
- source,
- `blog/${params.year}`,
- `blog/${params.year}.md`
- );
-
- // We add the extra `params` to the props as they're used within the `DynamicPage`
- return staticProps;
-};
-
-// This method generates the blog year paths to be generated at build time
-// Note that this logic is actually locale independent and we do not care about
-// the value for the locale prop as it's not used anywhere
-export const getStaticPaths: GetStaticPaths = async () => {
- if (nextConfig.enableStaticExport) {
- // This maps all available blog index pages by each available locale
- // since we need to provide the locale piece from the URL. We need to do this
- // for the full-static version of the website
- const dynamicPaths = nextLocales.availableLocales.map(locale =>
- availableYears.map(year => ({
- params: { year: `year-${year}`, locale: locale.code },
- }))
- );
-
- return { paths: dynamicPaths.flat(), fallback: 'blocking' };
- }
-
- return { paths: [], fallback: 'blocking' };
-};
-
-export default DynamicTheme;
diff --git a/pages/_app.mdx b/pages/_app.mdx
deleted file mode 100644
index f85d4756bfa67..0000000000000
--- a/pages/_app.mdx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Analytics } from '@vercel/analytics/react';
-import { NodeReleasesProvider } from '../providers/nodeReleasesProvider';
-import { LocaleProvider } from '../providers/localeProvider';
-import { sourceSans } from '../util/nextFonts';
-import BaseApp, { setAppFont } from '../next.app';
-
-import '../styles/old/index.scss';
-
-export default function App({ Component, pageProps }) {
- setAppFont(sourceSans.style.fontFamily);
- return (
-
-
-
-
-
-
-
-
- );
-}
diff --git a/pages/_app.tsx b/pages/_app.tsx
new file mode 100644
index 0000000000000..2c4e3291451fc
--- /dev/null
+++ b/pages/_app.tsx
@@ -0,0 +1,17 @@
+import { Analytics } from '@vercel/analytics/react';
+import { sourceSans } from '../util/nextFonts';
+import BaseApp, { setAppFonts } from '../next.app';
+import type { AppProps } from 'next/app';
+
+import '../styles/old/index.scss';
+
+setAppFonts([sourceSans.style.fontFamily]);
+
+const App = ({ Component, pageProps }: AppProps) => (
+
+
+
+
+);
+
+export default App;
diff --git a/pages/_document.tsx b/pages/_document.tsx
index e574f4b5e0011..dd6c4b64994a3 100644
--- a/pages/_document.tsx
+++ b/pages/_document.tsx
@@ -1,21 +1,20 @@
import Script from 'next/script';
import { Html, Head, Main, NextScript } from 'next/document';
+import * as nextConstants from '../next.constants.mjs';
-// current application base path (temporary workaround)
-const basePath = process.env.NEXT_BASE_PATH || '';
-
-// @TODO: The custom scripts should be removed in the future when switching over `nodejs/nodejs.dev` codebase
-// Note.: Some of these scripts will also be completely removed from the codebase such as jQuery
const Document = () => (
+
+
+
diff --git a/pages/en/blog/pagination.md b/pages/en/blog/pagination.md
new file mode 100644
index 0000000000000..f16a110c97f63
--- /dev/null
+++ b/pages/en/blog/pagination.md
@@ -0,0 +1,5 @@
+---
+layout: blog-index.hbs
+title: News from
+paginate: blog
+---
diff --git a/providers/blogDataProvider.tsx b/providers/blogDataProvider.tsx
new file mode 100644
index 0000000000000..cf24c87b17034
--- /dev/null
+++ b/providers/blogDataProvider.tsx
@@ -0,0 +1,16 @@
+import { createContext, useMemo } from 'react';
+import blogData from '../public/blog-posts-data.json';
+import type { FC, PropsWithChildren } from 'react';
+import type { BlogData } from '../types';
+
+export const BlogDataContext = createContext({
+ posts: [],
+ pagination: [],
+ categories: [],
+});
+
+export const BlogDataProvider: FC = ({ children }) => (
+
+ {children}
+
+);
diff --git a/providers/layoutProvider.tsx b/providers/layoutProvider.tsx
index b2704bc93c32f..502104235698e 100644
--- a/providers/layoutProvider.tsx
+++ b/providers/layoutProvider.tsx
@@ -1,4 +1,4 @@
-import { createContext } from 'react';
+import { createContext, useMemo } from 'react';
import AboutLayout from '../layouts/AboutLayout';
import BlogIndexLayout from '../layouts/BlogIndexLayout';
import BlogPostLayout from '../layouts/BlogPostLayout';
@@ -10,21 +10,16 @@ import DownloadLayout from '../layouts/DownloadLayout';
import DownloadCurrentLayout from '../layouts/DownloadCurrentLayout';
import DownloadReleasesLayout from '../layouts/DownloadReleasesLayout';
import IndexLayout from '../layouts/IndexLayout';
-import type { PageOpts } from 'nextra';
-import type { PropsWithChildren } from 'react';
-import type { LegacyLayouts, NextraAppProps } from '../types';
+import type { FC, PropsWithChildren } from 'react';
+import type { LegacyFrontMatter, LegacyLayouts } from '../types';
-type LayoutProviderProps = PropsWithChildren<{
- pageOpts: PageOpts;
- pageProps: NextraAppProps['pageProps'];
-}>;
+type LayoutContextProps = {
+ frontMatter: LegacyFrontMatter;
+};
-export const LayoutContext = createContext<{
- layout: LegacyLayouts;
- pageOpts: PageOpts;
- pageProps: NextraAppProps['pageProps'];
- // @TODO: Initialize the Layout Provider with a default value
-}>(undefined as any);
+export const LayoutContext = createContext({
+ frontMatter: {},
+});
const getLegacyLayout = (layout: LegacyLayouts) => {
switch (layout) {
@@ -53,15 +48,19 @@ const getLegacyLayout = (layout: LegacyLayouts) => {
}
};
-// In this case we want to separate the children prop from the remaining ones
-
-export const LayoutProvider = ({ children, ...props }: LayoutProviderProps) => {
- const layout = props.pageOpts.frontMatter.layout;
+type LayoutProviderProps = PropsWithChildren;
- const LayoutComponent = getLegacyLayout(layout);
+export const LayoutProvider: FC = ({
+ children,
+ frontMatter,
+}) => {
+ const LayoutComponent = useMemo(
+ () => getLegacyLayout(frontMatter.layout || 'page.hbs'),
+ [frontMatter.layout]
+ );
return (
-
+
{children}
);
diff --git a/providers/mdxProvider.tsx b/providers/mdxProvider.tsx
new file mode 100644
index 0000000000000..84c0aa0e11a98
--- /dev/null
+++ b/providers/mdxProvider.tsx
@@ -0,0 +1,34 @@
+import { useEffect } from 'react';
+import { MDXProvider as BaseMDXProvider } from '@mdx-js/react';
+import highlightJs from 'highlight.js/lib/common';
+import AnchoredHeading from '../components/AnchoredHeading';
+import NodeApiVersionLinks from '../components/Docs/NodeApiVersionLinks';
+import { useRouter } from '../hooks/useRouter';
+import type { FC, PropsWithChildren } from 'react';
+import type { MDXComponents } from 'mdx/types';
+
+const mdxComponents: MDXComponents = {
+ NodeApiVersionLinks: NodeApiVersionLinks,
+ h1: props => ,
+ h2: props => ,
+ h3: props => ,
+ h4: props => ,
+ h5: props => ,
+ h6: props => ,
+ blockquote: ({ children }) => {children}
,
+};
+
+export const MDXProvider: FC = ({ children }) => {
+ const { asPath } = useRouter();
+
+ // Re-highlights the pages on route change
+ useEffect(() => highlightJs.highlightAll(), [asPath]);
+
+ useEffect(() => window.startLegacyApp(), []);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/providers/siteProvider.tsx b/providers/siteProvider.tsx
index 2fd991e95e582..43e5a78e023c0 100644
--- a/providers/siteProvider.tsx
+++ b/providers/siteProvider.tsx
@@ -1,10 +1,12 @@
import { createContext } from 'react';
-import siteConfig from '../site.json';
+import * as nextJson from '../next.json.mjs';
import type { FC, PropsWithChildren } from 'react';
import type { SiteConfig } from '../types';
-export const SiteContext = createContext(siteConfig);
+export const SiteContext = createContext(nextJson.siteConfig);
export const SiteProvider: FC = ({ children }) => (
- {children}
+
+ {children}
+
);
diff --git a/public/blog-posts-data.json b/public/blog-posts-data.json
new file mode 100644
index 0000000000000..23462569ab7af
--- /dev/null
+++ b/public/blog-posts-data.json
@@ -0,0 +1 @@
+{"pagination": [],"categories": [],"posts": []}
diff --git a/public/en/feed/.gitkeep b/public/en/feed/.gitkeep
deleted file mode 100644
index f935021a8f8a7..0000000000000
--- a/public/en/feed/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-!.gitignore
diff --git a/site.json b/site.json
index ac3dc9159e8df..58f815e51bcc5 100644
--- a/site.json
+++ b/site.json
@@ -23,12 +23,12 @@
{
"title": "Node.js Blog: Releases",
"file": "releases.xml",
- "blogCategory": "release"
+ "category": "release"
},
{
"title": "Node.js Blog: Vulnerability Reports",
"file": "vulnerability.xml",
- "blogCategory": "vulnerability"
+ "category": "vulnerability"
}
],
"websiteBanners": {
diff --git a/theme.dynamic.tsx b/theme.dynamic.tsx
deleted file mode 100644
index d6ecbdfe597d3..0000000000000
--- a/theme.dynamic.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { useMemo } from 'react';
-import { MDXRemote } from 'next-mdx-remote';
-import NextraTheme from './theme';
-import type { FC } from 'react';
-import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
-import type { AppProps, LegacyFrontMatter } from './types';
-
-// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
-interface DynamicThemeProps extends AppProps {
- content: MDXRemoteSerializeResult;
-}
-
-// This is the engine for Incremental Generated Static Pages
-// this component supports rendering remote-Markdown content
-// loaded from the server-side/static-build
-// we simulate the props that the NextraTheme receives as much as possible
-// the _page.mdx is already wrapped surrounding the tree of children components
-// extra props might come from extra context hence why we spread the props
-const DynamicTheme: FC = ({ content, ...props }) => {
- const frontMatter = content.frontmatter as LegacyFrontMatter;
-
- // this configures the pageOpts passed down to the NextraTheme which are used
- // for resolving the layout and other intrinsic configuration of the Theme based on Frontmatter
- const nextraThemeProps = useMemo(
- () => ({
- // We're not sure what needs to be forwarded here, but afaik since we use a Custom Theme,
- // we don't really have any Theme Config to forward. This seems to be used only for native
- // (bundled) themes from Nextra to allow the end-user to customize the theme.
- // but this option seems to be unused for custom themes. It seems like we could use it if we wanted
- // to customize things directly on `theme.tsx` as this themeConfig comes from `withNextra` on `next.config.mjs`
- themeConfig: null,
- // We simply forward the `pageProps` we receive from here to NextraTheme
- pageProps: props,
- // We create a replica of what Nextra would be passing down to the Theme's `pageOpts`
- // Sadly for dynamic pages we're unable (yet) to compute the headings.
- // `pageMap`, `filePath` and `route` are unused by us and are only used by Nextra's native themes
- pageOpts: {
- frontMatter,
- headings: [],
- pageMap: [],
- title: frontMatter.title || 'Node.js',
- filePath: '',
- route: '',
- },
- }),
- [frontMatter, props]
- );
-
- return (
-
-
-
- );
-};
-
-export default DynamicTheme;
diff --git a/theme.tsx b/theme.tsx
index fdf2b25c75636..a40cb8a06dfbd 100644
--- a/theme.tsx
+++ b/theme.tsx
@@ -1,55 +1,27 @@
-import { useEffect } from 'react';
-import { MDXProvider } from '@mdx-js/react';
-import highlightJs from 'highlight.js/lib/common';
-import HtmlHead from './components/HtmlHead';
-import AnchoredHeading from './components/AnchoredHeading';
-import NodeApiVersionLinks from './components/Docs/NodeApiVersionLinks';
+import { MDXRemote } from 'next-mdx-remote';
import { LayoutProvider } from './providers/layoutProvider';
-import { useRouter } from './hooks/useRouter';
+import { MDXProvider } from './providers/mdxProvider';
+import HtmlHead from './components/HtmlHead';
import type { FC, PropsWithChildren } from 'react';
-import type { NextraThemeLayoutProps, PageOpts } from 'nextra';
-import type { MDXComponents } from 'mdx/types';
-import type { LegacyFrontMatter } from './types';
-
-const mdxComponents: MDXComponents = {
- NodeApiVersionLinks: NodeApiVersionLinks,
- h1: props => ,
- h2: props => ,
- h3: props => ,
- h4: props => ,
- h5: props => ,
- h6: props => ,
- blockquote: ({ children }) => {children}
,
-};
+import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
-const Content: FC = ({ children }) => {
- const { asPath } = useRouter();
+type ThemeProps = PropsWithChildren<{
+ content?: MDXRemoteSerializeResult;
+}>;
- // Re-highlights the pages on route change
- useEffect(() => highlightJs.highlightAll(), [asPath]);
-
- useEffect(() => window.startLegacyApp(), []);
+const Theme: FC = ({ content, children }) => {
+ const frontMatter = content?.frontmatter || {};
return (
-
- {children}
-
+ <>
+
+
+ {content && }
+
+ {children}
+
+ >
);
};
-// @TODO: Nextra should provide better customization to FrontMatter Props
-// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
-interface ThemeProps extends NextraThemeLayoutProps {
- pageOpts: PageOpts;
-}
-
-const Theme: FC = ({ pageOpts, pageProps, children }) => (
- <>
-
-
- {children}
-
- >
-);
-
export default Theme;
diff --git a/tsconfig.json b/tsconfig.json
index 39973cbd29e90..2f2f168278b41 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -16,8 +16,13 @@
"incremental": true,
"paths": {
"@/*": ["./*"]
- }
+ },
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
diff --git a/turbo.json b/turbo.json
index acb39686b00f7..b033b0d5a5692 100644
--- a/turbo.json
+++ b/turbo.json
@@ -17,11 +17,11 @@
},
"lint": {
"inputs": [
- "{components,hooks,layouts,providers,types,util,middlewares}/**/*.{ts,tsx}",
+ "{components,hooks,layouts,providers,types,util,middlewares,app,pages}/**/*.{ts,tsx}",
"{components,layouts,pages,styles}/**/*.{css,sass,scss}",
"{scripts,next-data}/**/*.{js,mjs}",
"*.{md,mdx,json,ts,tsx,mjs,js}",
- "pages/**/*.{tsx,ts,mdx,md}",
+ "pages/**/*.{mdx,md}",
"i18n/**/.json",
".eslintrc",
".eslintignore",
@@ -49,11 +49,11 @@
},
"build": {
"inputs": [
- "{components,hooks,layouts,providers,types,util,middlewares}/**/*.{ts,tsx}",
+ "{components,hooks,layouts,providers,types,util,middlewares,app,pages}/**/*.{ts,tsx}",
"{components,layouts,pages,styles}/**/*.{css,sass,scss}",
"{scripts,next-data}/**/*.{js,mjs}",
"*.{md,mdx,json,ts,tsx,mjs,js}",
- "pages/**/*.{tsx,ts,mdx,md}"
+ "pages/**/*.{mdx,md}"
],
"outputs": [".next/**", "!.next/cache/**"]
},
@@ -62,10 +62,10 @@
},
"deploy": {
"inputs": [
- "{components,hooks,layouts,providers,types,util,middlewares}/**/*.{ts,tsx}",
+ "{components,hooks,layouts,providers,types,util,middlewares,app,pages}/**/*.{ts,tsx}",
"{components,layouts,pages,styles}/**/*.{css,sass,scss}",
"*.{md,mdx,json,ts,tsx,mjs,js}",
- "pages/**/*.{tsx,ts,mdx,md}"
+ "pages/**/*.{mdx,md}"
],
"outputs": [".next/**", "!.next/cache/**"]
},
@@ -75,7 +75,7 @@
"storybook:build": {
"inputs": [
"{components,hooks,layouts,providers,types,util}/**/*.{ts,tsx}",
- "{components,layouts,pages,styles}/**/*.{css,sass,scss}"
+ "{components,layouts,styles}/**/*.{css,sass,scss}"
],
"outputs": ["storybook-static/**"]
}
diff --git a/types/blog.ts b/types/blog.ts
index 85847e5b21042..a45e12c6bca9b 100644
--- a/types/blog.ts
+++ b/types/blog.ts
@@ -1,15 +1,13 @@
export interface BlogPost {
title: string;
- author?: string;
- date: Date;
+ author: string;
+ date: string;
category: string;
slug: string;
- readingTime?: string; // TODO: verify this works when implementing blog
- file: string;
}
export interface BlogData {
posts: BlogPost[];
- currentCategory: string;
- pagination: { prev?: string; next?: string };
+ pagination: number[];
+ categories: string[];
}
diff --git a/types/index.ts b/types/index.ts
index 45f98c9333080..36de67ba63c56 100644
--- a/types/index.ts
+++ b/types/index.ts
@@ -1,7 +1,3 @@
-import type { AppProps as DefaultAppProps } from 'next/app';
-
-import type { BlogData } from './blog';
-
export * from './api';
export * from './blog';
export * from './config';
@@ -14,9 +10,3 @@ export * from './navigation';
export * from './prevNextLink';
export * from './releases';
export * from './middlewares';
-
-export interface AppProps {
- blogData?: BlogData;
-}
-
-export type NextraAppProps = DefaultAppProps;
diff --git a/types/middlewares.ts b/types/middlewares.ts
index e29786c5a40a1..fcf39dc97ac0f 100644
--- a/types/middlewares.ts
+++ b/types/middlewares.ts
@@ -15,3 +15,10 @@ export type CustomMiddleware = {
matcher: (request: NextRequest, locale: NextMiddlewareLocale) => boolean;
routes: (string | RegExp)[];
};
+
+export type RouteSegment = {
+ filename: string;
+ localised: boolean;
+ routeWithLocale: string;
+ pathname: string;
+};
diff --git a/types/navigation.ts b/types/navigation.ts
index 3f6ed84909301..e63900e363989 100644
--- a/types/navigation.ts
+++ b/types/navigation.ts
@@ -1,6 +1,12 @@
-import type navigation from '../navigation.json';
-
-export type NavigationKeys = keyof typeof navigation;
+export type NavigationKeys =
+ | 'home'
+ | 'about'
+ | 'download'
+ | 'docs'
+ | 'getInvolved'
+ | 'security'
+ | 'certification'
+ | 'blog';
export interface NavigationEntry {
translationId: string;
diff --git a/util/openSans.ts b/util/openSans.ts
deleted file mode 100644
index 4475f1f8e233e..0000000000000
--- a/util/openSans.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Open_Sans } from 'next/font/google';
-
-const openSans = Open_Sans({
- weight: ['300', '400', '600'],
- display: 'fallback',
- subsets: ['latin'],
-});
-
-export default openSans;
From c3d384c517b3a308511c668fa6da7eabd473a98a Mon Sep 17 00:00:00 2001
From: Claudio Wunder
Date: Mon, 26 Jun 2023 21:33:52 +0200
Subject: [PATCH 3/6] chore: do not resort blog posts
---
next-data/generateBlogPostsData.mjs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/next-data/generateBlogPostsData.mjs b/next-data/generateBlogPostsData.mjs
index 8dce356abcdae..62eea2f004b57 100644
--- a/next-data/generateBlogPostsData.mjs
+++ b/next-data/generateBlogPostsData.mjs
@@ -61,8 +61,7 @@ const generateBlogPostsData = async () => {
// and then parsing the frontmatter and source content and returning a minified object
const postsPromise = filenames
.map(name => ({ name, file: readFile(join(blogPath, name)) }))
- .map(({ name, file }) => file.then(source => getFrontMatter(name, source)))
- .sort();
+ .map(({ name, file }) => file.then(source => getFrontMatter(name, source)));
// we await for all the work to be conclued and return a nice blog posts object
const posts = await Promise.all(postsPromise);
From 1752e45c2d9c5174351dcedcc3971a4970d75ce0 Mon Sep 17 00:00:00 2001
From: Claudio Wunder
Date: Mon, 26 Jun 2023 22:09:35 +0200
Subject: [PATCH 4/6] fix: sort by post date
---
next-data/generateBlogPostsData.mjs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/next-data/generateBlogPostsData.mjs b/next-data/generateBlogPostsData.mjs
index 62eea2f004b57..5103c00c68295 100644
--- a/next-data/generateBlogPostsData.mjs
+++ b/next-data/generateBlogPostsData.mjs
@@ -71,7 +71,7 @@ const generateBlogPostsData = async () => {
JSON.stringify({
pagination: [...blogMetadata.pagination].sort(),
categories: [...blogMetadata.categories].sort(),
- posts,
+ posts: posts.sort((a, b) => b.date - a.date),
})
);
};
From 636129f187dabc710e79e701aacd5297ddcae365 Mon Sep 17 00:00:00 2001
From: Claudio Wunder
Date: Tue, 27 Jun 2023 10:59:34 +0200
Subject: [PATCH 5/6] chore: apply code-review changes
Co-authored-by: Brian Muenzenmeyer
Signed-off-by: Claudio Wunder
---
CONTRIBUTING.md | 6 +++---
next-data/generateBlogPostsData.mjs | 2 +-
next.constants.mjs | 2 +-
next.dynamic.mjs | 4 ++--
pages/[...path].tsx | 2 +-
5 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a7bf655135ad2..a1a33198b2201 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -60,8 +60,8 @@ for getting things done and landing your contribution.
```bash
git clone git@github.com:/nodejs.org.git # SSH
- git clone https://github.com//nodejs.org.git # HTTPS
- gh repo clone /nodejs.org # GitHub CLI
+ git clone https://github.com//nodejs.org.git # HTTPS
+ gh repo clone /nodejs.org # GitHub CLI
```
3. Change into the nodejs.org directory.
@@ -184,7 +184,7 @@ We recommend a read on our [Collaborator Guide](COLLABORATOR_GUIDE.md#accepting-
- The person that is fast-tracking the PR (adding the label) must also comment on the PR that they're requesting the PR to be fast-tracked
- The comment must mention `@nodejs/website` and must have at least one 👍 (or any other sort of approval reaction) if the person fast-tracking the PR is the author of the PR.
- Fast-tracking is only allowed for small bug fixes, small feature changes, localisation changes, or other sorts of non-critical/highly-impacting changes that are not covered by the previous rule that allows PRs to be merged immediately.
- - Fast-tracking cannot be used for updates on the `COLLABORATOR_GUIDE.md`, CONTRIBUTING.md`guide,`CODEOWNERS`, GitHub Actions or any security-impacting file or document that changes the governing policies of this repository.
+ - Fast-tracking cannot be used for updates on the `COLLABORATOR_GUIDE.md`, `CONTRIBUTING.md` guide, `CODEOWNERS`, GitHub Actions, or any security-impacting file or document that changes the governing policies of this repository.
- There must be no objections after a 48-hour period (Or 72 hours if the PR was authored on the weekend).
- At least one approval is required for any PR to be merged.
- Tests must be included in Pull Requests for new features or bug fixes. If any test(s) are failing, you are responsible for fixing them.
diff --git a/next-data/generateBlogPostsData.mjs b/next-data/generateBlogPostsData.mjs
index 5103c00c68295..7dc7dffa4eeb7 100644
--- a/next-data/generateBlogPostsData.mjs
+++ b/next-data/generateBlogPostsData.mjs
@@ -63,7 +63,7 @@ const generateBlogPostsData = async () => {
.map(name => ({ name, file: readFile(join(blogPath, name)) }))
.map(({ name, file }) => file.then(source => getFrontMatter(name, source)));
- // we await for all the work to be conclued and return a nice blog posts object
+ // we await for all the work to be concluded and return a nice blog posts object
const posts = await Promise.all(postsPromise);
return writeFile(
diff --git a/next.constants.mjs b/next.constants.mjs
index a4f0d131655ac..001a5d4e52ba3 100644
--- a/next.constants.mjs
+++ b/next.constants.mjs
@@ -14,7 +14,7 @@ export const ENABLE_STATIC_EXPORT =
process.env.NEXT_STATIC_EXPORT === true;
/**
- * Supports a manuall override of the base path of the Website
+ * Supports a manual override of the base path of the Website
*
* This is useful when running the deployment on a subdirectory
* of a domain, such as when hosted on GitHub Pages.
diff --git a/next.dynamic.mjs b/next.dynamic.mjs
index 037b2aab56a47..8bac6a6bc144a 100644
--- a/next.dynamic.mjs
+++ b/next.dynamic.mjs
@@ -16,10 +16,10 @@ const getPathsByLanguage = async (
nextData.helpers.getMarkdownFiles(process.cwd(), `pages/${locale}`, ignored);
/**
- * This method is responsible for generating a Collection of all availalbe paths that
+ * This method is responsible for generating a Collection of all available paths that
* are served by the Website dynamically based on the Markdown pages on `pages/` folder.
*
- * Each Collection is associated to its Locale Code and containins a subset of Dictionaries
+ * Each Collection is associated to its Locale Code and contains a subset of Dictionaries
* that inform which pages are provided by that language and which not.
*
* The non-localised pages will still be served but our runtime Markdown loader `getMarkdownFile`
diff --git a/pages/[...path].tsx b/pages/[...path].tsx
index 9b8b0dcec12e6..7b6f11ab2c5bd 100644
--- a/pages/[...path].tsx
+++ b/pages/[...path].tsx
@@ -55,7 +55,7 @@ export const getStaticProps: GetStaticProps<
// to be passed to the base page (`DynamicPage`) which will render the Markdown
const staticProps = await nextDynamic.getStaticProps(source, filename);
- // This checks if either we already the route does not exist or if we should ignore
+ // This checks if either we already determined the route does not exist or if we should ignore
// this route because it's on our ignored list
staticProps.notFound = staticProps.notFound || shouldIgnoreRoute(pathname);
From 3c21f7f5b66f09a37ad7117ba9df0a888342e87f Mon Sep 17 00:00:00 2001
From: Claudio Wunder
Date: Tue, 27 Jun 2023 11:09:18 +0200
Subject: [PATCH 6/6] chore: some code review changes
---
hooks/useBlogData.ts | 20 ++++++++++++++++----
next.locales.mjs | 2 +-
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/hooks/useBlogData.ts b/hooks/useBlogData.ts
index 95ba597dcf0f3..aa3d5f5d48d2d 100644
--- a/hooks/useBlogData.ts
+++ b/hooks/useBlogData.ts
@@ -26,10 +26,22 @@ export const useBlogData = () => {
[pagination]
);
- const currentCategory = useMemo(
- () => asPath.split('/')[3] || new Date().getFullYear().toString(),
- [asPath]
- );
+ const currentCategory = useMemo(() => {
+ // We split the pathname to retrieve the blog category from it
+ // since the URL is usually /{languageCode}/blog/{category}
+ // the third path piece is usually the category name
+ const [, , pathname, category] = asPath.split('/');
+
+ if (pathname === 'blog' && category && category.length) {
+ return category;
+ }
+
+ // if either the pathname does not match to a blog page
+ // which should not happen (as this hook should only be used in blog pages)
+ // or if there is no category in the URL we return the current year as category name
+ // which is always the default category (for example, the blog index)
+ return new Date().getFullYear().toString();
+ }, [asPath]);
return {
posts,
diff --git a/next.locales.mjs b/next.locales.mjs
index 9cb821483dabc..e4eec239ad4e6 100644
--- a/next.locales.mjs
+++ b/next.locales.mjs
@@ -45,8 +45,8 @@ const getCurrentTranslations = locale => ({
});
export {
- defaultLocale,
availableLocales,
+ defaultLocale,
getCurrentLocale,
getCurrentTranslations,
};