From 3a0e80449de321b3ed0af79270312f0a76c55fd7 Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 2 Aug 2026 17:04:44 +0530 Subject: [PATCH 1/8] upgrade mermaid --- components/MDX/MDX.tsx | 145 +---------- components/MDX/MermaidDiagram.tsx | 133 ++++++++++ next.config.mjs | 1 + package-lock.json | 387 ++++++++++++++++++++++++++---- package.json | 2 +- 5 files changed, 472 insertions(+), 196 deletions(-) create mode 100644 components/MDX/MermaidDiagram.tsx diff --git a/components/MDX/MDX.tsx b/components/MDX/MDX.tsx index f357a451faf5..65a4e0dd3d49 100644 --- a/components/MDX/MDX.tsx +++ b/components/MDX/MDX.tsx @@ -1,7 +1,7 @@ import { MDXProvider as CoreMDXProvider } from '@mdx-js/react'; -import mermaid from 'mermaid'; +import dynamic from 'next/dynamic'; import Link from 'next/link'; -import React, { useEffect, useId, useState } from 'react'; +import React, { useId } from 'react'; import { TwitterDMButton, TwitterFollowButton, @@ -41,146 +41,7 @@ import Sponsors from '../sponsors/PlatinumSponsors'; import Warning from '../Warning'; import { Table, TableBody, TableCell, TableHeader, TableRow, Thead } from './MDXTable'; -type MermaidTheme = 'light' | 'dark'; - -const MERMAID_THEME_VARIABLES: Record> = { - light: { - primaryColor: '#EDFAFF', - primaryBorderColor: '#47BCEE', - secondaryColor: '#F4EFFC', - secondaryBorderColor: '#875AE2', - fontFamily: 'Inter, sans-serif', - fontSize: '18px', - primaryTextColor: '#242929', - tertiaryColor: '#F7F9FA', - tertiaryBorderColor: '#BFC6C7', - lineColor: '#BFC6C7', - mainBkg: '#EDFAFF', - secondBkg: '#F4EFFC', - tertiaryBkg: '#F7F9FA', - clusterBkg: '#F7F9FA', - clusterBorder: '#BFC6C7', - edgeLabelBackground: '#FFFFFF' - }, - dark: { - primaryColor: '#1E293B', - primaryBorderColor: '#38BDF8', - secondaryColor: '#2E2459', - secondaryBorderColor: '#A87EFC', - fontFamily: 'Inter, sans-serif', - fontSize: '18px', - primaryTextColor: '#F8FAFC', - tertiaryColor: '#121825', - tertiaryBorderColor: '#475569', - lineColor: '#94A3B8', - mainBkg: '#1E293B', - secondBkg: '#2E2459', - tertiaryBkg: '#121825', - clusterBkg: '#121825', - clusterBorder: '#475569', - edgeLabelBackground: '#1E293B' - } -}; - -// Cache the theme Mermaid was initialized with across client-side page transitions. -let initializedMermaidTheme: MermaidTheme | null = null; - -/** - * @description Returns the Mermaid theme that matches the current website theme. - */ -function getMermaidTheme(): MermaidTheme { - if (typeof document === 'undefined') { - return 'light'; - } - - return document.documentElement.classList.contains('dark') ? 'dark' : 'light'; -} - -/** - * @description Initializes the Mermaid library for the selected theme. - */ -function initializeMermaid(theme: MermaidTheme) { - if (initializedMermaidTheme === theme) { - return; - } - - initializedMermaidTheme = theme; - mermaid.initialize({ - startOnLoad: false, - theme: 'base', - securityLevel: 'strict', - // Keep Mermaid styling fully controlled by MERMAID_THEME_VARIABLES. - themeCSS: '', - themeVariables: MERMAID_THEME_VARIABLES[theme] - }); -} - -let currentId = 0; - -/** - * @description Generates a unique identifier. - * @returns {string} - A unique identifier. - */ -const uuid = (): string => `mermaid-${(currentId++).toString()}`; - -interface MermaidDiagramProps { - graph: string; -} - -/** - * @description This component renders Mermaid diagrams. - * - * @param {MermaidDiagramProps} props - The props for the MermaidDiagram component. - * @param {string} props.graph - The Mermaid graph to render. - */ -function MermaidDiagram({ graph }: Readonly) { - const [svg, setSvg] = useState(null); - const [theme, setTheme] = useState('light'); - - useEffect(() => { - setTheme(getMermaidTheme()); - - const observer = new MutationObserver(() => { - setTheme(getMermaidTheme()); - }); - - observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); - - return () => observer.disconnect(); - }, []); - - /** - * @description Renders the Mermaid diagram. - */ - useEffect(() => { - let mounted = true; - - if (graph) { - try { - initializeMermaid(theme); - mermaid.mermaidAPI.render(uuid(), graph.trim(), (svgGraph) => { - if (mounted) { - setSvg(svgGraph); - } - }); - } catch (e) { - if (mounted) { - setSvg(null); - } - // eslint-disable-next-line no-console - console.error(e); - } - } else { - setSvg(null); - } - - return () => { - mounted = false; - }; - }, [graph, theme]); - - return
; -} +const MermaidDiagram = dynamic(() => import('./MermaidDiagram'), { ssr: false }); interface CodeComponentProps { children: string; diff --git a/components/MDX/MermaidDiagram.tsx b/components/MDX/MermaidDiagram.tsx new file mode 100644 index 000000000000..5a034cd48317 --- /dev/null +++ b/components/MDX/MermaidDiagram.tsx @@ -0,0 +1,133 @@ +import { useEffect, useId, useState } from 'react'; + +type MermaidTheme = 'light' | 'dark'; + +const MERMAID_THEME_VARIABLES: Record> = { + light: { + primaryColor: '#EDFAFF', + primaryBorderColor: '#47BCEE', + secondaryColor: '#F4EFFC', + secondaryBorderColor: '#875AE2', + fontFamily: 'Inter, sans-serif', + fontSize: '18px', + primaryTextColor: '#242929', + tertiaryColor: '#F7F9FA', + tertiaryBorderColor: '#BFC6C7', + lineColor: '#BFC6C7', + mainBkg: '#EDFAFF', + secondBkg: '#F4EFFC', + tertiaryBkg: '#F7F9FA', + clusterBkg: '#F7F9FA', + clusterBorder: '#BFC6C7', + edgeLabelBackground: '#FFFFFF' + }, + dark: { + primaryColor: '#1E293B', + primaryBorderColor: '#38BDF8', + secondaryColor: '#2E2459', + secondaryBorderColor: '#A87EFC', + fontFamily: 'Inter, sans-serif', + fontSize: '18px', + primaryTextColor: '#F8FAFC', + tertiaryColor: '#121825', + tertiaryBorderColor: '#475569', + lineColor: '#94A3B8', + mainBkg: '#1E293B', + secondBkg: '#2E2459', + tertiaryBkg: '#121825', + clusterBkg: '#121825', + clusterBorder: '#475569', + edgeLabelBackground: '#1E293B' + } +}; + +// Cache the theme Mermaid was initialized with across client-side page transitions. +let initializedMermaidTheme: MermaidTheme | null = null; + +/** + * @description Returns the Mermaid theme that matches the current website theme. + */ +function getMermaidTheme(): MermaidTheme { + return document.documentElement.classList.contains('dark') ? 'dark' : 'light'; +} + +interface MermaidDiagramProps { + graph: string; +} + +/** + * @description This component renders Mermaid diagrams. + * + * Mermaid is loaded via a dynamic import inside the render effect so it is + * never included in the initial JS bundle — it is fetched on demand only + * when a page actually contains a diagram. + * + * @param {MermaidDiagramProps} props - The props for the MermaidDiagram component. + * @param {string} props.graph - The Mermaid graph definition to render. + */ +export default function MermaidDiagram({ graph }: Readonly) { + const [svg, setSvg] = useState(null); + const [theme, setTheme] = useState('light'); + const reactId = useId(); + // Produce a stable, DOM-safe ID for the SVG element mermaid generates. + const diagramId = `mermaid${reactId.replace(/:/g, '')}`; + + useEffect(() => { + setTheme(getMermaidTheme()); + + const observer = new MutationObserver(() => { + setTheme(getMermaidTheme()); + }); + + observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); + + return () => observer.disconnect(); + }, []); + + useEffect(() => { + if (!graph) { + setSvg(null); + + return; + } + + let mounted = true; + + async function render() { + try { + const { default: mermaid } = await import('mermaid'); + + if (initializedMermaidTheme !== theme) { + initializedMermaidTheme = theme; + mermaid.initialize({ + startOnLoad: false, + theme: 'base', + securityLevel: 'strict', + // Keep Mermaid styling fully controlled by MERMAID_THEME_VARIABLES. + themeVariables: MERMAID_THEME_VARIABLES[theme] + }); + } + + const { svg: rendered } = await mermaid.render(diagramId, graph.trim()); + + if (mounted) { + setSvg(rendered); + } + } catch (e) { + if (mounted) { + setSvg(null); + } + // eslint-disable-next-line no-console + console.error(e); + } + } + + render(); + + return () => { + mounted = false; + }; + }, [graph, theme, diagramId]); + + return
; +} diff --git a/next.config.mjs b/next.config.mjs index 52d52fcc41c3..f5ee23583088 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -16,6 +16,7 @@ const nextConfig = { ignoreDuringBuilds: true }, output: 'export', + serverExternalPackages: ['langium', '@mermaid-js/parser'], webpack(config, { isServer }) { if (!isServer) { config.resolve.fallback.fs = false; diff --git a/package-lock.json b/package-lock.json index 86767630a29d..3872ac84459c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "lodash": "^4.18.1", "markdown-to-txt": "^2.0.1", "markdown-toc": "^1.2.0", - "mermaid": "9.3.0", + "mermaid": "^11.4.1", "next": "15.5.21", "next-i18next": "^15.3.0", "next-language-detector": "^1.1.1", @@ -392,6 +392,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "14.2.1", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.2.1.tgz", @@ -2327,11 +2340,17 @@ "license": "MIT" }, "node_modules/@braintree/sanitize-url": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", - "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", "license": "MIT" }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, "node_modules/@chromatic-com/storybook": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-1.9.0.tgz", @@ -3185,6 +3204,23 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, "node_modules/@img/colour": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", @@ -4820,6 +4856,15 @@ "react": ">=16" } }, + "node_modules/@mermaid-js/parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.0.tgz", + "integrity": "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==", + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.2" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -8198,6 +8243,13 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -8740,6 +8792,16 @@ "win32" ] }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vercel/node-bridge": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@vercel/node-bridge/-/node-bridge-2.2.2.tgz", @@ -11630,6 +11692,15 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "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==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -12173,6 +12244,54 @@ "dev": true, "license": "0BSD" }, + "node_modules/cytoscape": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", + "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, "node_modules/d3": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", @@ -12477,6 +12596,46 @@ "node": ">=12" } }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -12596,12 +12755,12 @@ } }, "node_modules/dagre-d3-es": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.6.tgz", - "integrity": "sha512-CaaE/nZh205ix+Up4xsnlGmpog5GGm81Upi2+/SBHxwNwrccBb3K51LzjZ1U6hgvOlAEUsVWf1xSTzCyKpJ6+Q==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", "license": "MIT", "dependencies": { - "d3": "^7.7.0", + "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, @@ -12689,9 +12848,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", "license": "MIT" }, "node_modules/debug": { @@ -12849,9 +13008,9 @@ "license": "MIT" }, "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" @@ -13112,10 +13271,13 @@ } }, "node_modules/dompurify": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz", - "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==", - "license": "(MPL-2.0 OR Apache-2.0)" + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } }, "node_modules/domutils": { "version": "2.8.0", @@ -13556,6 +13718,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz", + "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types" + ] + }, "node_modules/esast-util-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", @@ -16202,6 +16375,12 @@ "dev": true, "license": "MIT" }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -16905,7 +17084,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -19325,6 +19503,31 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "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==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -19385,6 +19588,12 @@ "node": ">=0.10" } }, + "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==", + "license": "MIT" + }, "node_modules/lazy-cache": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", @@ -19705,9 +19914,9 @@ "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/lodash._reinterpolate": { @@ -20743,21 +20952,57 @@ } }, "node_modules/mermaid": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.3.0.tgz", - "integrity": "sha512-mGl0BM19TD/HbU/LmlaZbjBi//tojelg8P/mxD6pPZTAYaI+VawcyBdqRsoUHSc7j71PrMdJ3HBadoQNdvP5cg==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "^6.0.0", - "d3": "^7.0.0", - "dagre-d3-es": "7.0.6", - "dompurify": "2.4.1", - "khroma": "^2.0.0", - "lodash-es": "^4.17.21", - "moment-mini": "^2.24.0", - "non-layered-tidy-tree-layout": "^2.0.2", - "stylis": "^4.1.2", - "uuid": "^9.0.0" + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.16.0.tgz", + "integrity": "sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.2", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.2.0", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.20", + "dompurify": "^3.3.3", + "es-toolkit": "^1.45.1", + "katex": "^0.16.45", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mermaid/node_modules/uuid": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" } }, "node_modules/micro-memoize": { @@ -21765,12 +22010,6 @@ "micro-memoize": "^4.1.2" } }, - "node_modules/moment-mini": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.29.4.tgz", - "integrity": "sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg==", - "license": "MIT" - }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -22783,12 +23022,6 @@ "url": "https://github.com/sponsors/antelle" } }, - "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==", - "license": "MIT" - }, "node_modules/nopt": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", @@ -23379,6 +23612,12 @@ "node": ">=6" } }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "license": "MIT" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -23498,6 +23737,12 @@ "dev": true, "license": "MIT" }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -23770,6 +24015,22 @@ "node": ">=6" } }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/polished": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", @@ -26795,11 +27056,23 @@ } }, "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", "license": "Unlicense" }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, "node_modules/run-async": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", @@ -29191,6 +29464,15 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -29450,7 +29732,6 @@ "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, "license": "MIT", "engines": { "node": ">=6.10" diff --git a/package.json b/package.json index a2721dcfc015..abe04b928860 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "lodash": "^4.18.1", "markdown-to-txt": "^2.0.1", "markdown-toc": "^1.2.0", - "mermaid": "9.3.0", + "mermaid": "^11.4.1", "next": "15.5.21", "next-i18next": "^15.3.0", "next-language-detector": "^1.1.1", From dcaec53264c5c9b604c5d29a0be32513a4b4f8fe Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 2 Aug 2026 17:20:07 +0530 Subject: [PATCH 2/8] coderabbit suggestion --- components/MDX/MDX.tsx | 5 +- components/MDX/MermaidDiagram.tsx | 87 +++++++++++++++---------------- 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/components/MDX/MDX.tsx b/components/MDX/MDX.tsx index 65a4e0dd3d49..0e72e8a7041b 100644 --- a/components/MDX/MDX.tsx +++ b/components/MDX/MDX.tsx @@ -41,7 +41,10 @@ import Sponsors from '../sponsors/PlatinumSponsors'; import Warning from '../Warning'; import { Table, TableBody, TableCell, TableHeader, TableRow, Thead } from './MDXTable'; -const MermaidDiagram = dynamic(() => import('./MermaidDiagram'), { ssr: false }); +const MermaidDiagram = dynamic(() => import('./MermaidDiagram'), { + ssr: false, + loading: () =>
+}); interface CodeComponentProps { children: string; diff --git a/components/MDX/MermaidDiagram.tsx b/components/MDX/MermaidDiagram.tsx index 5a034cd48317..39068999097c 100644 --- a/components/MDX/MermaidDiagram.tsx +++ b/components/MDX/MermaidDiagram.tsx @@ -41,11 +41,11 @@ const MERMAID_THEME_VARIABLES: Record> = { } }; -// Cache the theme Mermaid was initialized with across client-side page transitions. +// Tracks the theme Mermaid was last initialized with to skip redundant re-initialization. let initializedMermaidTheme: MermaidTheme | null = null; /** - * @description Returns the Mermaid theme that matches the current website theme. + * @description Returns the Mermaid theme matching the current website theme. */ function getMermaidTheme(): MermaidTheme { return document.documentElement.classList.contains('dark') ? 'dark' : 'light'; @@ -56,25 +56,22 @@ interface MermaidDiagramProps { } /** - * @description This component renders Mermaid diagrams. + * @description Renders a Mermaid diagram from a graph definition string. * - * Mermaid is loaded via a dynamic import inside the render effect so it is - * never included in the initial JS bundle — it is fetched on demand only - * when a page actually contains a diagram. + * Mermaid is loaded via a dynamic import so it is never included in the + * initial JS bundle — it is fetched on demand only when a diagram mounts. * - * @param {MermaidDiagramProps} props - The props for the MermaidDiagram component. - * @param {string} props.graph - The Mermaid graph definition to render. + * @param {MermaidDiagramProps} props - Component props. + * @param {string} props.graph - Mermaid graph definition to render. */ export default function MermaidDiagram({ graph }: Readonly) { const [svg, setSvg] = useState(null); - const [theme, setTheme] = useState('light'); + // Lazy initializer reads the DOM once on mount — avoids a wasted light→correct-theme re-render. + const [theme, setTheme] = useState(getMermaidTheme); const reactId = useId(); - // Produce a stable, DOM-safe ID for the SVG element mermaid generates. - const diagramId = `mermaid${reactId.replace(/:/g, '')}`; + const diagramId = `mermaid${reactId.replaceAll(':', '')}`; useEffect(() => { - setTheme(getMermaidTheme()); - const observer = new MutationObserver(() => { setTheme(getMermaidTheme()); }); @@ -85,45 +82,43 @@ export default function MermaidDiagram({ graph }: Readonly) }, []); useEffect(() => { - if (!graph) { - setSvg(null); - - return; - } - let mounted = true; - async function render() { - try { - const { default: mermaid } = await import('mermaid'); - - if (initializedMermaidTheme !== theme) { - initializedMermaidTheme = theme; - mermaid.initialize({ - startOnLoad: false, - theme: 'base', - securityLevel: 'strict', - // Keep Mermaid styling fully controlled by MERMAID_THEME_VARIABLES. - themeVariables: MERMAID_THEME_VARIABLES[theme] - }); + if (!graph) { + setSvg(null); + } else { + const render = async () => { + try { + const { default: mermaid } = await import('mermaid'); + + if (initializedMermaidTheme !== theme) { + initializedMermaidTheme = theme; + mermaid.initialize({ + startOnLoad: false, + theme: 'base', + securityLevel: 'strict', + themeVariables: MERMAID_THEME_VARIABLES[theme] + }); + } + + const { svg: rendered } = await mermaid.render(diagramId, graph.trim()); + + if (mounted) { + setSvg(rendered); + } + } catch (e) { + if (mounted) { + setSvg(null); + } + + // eslint-disable-next-line no-console + console.error(e); } + }; - const { svg: rendered } = await mermaid.render(diagramId, graph.trim()); - - if (mounted) { - setSvg(rendered); - } - } catch (e) { - if (mounted) { - setSvg(null); - } - // eslint-disable-next-line no-console - console.error(e); - } + render(); } - render(); - return () => { mounted = false; }; From 471dec4f74e892bd82f9db80c9e871bc412659a8 Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 2 Aug 2026 18:04:46 +0530 Subject: [PATCH 3/8] changes --- components/MDX/MermaidDiagram.tsx | 18 ++++++++++++++---- package.json | 1 + 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/components/MDX/MermaidDiagram.tsx b/components/MDX/MermaidDiagram.tsx index 39068999097c..70fbda0f737f 100644 --- a/components/MDX/MermaidDiagram.tsx +++ b/components/MDX/MermaidDiagram.tsx @@ -1,3 +1,4 @@ +import DOMPurify from 'dompurify'; import { useEffect, useId, useState } from 'react'; type MermaidTheme = 'light' | 'dark'; @@ -66,6 +67,7 @@ interface MermaidDiagramProps { */ export default function MermaidDiagram({ graph }: Readonly) { const [svg, setSvg] = useState(null); + const [hasError, setHasError] = useState(false); // Lazy initializer reads the DOM once on mount — avoids a wasted light→correct-theme re-render. const [theme, setTheme] = useState(getMermaidTheme); const reactId = useId(); @@ -84,9 +86,9 @@ export default function MermaidDiagram({ graph }: Readonly) useEffect(() => { let mounted = true; - if (!graph) { - setSvg(null); - } else { + if (graph) { + setHasError(false); + const render = async () => { try { const { default: mermaid } = await import('mermaid'); @@ -102,13 +104,15 @@ export default function MermaidDiagram({ graph }: Readonly) } const { svg: rendered } = await mermaid.render(diagramId, graph.trim()); + const sanitized = DOMPurify.sanitize(rendered, { USE_PROFILES: { svg: true, svgFilters: true } }); if (mounted) { - setSvg(rendered); + setSvg(sanitized); } } catch (e) { if (mounted) { setSvg(null); + setHasError(true); } // eslint-disable-next-line no-console @@ -117,6 +121,8 @@ export default function MermaidDiagram({ graph }: Readonly) }; render(); + } else { + setSvg(null); } return () => { @@ -124,5 +130,9 @@ export default function MermaidDiagram({ graph }: Readonly) }; }, [graph, theme, diagramId]); + if (hasError) { + return

Unable to render the diagram.

; + } + return
; } diff --git a/package.json b/package.json index abe04b928860..44cc3d79411a 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "dayjs": "^1.11.19", "dedent": "^1.5.1", "dotenv": "^16.4.4", + "dompurify": "^3.4.0", "fs-extra": "^11.2.0", "fuse.js": "^7.0.0", "googleapis": "^133.0.0", From 6f04383424f06cc53ec73fb1163fd16982a136cc Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 2 Aug 2026 18:29:37 +0530 Subject: [PATCH 4/8] minor change --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 44cc3d79411a..05c9b57202af 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "dayjs": "^1.11.19", "dedent": "^1.5.1", "dotenv": "^16.4.4", - "dompurify": "^3.4.0", + "dompurify": "3.4.12", "fs-extra": "^11.2.0", "fuse.js": "^7.0.0", "googleapis": "^133.0.0", @@ -86,7 +86,7 @@ "lodash": "^4.18.1", "markdown-to-txt": "^2.0.1", "markdown-toc": "^1.2.0", - "mermaid": "^11.4.1", + "mermaid": "11.16.0", "next": "15.5.21", "next-i18next": "^15.3.0", "next-language-detector": "^1.1.1", From 2647a388de66c6ed5dd9510b47e5d22bf23f6765 Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 2 Aug 2026 18:30:33 +0530 Subject: [PATCH 5/8] minor change --- components/MDX/MermaidDiagram.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/MDX/MermaidDiagram.tsx b/components/MDX/MermaidDiagram.tsx index 70fbda0f737f..86081a00166e 100644 --- a/components/MDX/MermaidDiagram.tsx +++ b/components/MDX/MermaidDiagram.tsx @@ -84,9 +84,10 @@ export default function MermaidDiagram({ graph }: Readonly) }, []); useEffect(() => { + const trimmedGraph = graph.trim(); let mounted = true; - if (graph) { + if (trimmedGraph) { setHasError(false); const render = async () => { @@ -103,7 +104,7 @@ export default function MermaidDiagram({ graph }: Readonly) }); } - const { svg: rendered } = await mermaid.render(diagramId, graph.trim()); + const { svg: rendered } = await mermaid.render(diagramId, trimmedGraph); const sanitized = DOMPurify.sanitize(rendered, { USE_PROFILES: { svg: true, svgFilters: true } }); if (mounted) { @@ -123,6 +124,7 @@ export default function MermaidDiagram({ graph }: Readonly) render(); } else { setSvg(null); + setHasError(false); } return () => { From 4f6680e324a2b8cded6cbc9814e895bbd916b8ef Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 2 Aug 2026 20:26:41 +0530 Subject: [PATCH 6/8] fix deno module issue --- .../tests/serve-definitions.test.ts | 51 ++++++++----------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/netlify/edge-functions/tests/serve-definitions.test.ts b/netlify/edge-functions/tests/serve-definitions.test.ts index ea9bbdf76215..c849925eca3c 100644 --- a/netlify/edge-functions/tests/serve-definitions.test.ts +++ b/netlify/edge-functions/tests/serve-definitions.test.ts @@ -1,6 +1,6 @@ import serveDefinitions from "../serve-definitions.ts"; import { Context } from "https://edge-bootstrap.netlify.app/v1/index.ts"; -import * as mf from "https://deno.land/x/mock_fetch@0.3.0/mod.ts"; +import { stub } from "jsr:@std/testing/mock"; import { assertEquals } from "https://deno.land/std@0.208.0/assert/assert_equals.ts"; const metricURL = "https://metric-api.eu.newrelic.com/metric/v1"; @@ -51,11 +51,11 @@ const context = { let metricCalls = 0; function setup() { - mf.install(); + return stub(globalThis, "fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const req = new Request(input, init); - mf.mock("*", (req) => { console.log(req.url); - + if (req.url === metricURL) { metricCalls++; } @@ -78,7 +78,7 @@ function setup() { Deno.test("serve-definitions test for validRequests", async () => { metricCalls = 0; - setup(); + using _fetchStub = setup(); for (const entry of validRequests) { console.log("Testing: " + entry.requestURL); @@ -95,43 +95,37 @@ Deno.test("serve-definitions test for validRequests", async () => { } assertEquals(metricCalls, validRequests.length); - - mf.uninstall(); }); Deno.test("serve-definitions test for invalidRequests", async () => { metricCalls = 0; - setup(); + using _fetchStub = setup(); for (const entry of invalidRequests) { console.log("Testing: " + entry.requestURL); const request = new Request(entry.requestURL, { method: "GET" }); const response = await serveDefinitions(request, context as Context); - - assertEquals(response, undefined); + + assertEquals(response, undefined); } // No metrics should be sent for invalid requests assertEquals(metricCalls, 0); - - mf.uninstall(); }); Deno.test("serve-definitions test for various response statuses", async () => { const testCases = [ - { requestURL: "https://asyncapi.com/definitions/2.4.0/info.json", status: 200, mockParam: "GET@https://asyncapi.com/definitions/2.4.0/info.json" }, - { requestURL: "https://asyncapi.com/definitions/2.4.0/info.json", status: 304, mockParam: "GET@https://asyncapi.com/definitions/2.4.0/info.json" }, - { requestURL: "https://asyncapi.com/definitions/2.4.0/info.json", status: 404, mockParam: "GET@https://asyncapi.com/definitions/2.4.0/info.json" }, - { requestURL: "https://asyncapi.com/definitions/2.4.0/info.json", status: 500, mockParam: "GET@https://asyncapi.com/definitions/2.4.0/info.json" }, + { requestURL: "https://asyncapi.com/definitions/2.4.0/info.json", status: 200 }, + { requestURL: "https://asyncapi.com/definitions/2.4.0/info.json", status: 304 }, + { requestURL: "https://asyncapi.com/definitions/2.4.0/info.json", status: 404 }, + { requestURL: "https://asyncapi.com/definitions/2.4.0/info.json", status: 500 }, ]; - for (const { requestURL, status, mockParam } of testCases) { + for (const { requestURL, status } of testCases) { console.log("Testing: " + requestURL); - mf.install(); - - mf.mock("*", () => { + using _fetchStub = stub(globalThis, "fetch", async () => { return new Response(status === 200 ? JSON.stringify({ url: requestURL }) : null, { status, }); @@ -152,16 +146,14 @@ Deno.test("serve-definitions test for various response statuses", async () => { metricCalls++; } - console.log("\n"); - mf.uninstall(); } assertEquals(metricCalls, testCases.filter((testCase) => testCase.status === 200 || testCase.status === 304).length); }); Deno.test("serve-definitions test for schema-unrelated requests", async () => { - setup(); + using _fetchStub = setup(); const schemaUnrelatedRequests = [ "https://asyncapi.com/definitions/asyncapi.yaml", @@ -177,12 +169,11 @@ Deno.test("serve-definitions test for schema-unrelated requests", async () => { assertEquals(response, undefined); } - - mf.uninstall(); }); Deno.test("serve-definitions test for schema-related non-JSON requests", async () => { - setup(); + using _fetchStub = setup(); + metricCalls = 0; const schemaRelatedNonJsonRequests = [ @@ -190,7 +181,7 @@ Deno.test("serve-definitions test for schema-related non-JSON requests", async ( ]; for (const requestURL of schemaRelatedNonJsonRequests) { - const context = { + const localContext = { next: () => { return new Response(JSON.stringify({ url: requestURL }), { status: 200, @@ -200,11 +191,11 @@ Deno.test("serve-definitions test for schema-related non-JSON requests", async ( }); }, log: () => {}, - } + }; console.log("Testing: " + requestURL); const request = new Request(requestURL, { method: "GET" }); - const response = await serveDefinitions(request, context as unknown as Context); + const response = await serveDefinitions(request, localContext as unknown as Context); const body = response?.body ? await response.json() : null; assertEquals(response?.status, 200); @@ -212,7 +203,5 @@ Deno.test("serve-definitions test for schema-related non-JSON requests", async ( assertEquals(response.headers.get("Content-Type"), "application/json"); // Default content type for non-JSON requests } - mf.uninstall(); - assertEquals(metricCalls, 0); }); From 6505cf97a53d2ae7c56ef7b683fb80a043110d0b Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 2 Aug 2026 20:43:44 +0530 Subject: [PATCH 7/8] minor fix --- components/MDX/MermaidDiagram.tsx | 10 +++++++++- netlify/edge-functions/tests/serve-definitions.test.ts | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/components/MDX/MermaidDiagram.tsx b/components/MDX/MermaidDiagram.tsx index 86081a00166e..a960f4ffbaa4 100644 --- a/components/MDX/MermaidDiagram.tsx +++ b/components/MDX/MermaidDiagram.tsx @@ -105,7 +105,15 @@ export default function MermaidDiagram({ graph }: Readonly) } const { svg: rendered } = await mermaid.render(diagramId, trimmedGraph); - const sanitized = DOMPurify.sanitize(rendered, { USE_PROFILES: { svg: true, svgFilters: true } }); + // USE_PROFILES svg+svgFilters covers SVG elements; html covers the HTML content + // inside which Mermaid uses for node labels. + // ADD_TAGS explicitly permits itself (not in DOMPurify's svg profile). + // ADD_ATTR permits requiredExtensions, the standard attribute on . + const sanitized = DOMPurify.sanitize(rendered, { + USE_PROFILES: { svg: true, svgFilters: true, html: true }, + ADD_TAGS: ['foreignObject'], + ADD_ATTR: ['requiredExtensions'] + }); if (mounted) { setSvg(sanitized); diff --git a/netlify/edge-functions/tests/serve-definitions.test.ts b/netlify/edge-functions/tests/serve-definitions.test.ts index c849925eca3c..03881f22844f 100644 --- a/netlify/edge-functions/tests/serve-definitions.test.ts +++ b/netlify/edge-functions/tests/serve-definitions.test.ts @@ -1,6 +1,6 @@ import serveDefinitions from "../serve-definitions.ts"; import { Context } from "https://edge-bootstrap.netlify.app/v1/index.ts"; -import { stub } from "jsr:@std/testing/mock"; +import { stub } from "jsr:@std/testing@1.0.20/mock"; import { assertEquals } from "https://deno.land/std@0.208.0/assert/assert_equals.ts"; const metricURL = "https://metric-api.eu.newrelic.com/metric/v1"; From b908eed7855f75e6ddc8a9ba3f6c4f6ccec6f4ba Mon Sep 17 00:00:00 2001 From: Prince Rajpoot Date: Sun, 2 Aug 2026 22:16:25 +0530 Subject: [PATCH 8/8] minor fix --- components/MDX/MermaidDiagram.tsx | 11 ++++------- package-lock.json | 3 ++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/components/MDX/MermaidDiagram.tsx b/components/MDX/MermaidDiagram.tsx index a960f4ffbaa4..efd0be53df88 100644 --- a/components/MDX/MermaidDiagram.tsx +++ b/components/MDX/MermaidDiagram.tsx @@ -100,19 +100,16 @@ export default function MermaidDiagram({ graph }: Readonly) startOnLoad: false, theme: 'base', securityLevel: 'strict', + htmlLabels: false, themeVariables: MERMAID_THEME_VARIABLES[theme] }); } + document.getElementById(diagramId)?.remove(); + const { svg: rendered } = await mermaid.render(diagramId, trimmedGraph); - // USE_PROFILES svg+svgFilters covers SVG elements; html covers the HTML content - // inside which Mermaid uses for node labels. - // ADD_TAGS explicitly permits itself (not in DOMPurify's svg profile). - // ADD_ATTR permits requiredExtensions, the standard attribute on . const sanitized = DOMPurify.sanitize(rendered, { - USE_PROFILES: { svg: true, svgFilters: true, html: true }, - ADD_TAGS: ['foreignObject'], - ADD_ATTR: ['requiredExtensions'] + USE_PROFILES: { svg: true, svgFilters: true } }); if (mounted) { diff --git a/package-lock.json b/package-lock.json index 3872ac84459c..e9a5f79ed1fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "cssnano": "^6.0.3", "dayjs": "^1.11.19", "dedent": "^1.5.1", + "dompurify": "3.4.12", "dotenv": "^16.4.4", "fs-extra": "^11.2.0", "fuse.js": "^7.0.0", @@ -47,7 +48,7 @@ "lodash": "^4.18.1", "markdown-to-txt": "^2.0.1", "markdown-toc": "^1.2.0", - "mermaid": "^11.4.1", + "mermaid": "11.16.0", "next": "15.5.21", "next-i18next": "^15.3.0", "next-language-detector": "^1.1.1",