diff --git a/components/editor/CodeBlock.tsx b/components/editor/CodeBlock.tsx index 860b7791f07b..1132c7d2be4d 100644 --- a/components/editor/CodeBlock.tsx +++ b/components/editor/CodeBlock.tsx @@ -1,6 +1,6 @@ import { CheckIcon } from '@heroicons/react/24/outline'; +import { Highlight, type Language, type Token } from 'prism-react-renderer'; import React, { useState } from 'react'; -import Highlight from 'react-syntax-highlighter'; import Caption from '../Caption'; import IconClipboard from '../icons/Clipboard'; @@ -22,160 +22,195 @@ interface CodeBlockProps { title?: string; } -interface Theme { - [key: string]: { - display?: string; - background?: string; - color?: string; - fontWeight?: string | number; - backgroundColor?: string; - fontStyle?: string; - textDecoration?: string; - }; +/** Maps common language aliases to valid Prism identifiers, falls back to 'plaintext'. */ +const LANGUAGE_ALIASES: Record = { + yml: 'yaml', + sh: 'bash', + shell: 'bash', + js: 'javascript', + ts: 'typescript', + py: 'python', + rb: 'ruby', + asyncapi: 'yaml', + 'generator-cli': 'bash', + mermaid: 'plaintext', +}; + +const SUPPORTED_LANGUAGES = new Set([ + 'markup', + 'bash', + 'clike', + 'c', + 'cpp', + 'css', + 'javascript', + 'jsx', + 'coffeescript', + 'actionscript', + 'css-extr', + 'diff', + 'git', + 'go', + 'graphql', + 'handlebars', + 'json', + 'less', + 'makefile', + 'markdown', + 'objectivec', + 'ocaml', + 'python', + 'reason', + 'sass', + 'scss', + 'sql', + 'stylus', + 'tsx', + 'typescript', + 'wasm', + 'yaml', + 'plaintext', + 'ruby', +]); + +/** + * @description Normalizes a language string to a valid Prism identifier. + */ +function normalizeLanguage(lang?: string): Language { + if (!lang) return 'plaintext' as Language; + const lower = lang.toLowerCase(); + const mapped = LANGUAGE_ALIASES[lower] || lower; + + return (SUPPORTED_LANGUAGES.has(mapped) ? mapped : 'plaintext') as Language; } -const theme: Theme = { - hljs: { - display: 'inline-block', - background: '#252f3f', - color: '#c0e2a3' - }, - 'hljs-subst': { - color: '#d6deeb' - }, - 'hljs-selector-tag': { - color: '#ff6363' - }, - 'hljs-selector-id': { - color: '#fad430', - fontWeight: 'bold' - }, - 'hljs-selector-class': { - color: '#7edcda' - }, - 'hljs-selector-attr': { - color: '#7edcda' - }, - 'hljs-selector-pseudo': { - color: '#74e287' - }, - 'hljs-addition': { - backgroundColor: 'rgba(163, 190, 140, 0.5)' - }, - 'hljs-deletion': { - backgroundColor: 'rgba(191, 97, 106, 0.5)' - }, - 'hljs-built_in': { - color: '#7edcda' - }, - 'hljs-type': { - color: '#7edcda' - }, - 'hljs-class': { - color: '#7edcda' - }, - 'hljs-function': { - color: '#74e287' - }, - 'hljs-function > .hljs-title': { - color: '#74e287' - }, - 'hljs-keyword': { - color: '#64a0dc' - }, - 'hljs-literal': { - color: '#64a0dc' - }, - 'hljs-symbol': { - color: '#64a0dc' - }, - 'hljs-number': { - color: '#d8da68' - }, - 'hljs-regexp': { - color: '#EBCB8B' - }, - 'hljs-string': { +const customTheme = { + plain: { color: '#c0e2a3', - fontWeight: '500' - }, - 'hljs-title': { - color: '#7edcda' - }, - 'hljs-params': { - color: '#d6deeb' - }, - 'hljs-bullet': { - color: '#64a0dc' - }, - 'hljs-code': { - color: '#7edcda' - }, - 'hljs-emphasis': { - fontStyle: 'italic' - }, - 'hljs-formula': { - color: '#7edcda' - }, - 'hljs-strong': { - fontWeight: 'bold' - }, - 'hljs-link:hover': { - textDecoration: 'underline' - }, - 'hljs-quote': { - color: '#797f8c' - }, - 'hljs-comment': { - color: '#797f8c' - }, - 'hljs-doctag': { - color: '#7edcda' - }, - 'hljs-$ref': { - color: 'yellow' - }, - 'hljs-meta': { - color: '#5E81AC' - }, - 'hljs-meta-keyword': { - color: '#5E81AC' - }, - 'hljs-meta-string': { - color: '#c0e2a3' - }, - 'hljs-attr': { - color: '#7edcda' - }, - 'hljs-attribute': { - color: '#d6deeb' - }, - 'hljs-builtin-name': { - color: '#64a0dc' - }, - 'hljs-name': { - color: '#64a0dc' - }, - 'hljs-section': { - color: '#74e287' - }, - 'hljs-tag': { - color: '#64a0dc' - }, - 'hljs-variable': { - color: '#d6deeb' - }, - 'hljs-template-variable': { - color: '#d6deeb' - }, - 'hljs-template-tag': { - color: '#5E81AC' - }, - 'yaml .hljs-meta': { - color: '#D08770' - } + backgroundColor: '#252f3f', + }, + styles: [ + { + types: ['comment', 'prolog', 'doctype', 'cdata'], + style: { + color: '#797f8c', + }, + }, + { + types: ['punctuation'], + style: { + color: '#d6deeb', + }, + }, + { + types: [ + 'property', + 'tag', + 'boolean', + 'number', + 'constant', + 'symbol', + 'deleted', + ], + style: { + color: '#64a0dc', + }, + }, + { + types: ['selector', 'attr-name', 'string', 'char', 'builtin', 'inserted'], + style: { + color: '#c0e2a3', + fontWeight: '500' as const, + }, + }, + { + types: ['operator', 'entity', 'url', 'variable'], + style: { + color: '#d6deeb', + }, + }, + { + types: ['atrule', 'attr-value', 'function', 'class-name'], + style: { + color: '#74e287', + }, + }, + { + types: ['keyword'], + style: { + color: '#64a0dc', + }, + }, + { + types: ['regex', 'important'], + style: { + color: '#EBCB8B', + }, + }, + ], }; +interface CodeLineProps { + line: Token[]; + lineIndex: number; + startingLineNumber: number; + highlightedLines?: number[]; + showLineNumbers: boolean; + getLineProps: (input: { line: Token[] }) => Record; + getTokenProps: (input: { token: Token }) => Record; +} + +/** + * @description Renders a single highlighted line of code. + * Extracted to reduce function nesting depth (SonarCloud S2004). + */ +function CodeLine({ + line, + lineIndex, + startingLineNumber, + highlightedLines, + showLineNumbers, + getLineProps, + getTokenProps, +}: Readonly) { + const lineNumber = lineIndex + startingLineNumber; + const isHighlighted = highlightedLines?.includes(lineNumber); + + return ( +
+ {showLineNumbers && ( + + {lineNumber} + + )} + {line.map((token, tokenIndex) => ( + + ))} +
+ ); +} + /** * @description This component displays code snippets with syntax highlighting. * @@ -209,13 +244,14 @@ export default function CodeBlock({ caption = '', showLineNumbers = true, startingLineNumber = 1, - textSizeClassName = 'text-xs' + textSizeClassName = 'text-xs', }: CodeBlockProps): React.ReactNode { const [activeBlock, setActiveBlock] = useState(0); const [showIsCopied, setShowIsCopied] = useState(false); - // eslint-disable-next-line no-param-reassign - codeBlocks = codeBlocks && codeBlocks.length ? codeBlocks : [{ code: children.replace(/\n$/, '') }]; + const resolvedBlocks = codeBlocks?.length + ? codeBlocks + : [{ code: children.replace(/\n$/, '') }]; const tabItemsCommonClassNames = 'inline-block border-teal-300 py-1 px-2 mx-px cursor-pointer hover:text-teal-300 font-bold'; @@ -223,34 +259,49 @@ export default function CodeBlock({ const tabItemsActiveClassNames = `${tabItemsCommonClassNames} text-teal-300 border-b-2`; /** - * @description This function handles the copy button click event by copying the active code block to the clipboard. + * @description Marks the copy action as successful and resets after 2 seconds. + */ + function markCopied() { + setShowIsCopied(true); + setTimeout(() => setShowIsCopied(false), 2000); + } + + /** + * @description Handles the copy button click by writing code to the clipboard. */ function onClickCopy() { - // check if navigator with clipboard exists (fallback for older browsers) - if (navigator && navigator.clipboard && codeBlocks && codeBlocks[activeBlock]) { - navigator.clipboard.writeText(codeBlocks[activeBlock].code).then(() => { - setShowIsCopied(true); - setTimeout(() => { - setShowIsCopied(false); - }, 2000); - }); - } + const code = resolvedBlocks[activeBlock]?.code; + + if (!code || !navigator?.clipboard) return; + + navigator.clipboard + .writeText(code) + .then(markCopied) + .catch(() => {}); } /** - * @description This function renders the syntax-highlighted code blocks. + * @description Renders the syntax-highlighted code block with optional tab navigation. */ function renderHighlight() { + const currentBlock = resolvedBlocks[activeBlock]; + const codeLanguage = currentBlock?.language || language; + const codeContent = currentBlock?.code || ''; + return ( -
- {codeBlocks && codeBlocks.length > 1 && ( -
+
+ {resolvedBlocks.length > 1 && ( +
@@ -317,28 +351,36 @@ export default function CodeBlock({ return ( <> -
+
{hasWindow && ( -
- - - +
+ + +
)} {showCopy && ( -
+
diff --git a/components/icons/Book.tsx b/components/icons/Book.tsx index 044dd0cf5ec7..21eae028f881 100644 --- a/components/icons/Book.tsx +++ b/components/icons/Book.tsx @@ -1,8 +1,8 @@ +import React from 'react'; + /** * @description Icon component for Book */ -import React from 'react'; - export default function IconBook({ className = '', ...props }: React.SVGProps) { return ( ; + + /** Inline style overrides applied to the outer
 */
+  customStyle?: React.CSSProperties;
+
+  /** Whether to render line numbers */
+  showLineNumbers?: boolean;
+
+  /** The code string to highlight */
+  children?: string;
+}
+
+/**
+ * @description Lightweight syntax highlighting component that replaces react-syntax-highlighter.
+ * Acts as a shim backed by prism-react-renderer to reduce bundle size.
+ */
+function SyntaxHighlighter({
+  language = 'json',
+  customStyle = {},
+  showLineNumbers = false,
+  children = '',
+}: Readonly) {
+  return (
+    
+      {({ className, style, tokens, getLineProps, getTokenProps }) => (
+        
+          {tokens.map((line, i) => (
+            
+ {showLineNumbers && ( + + {i + 1} + + )} + {line.map((token, tokenIndex) => ( + + ))} +
+ ))} +
+ )} +
+ ); +} + +export { SyntaxHighlighter as Prism }; +export { prismLightTheme as prism }; + +export default SyntaxHighlighter; diff --git a/next.config.mjs b/next.config.mjs index 52d52fcc41c3..c047f745efbd 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,3 +1,6 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + import frontmatter from 'remark-frontmatter'; import images from 'remark-images'; import gemoji from 'remark-gemoji-to-emoji'; @@ -7,13 +10,28 @@ import headingId from 'remark-heading-id'; import remarkGfm from 'remark-gfm'; import withMDX from '@next/mdx'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +/** + * Lightweight shim that replaces the heavy react-syntax-highlighter (and its + * Refractor engine with ~280 language definitions) with a thin wrapper around + * prism-react-renderer, which is already used by the project's own CodeBlock + * component. Schyma is the only consumer of react-syntax-highlighter; the + * alias is therefore safe and removes ~2 MB of unused JS from the client + * bundle. + */ +const rshShimPath = path.resolve( + __dirname, + 'components/shims/react-syntax-highlighter-shim.tsx', +); + /** * @type {import('next').NextConfig} */ const nextConfig = { pageExtensions: ['tsx', 'ts', 'md', 'mdx'], eslint: { - ignoreDuringBuilds: true + ignoreDuringBuilds: true, }, output: 'export', webpack(config, { isServer }) { @@ -21,17 +39,48 @@ const nextConfig = { config.resolve.fallback.fs = false; } + // Redirect all react-syntax-highlighter imports to the lightweight shim. + // Schyma imports from two paths: + // 1. 'react-syntax-highlighter' → { Prism } + // 2. 'react-syntax-highlighter/dist/cjs/styles/prism' → { prism } theme + // Both are satisfied by the single shim module. + config.resolve.alias = { + ...config.resolve.alias, + 'react-syntax-highlighter/dist/cjs/styles/prism': rshShimPath, + 'react-syntax-highlighter': rshShimPath, + }; + return config; - } + }, }; const mdxConfig = withMDX({ extension: /\.mdx?$/, - providerImportSource: "@mdx-js/react", + providerImportSource: '@mdx-js/react', options: { - remarkPlugins: [frontmatter, gemoji, headingId, slug, images, a11yEmoji, remarkGfm], - rehypePlugins: [] - } + remarkPlugins: [ + frontmatter, + gemoji, + headingId, + slug, + images, + a11yEmoji, + remarkGfm, + ], + rehypePlugins: [], + }, }); -export default mdxConfig(nextConfig); +export default async function getNextConfig() { + // Only load @next/bundle-analyzer when ANALYZE=true to avoid MODULE_NOT_FOUND + // in production environments where devDependencies are pruned. + const withBundleAnalyzer = + process.env.ANALYZE === 'true' + ? (await import('@next/bundle-analyzer')).default({ + enabled: true, + openAnalyzer: true, + }) + : (config) => config; + + return withBundleAnalyzer(mdxConfig(nextConfig)); +} diff --git a/package-lock.json b/package-lock.json index 2c5305f2891c..ce37c6ef9a0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "autoprefixer": "^10.4.17", "axios": "^1.16.0", "clsx": "^2.1.0", + "cross-env": "^10.1.0", "cssnano": "^6.0.3", "dayjs": "^1.11.19", "dedent": "^1.5.1", @@ -56,16 +57,17 @@ "node-fetch-2": "npm:node-fetch@^2.7.0", "postcss": "^8.5.10", "prettier": "^3.3.3", + "prism-react-renderer": "^2.4.1", "react": "^18", "react-dom": "^18", "react-ga": "^3.3.1", "react-gtm-module": "^2.0.11", "react-scrollspy": "^3.4.3", - "react-syntax-highlighter": "^15.5.0", "react-text-truncate": "^0.19.0", "react-twitter-embed": "^4.0.4", "react-typist-component": "^1.0.6", "react-youtube-embed": "^1.0.3", + "reactflow": "^11.11.4", "reading-time": "^1.5.0", "recharts": "^2.12.2", "remark-frontmatter": "^5.0.0", @@ -87,6 +89,7 @@ "@chromatic-com/storybook": "^1.6.1", "@netlify/functions": "^2.6.0", "@netlify/plugin-nextjs": "^4.41.3", + "@next/bundle-analyzer": "^16.2.9", "@storybook/addon-essentials": "^8.2.4", "@storybook/addon-interactions": "^8.2.4", "@storybook/addon-links": "^8.2.4", @@ -103,7 +106,6 @@ "@types/node": "^20", "@types/react": "^18.0.1", "@types/react-dom": "^18", - "@types/react-syntax-highlighter": "^15.5.11", "@types/react-text-truncate": "^0.14.4", "@types/react-youtube-embed": "^1.0.4", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -2449,6 +2451,16 @@ "node": ">17.0.0" } }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@docsearch/css": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.9.0.tgz", @@ -2520,6 +2532,12 @@ "tslib": "^2.4.0" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -5405,6 +5423,16 @@ "node": ">=18.0.0" } }, + "node_modules/@next/bundle-analyzer": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-16.2.9.tgz", + "integrity": "sha512-yGWyLbC8MMn+hk9j6l6GammpbUz5S3yZzl3lpoWfVXhIpJfh6kAWG7JwF4WoG3f0VnYRY959yo1QQEI8mV/+jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "webpack-bundle-analyzer": "4.10.1" + } + }, "node_modules/@next/env": { "version": "15.5.18", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.18.tgz", @@ -6364,6 +6392,115 @@ "node": ">=8.9.0" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@reactflow/background": { + "version": "11.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz", + "integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/controls": { + "version": "11.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz", + "integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/core": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz", + "integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==", + "license": "MIT", + "dependencies": { + "@types/d3": "^7.4.0", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/minimap": { + "version": "11.7.14", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz", + "integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-resizer": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz", + "integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.4", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-toolbar": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz", + "integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -8060,6 +8197,12 @@ "postcss": "^8.0.0" } }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -8080,7 +8223,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -8101,16 +8244,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-syntax-highlighter": { - "version": "15.5.13", - "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", - "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-text-truncate": { "version": "0.14.4", "resolved": "https://registry.npmjs.org/@types/react-text-truncate/-/react-text-truncate-0.14.4.tgz", @@ -9032,10 +9165,36 @@ "devOptional": true, "license": "Apache-2.0" }, + "node_modules/@xyflow/react": { + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.11.1.tgz", + "integrity": "sha512-L+zBoLGSXham0MnlY8QqjfR7/C5JNw0zxkaey5aZ5XmCgJBAdH4+WRIu8CR40d3l/BdU635V6YbhBK1jMo8/6Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@xyflow/system": "0.0.78", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "@types/react": ">=17", + "@types/react-dom": ">=17", + "react": ">=17", + "react-dom": ">=17" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@xyflow/system": { - "version": "0.0.76", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.76.tgz", - "integrity": "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==", + "version": "0.0.78", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.78.tgz", + "integrity": "sha512-lY0z2qP33fUhTva9Vaxrk0lqZta2pkbxB1trHAx1omnJqRtPvDlAQYV2r5fhS6AdpkulYmbNW0svy+A4/t4B/g==", "license": "MIT", "peer": true, "dependencies": { @@ -9107,6 +9266,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -11644,12 +11816,15 @@ "license": "MIT" }, "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", "license": "MIT", "dependencies": { - "is-what": "^3.14.1" + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" }, "funding": { "url": "https://github.com/sponsors/mesqueeb" @@ -11794,11 +11969,27 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -12773,6 +12964,13 @@ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", "license": "MIT" }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -13248,6 +13446,13 @@ "node": ">= 0.4" } }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -16340,6 +16545,22 @@ "through2": "^2.0.0" } }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/h3": { "version": "1.15.5", "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", @@ -17993,10 +18214,16 @@ } }, "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "license": "MIT" + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } }, "node_modules/is-wsl": { "version": "3.1.0", @@ -18040,7 +18267,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/isobject": { @@ -19571,27 +19797,25 @@ } }, "node_modules/less": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/less/-/less-4.5.1.tgz", - "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", - "hasInstallScript": true, + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/less/-/less-4.6.7.tgz", + "integrity": "sha512-o3UxHBPPVY1HtCXx15/z1NlknQiWyafRNbtLEv+6xFaDRI2g2xPKIH43do9dSwt8bGLTsjNSaifa48N3d6odsQ==", "license": "Apache-2.0", "dependencies": { - "copy-anything": "^2.0.1", - "parse-node-version": "^1.0.1", - "tslib": "^2.3.0" + "copy-anything": "^3.0.5", + "parse-node-version": "^1.0.1" }, "bin": { "lessc": "bin/lessc" }, "engines": { - "node": ">=14" + "node": ">=18" }, "optionalDependencies": { "errno": "^0.1.1", "graceful-fs": "^4.1.2", "image-size": "~0.5.0", - "make-dir": "^2.1.0", + "make-dir": "^5.1.0", "mime": "^1.4.1", "needle": "^3.1.0", "source-map": "~0.6.0" @@ -19611,37 +19835,16 @@ } }, "node_modules/less/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "license": "MIT", - "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/less/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-5.1.0.tgz", + "integrity": "sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==", "license": "MIT", "optional": true, "engines": { - "node": ">=6" - } - }, - "node_modules/less/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/less/node_modules/source-map": { @@ -21852,6 +22055,16 @@ "node": ">=4" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -21934,9 +22147,9 @@ "license": "MIT" }, "node_modules/needle": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", - "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.5.0.tgz", + "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", "license": "MIT", "optional": true, "dependencies": { @@ -23295,6 +23508,16 @@ "node": ">=8" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -23625,7 +23848,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -24835,6 +25057,19 @@ "dev": true, "license": "MIT" }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "license": "MIT", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -25476,6 +25711,24 @@ "stylis": "^3.5.0" } }, + "node_modules/reactflow": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", + "integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==", + "license": "MIT", + "dependencies": { + "@reactflow/background": "11.3.14", + "@reactflow/controls": "11.2.14", + "@reactflow/core": "11.11.4", + "@reactflow/minimap": "11.7.14", + "@reactflow/node-resizer": "2.2.14", + "@reactflow/node-toolbar": "1.3.14" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -27107,9 +27360,9 @@ } }, "node_modules/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "license": "BlueOak-1.0.0", "optional": true, "engines": { @@ -27169,10 +27422,13 @@ } }, "node_modules/schyma/node_modules/@tisoap/react-flow-smart-edge": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@tisoap/react-flow-smart-edge/-/react-flow-smart-edge-4.1.0.tgz", - "integrity": "sha512-y2DuuGQkAKz0yzD7G7IqsXoj12QA04TCJxYti2ipWNApS+TqUBudY/qWd/dxcwiBTPcLDO9PKYwzgDYk6d2z+Q==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@tisoap/react-flow-smart-edge/-/react-flow-smart-edge-4.11.1.tgz", + "integrity": "sha512-XwMwQQ1AGVO5fQyRt+e1CQwUhyolB93CnxX80nXVf4y3XOJpLkE5+oSdnq24NVOPpNfl+ciAcnjEJsReHFXpXg==", "license": "MIT", + "workspaces": [ + "website" + ], "peerDependencies": { "@xyflow/react": ">=12", "react": ">=18", @@ -27180,142 +27436,6 @@ "typescript": ">=5" } }, - "node_modules/schyma/node_modules/@xyflow/react": { - "version": "12.10.2", - "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.2.tgz", - "integrity": "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@xyflow/system": "0.0.76", - "classcat": "^5.0.3", - "zustand": "^4.4.0" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/schyma/node_modules/reactflow": { - "version": "11.11.4", - "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", - "integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==", - "license": "MIT", - "dependencies": { - "@reactflow/background": "11.3.14", - "@reactflow/controls": "11.2.14", - "@reactflow/core": "11.11.4", - "@reactflow/minimap": "11.7.14", - "@reactflow/node-resizer": "2.2.14", - "@reactflow/node-toolbar": "1.3.14" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/schyma/node_modules/reactflow/node_modules/@reactflow/background": { - "version": "11.3.14", - "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz", - "integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==", - "license": "MIT", - "dependencies": { - "@reactflow/core": "11.11.4", - "classcat": "^5.0.3", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/schyma/node_modules/reactflow/node_modules/@reactflow/controls": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz", - "integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==", - "license": "MIT", - "dependencies": { - "@reactflow/core": "11.11.4", - "classcat": "^5.0.3", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/schyma/node_modules/reactflow/node_modules/@reactflow/core": { - "version": "11.11.4", - "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz", - "integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==", - "license": "MIT", - "dependencies": { - "@types/d3": "^7.4.0", - "@types/d3-drag": "^3.0.1", - "@types/d3-selection": "^3.0.3", - "@types/d3-zoom": "^3.0.1", - "classcat": "^5.0.3", - "d3-drag": "^3.0.0", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/schyma/node_modules/reactflow/node_modules/@reactflow/minimap": { - "version": "11.7.14", - "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz", - "integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==", - "license": "MIT", - "dependencies": { - "@reactflow/core": "11.11.4", - "@types/d3-selection": "^3.0.3", - "@types/d3-zoom": "^3.0.1", - "classcat": "^5.0.3", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/schyma/node_modules/reactflow/node_modules/@reactflow/node-resizer": { - "version": "2.2.14", - "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz", - "integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==", - "license": "MIT", - "dependencies": { - "@reactflow/core": "11.11.4", - "classcat": "^5.0.4", - "d3-drag": "^3.0.0", - "d3-selection": "^3.0.0", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/schyma/node_modules/reactflow/node_modules/@reactflow/node-toolbar": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz", - "integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==", - "license": "MIT", - "dependencies": { - "@reactflow/core": "11.11.4", - "classcat": "^5.0.3", - "zustand": "^4.4.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, "node_modules/scriptjs": { "version": "2.5.9", "resolved": "https://registry.npmjs.org/scriptjs/-/scriptjs-2.5.9.tgz", @@ -27499,7 +27619,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -27512,7 +27631,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -27661,6 +27779,21 @@ "dev": true, "license": "MIT" }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -28458,9 +28591,9 @@ "optional": true }, "node_modules/stylus/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "license": "MIT", "optional": true, "dependencies": { @@ -28472,7 +28605,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "optional": true, "dependencies": { @@ -28491,9 +28624,9 @@ } }, "node_modules/stylus/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "optional": true, "dependencies": { @@ -29379,6 +29512,16 @@ "integrity": "sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==", "license": "MIT" }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tough-cookie": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", @@ -30288,9 +30431,9 @@ } }, "node_modules/typescript-plugin-css-modules/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -31450,6 +31593,76 @@ } } }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", + "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "is-plain-object": "^5.0.0", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/webpack-dev-middleware": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.3.tgz", @@ -31572,7 +31785,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" diff --git a/package.json b/package.json index e9fedb282cb9..0f3101dcaf98 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "scripts": { "dev": "npm run build-scripts && next dev", "build": "npm run build-scripts && next build", + "analyze": "cross-env NODE_OPTIONS=--max-old-space-size=16384 npm run build-scripts && cross-env NODE_OPTIONS=--max-old-space-size=16384 ANALYZE=true next build", + "analyze:server": "cross-env ANALYZE=true BUNDLE_ANALYZE=server npm run build", + "analyze:browser": "cross-env ANALYZE=true BUNDLE_ANALYZE=browser npm run build", "test:e2e": "npx cypress run", "test": "jest --passWithNoTests", "build:pages": "tsx scripts/build-pages.ts && npm run format:mdx", @@ -68,6 +71,7 @@ "autoprefixer": "^10.4.17", "axios": "^1.16.0", "clsx": "^2.1.0", + "cross-env": "^10.1.0", "cssnano": "^6.0.3", "dayjs": "^1.11.19", "dedent": "^1.5.1", @@ -94,16 +98,17 @@ "node-fetch-2": "npm:node-fetch@^2.7.0", "postcss": "^8.5.10", "prettier": "^3.3.3", + "prism-react-renderer": "^2.4.1", "react": "^18", "react-dom": "^18", "react-ga": "^3.3.1", "react-gtm-module": "^2.0.11", "react-scrollspy": "^3.4.3", - "react-syntax-highlighter": "^15.5.0", "react-text-truncate": "^0.19.0", "react-twitter-embed": "^4.0.4", "react-typist-component": "^1.0.6", "react-youtube-embed": "^1.0.3", + "reactflow": "^11.11.4", "reading-time": "^1.5.0", "recharts": "^2.12.2", "remark-frontmatter": "^5.0.0", @@ -125,6 +130,7 @@ "@chromatic-com/storybook": "^1.6.1", "@netlify/functions": "^2.6.0", "@netlify/plugin-nextjs": "^4.41.3", + "@next/bundle-analyzer": "^16.2.9", "@storybook/addon-essentials": "^8.2.4", "@storybook/addon-interactions": "^8.2.4", "@storybook/addon-links": "^8.2.4", @@ -141,7 +147,6 @@ "@types/node": "^20", "@types/react": "^18.0.1", "@types/react-dom": "^18", - "@types/react-syntax-highlighter": "^15.5.11", "@types/react-text-truncate": "^0.14.4", "@types/react-youtube-embed": "^1.0.4", "@typescript-eslint/eslint-plugin": "^6.21.0", diff --git a/scripts/build-tools.ts b/scripts/build-tools.ts index eb1e2d98f635..33f2e1677e16 100644 --- a/scripts/build-tools.ts +++ b/scripts/build-tools.ts @@ -11,7 +11,7 @@ const currentDirPath = dirname(currentFilePath); /** * Combines automated and manual tools data. - * + * * @param automatedTools - The automated tools data * @param manualTools - The manual tools data * @param toolsPath - The file path where the combined tools data will be written @@ -25,13 +25,22 @@ async function combineAutomatedAndManualTools( toolsPath: string, tagsPath: string, ignorePath?: string, - ignoredOutputPath?: string + ignoredOutputPath?: string, ) { try { - await combineTools(automatedTools, manualTools, toolsPath, tagsPath, ignorePath, ignoredOutputPath); + await combineTools( + automatedTools, + manualTools, + toolsPath, + tagsPath, + ignorePath, + ignoredOutputPath, + ); } catch (err) { logger.error('Error while combining tools:', err); - throw new Error(`An error occurred while combining tools: ${(err as Error).message}`); + throw new Error( + `An error occurred while combining tools: ${(err as Error).message}`, + ); } } @@ -57,18 +66,30 @@ async function buildTools( toolsPath: string, tagsPath: string, ignorePath?: string, - ignoredOutputPath?: string + ignoredOutputPath?: string, ) { try { const githubExtractData = await getData(); const automatedTools = await convertTools(githubExtractData); - await fs.writeFile(automatedToolsPath, JSON.stringify(automatedTools, null, ' ')); + await fs.writeFile( + automatedToolsPath, + JSON.stringify(automatedTools, null, ' '), + ); const manualTools = JSON.parse(await fs.readFile(manualToolsPath, 'utf-8')); - await combineAutomatedAndManualTools(automatedTools, manualTools, toolsPath, tagsPath, ignorePath, ignoredOutputPath); + await combineAutomatedAndManualTools( + automatedTools, + manualTools, + toolsPath, + tagsPath, + ignorePath, + ignoredOutputPath, + ); } catch (err) { - throw new Error(`An error occurred while building tools: ${(err as Error).message}`); + throw new Error( + `An error occurred while building tools: ${(err as Error).message}`, + ); } } @@ -85,45 +106,81 @@ async function buildTools( * @throws {Error} If the automated or manual tools files are not found, or if an error occurs during the build process. */ async function buildToolsManual( - automatedToolsPath: string, - manualToolsPath: string, - toolsPath: string, + automatedToolsPath: string, + manualToolsPath: string, + toolsPath: string, tagsPath: string, ignorePath?: string, - ignoredOutputPath?: string + ignoredOutputPath?: string, ) { try { - if (!await fs.pathExists(automatedToolsPath)) { + if (!(await fs.pathExists(automatedToolsPath))) { throw new Error( - `Automated tools file not found at ${automatedToolsPath}.`); + `Automated tools file not found at ${automatedToolsPath}.`, + ); } - if (!await fs.pathExists(manualToolsPath)) { + if (!(await fs.pathExists(manualToolsPath))) { throw new Error(`Manual tools file not found at ${manualToolsPath}.`); } - - const automatedTools = JSON.parse(await fs.readFile(automatedToolsPath, 'utf-8')); + + const automatedTools = JSON.parse( + await fs.readFile(automatedToolsPath, 'utf-8'), + ); const manualTools = JSON.parse(await fs.readFile(manualToolsPath, 'utf-8')); - await combineAutomatedAndManualTools(automatedTools, manualTools, toolsPath, tagsPath, ignorePath, ignoredOutputPath); + + await combineAutomatedAndManualTools( + automatedTools, + manualTools, + toolsPath, + tagsPath, + ignorePath, + ignoredOutputPath, + ); } catch (err) { logger.error('Error in buildToolsManual:', err); - throw new Error(`An error occurred while building tools manually: ${(err as Error).message}`); + throw new Error( + `An error occurred while building tools manually: ${(err as Error).message}`, + ); } } /* istanbul ignore next */ if (process.argv[1] === fileURLToPath(import.meta.url)) { - const automatedToolsPath = resolve(currentDirPath, '../config', 'tools-automated.json'); - const manualToolsPath = resolve(currentDirPath, '../config', 'tools-manual.json'); + const automatedToolsPath = resolve( + currentDirPath, + '../config', + 'tools-automated.json', + ); + const manualToolsPath = resolve( + currentDirPath, + '../config', + 'tools-manual.json', + ); const toolsPath = resolve(currentDirPath, '../config', 'tools.json'); const tagsPath = resolve(currentDirPath, '../config', 'all-tags.json'); const ignorePath = resolve(currentDirPath, '../config', 'tools-ignore.json'); - const ignoredOutputPath = resolve(currentDirPath, '../config', 'tools-ignored.json'); + const ignoredOutputPath = resolve( + currentDirPath, + '../config', + 'tools-ignored.json', + ); - buildTools(automatedToolsPath, manualToolsPath, toolsPath, tagsPath, ignorePath, ignoredOutputPath).catch((err) => { - logger.error('Failed to build tools:', err); - process.exit(1); - }); + (async () => { + try { + await buildTools( + automatedToolsPath, + manualToolsPath, + toolsPath, + tagsPath, + ignorePath, + ignoredOutputPath, + ); + } catch (err) { + logger.error('Failed to build tools:', err); + process.exit(1); + } + })(); } export { buildTools, buildToolsManual }; diff --git a/scripts/tools/combine-tools.ts b/scripts/tools/combine-tools.ts index efc59f7f623f..757a6d3b15da 100644 --- a/scripts/tools/combine-tools.ts +++ b/scripts/tools/combine-tools.ts @@ -11,7 +11,7 @@ import type { LanguageColorItem, ToolIgnoreEntry, ToolsIgnoreFile, - ToolsListObject + ToolsListObject, } from '@/types/scripts/tools'; import { logger } from '../helpers/logger'; @@ -31,7 +31,7 @@ const finalTools: FinalToolsListObject = {}; for (const category of categoryList) { finalTools[category.name] = { description: category.description, - toolsList: [] + toolsList: [], }; } @@ -40,7 +40,7 @@ const options = { includeScore: true, shouldSort: true, threshold: 0.39, - keys: ['name', 'color', 'borderColor'] + keys: ['name', 'color', 'borderColor'], }; // Two seperate lists and Fuse objects initialised to search languages and technologies tags @@ -52,7 +52,10 @@ const initialTechnologyCount = technologyList.length; let languageFuse = new Fuse(languageList, options); let technologyFuse = new Fuse(technologyList, options); -function sortColorItems(list: LanguageColorItem[], initialCount: number): LanguageColorItem[] { +function sortColorItems( + list: LanguageColorItem[], + initialCount: number, +): LanguageColorItem[] { const initial = list.slice(0, initialCount); const discovered = list.slice(initialCount); @@ -71,6 +74,83 @@ function sortColorItems(list: LanguageColorItem[], initialCount: number): Langua return [...initial, ...uniqueDiscovered]; } +function resolveTag( + name: string, + list: LanguageColorItem[], + fuse: Fuse, + defaultColor: string, + defaultBorder: string, +): { item: LanguageColorItem; fuse: Fuse } { + const results = fuse.search(name); + + if (results.length > 0) { + return { item: results[0].item, fuse }; + } + + const newItem: LanguageColorItem = { + name, + color: defaultColor, + borderColor: defaultBorder, + }; + + list.push(newItem); + + return { item: newItem, fuse: new Fuse(list, options) }; +} + +async function resolveLanguageTags( + language: string | string[], +): Promise<{ + tags: LanguageColorItem[]; + updatedFuse: Fuse; +}> { + const tags: LanguageColorItem[] = []; + let currentFuse = languageFuse; + const langs = typeof language === 'string' ? [language] : language; + + for (const lang of langs) { + // eslint-disable-next-line no-await-in-loop + const { item, fuse } = resolveTag( + lang, + languageList, + currentFuse, + 'bg-[#57f281]', + 'border-[#37f069]', + ); + + tags.push(item); + currentFuse = fuse; + } + + return { tags, updatedFuse: currentFuse }; +} + +async function resolveTechnologyTags( + technology: string[], +): Promise<{ + tags: LanguageColorItem[]; + updatedFuse: Fuse; +}> { + const tags: LanguageColorItem[] = []; + let currentFuse = technologyFuse; + + for (const tech of technology) { + // eslint-disable-next-line no-await-in-loop + const { item, fuse } = resolveTag( + tech, + technologyList, + currentFuse, + 'bg-[#61d0f2]', + 'border-[#40ccf7]', + ); + + tags.push(item); + currentFuse = fuse; + } + + return { tags, updatedFuse: currentFuse }; +} + /** * Enriches a tool object by processing its language and technology filters for display on the website. * @@ -82,88 +162,36 @@ function sortColorItems(list: LanguageColorItem[], initialCount: number): Langua * @param toolObject - The original tool object containing filter tags. * @returns A promise that resolves to the updated tool object with enriched language and technology filters. */ -export async function getFinalTool(toolObject: AsyncAPITool): Promise { +export async function getFinalTool( + toolObject: AsyncAPITool, +): Promise { const finalObject: FinalAsyncAPITool = { ...toolObject, filters: { language: [], technology: [], categories: toolObject.filters.categories, - hasCommercial: toolObject.filters.hasCommercial - } + hasCommercial: toolObject.filters.hasCommercial, + }, } as FinalAsyncAPITool; - // there might be a tool without language if (toolObject.filters.language) { - const languageArray: LanguageColorItem[] = []; - - if (typeof toolObject.filters.language === 'string') { - const languageSearch = await languageFuse.search(toolObject.filters.language); - - if (languageSearch.length) { - languageArray.push(languageSearch[0].item); - } else { - // adds a new language object in the Fuse list as well as in tool object - // so that it isn't missed out in the UI. - const languageObject = { - name: toolObject.filters.language, - color: 'bg-[#57f281]', - borderColor: 'border-[#37f069]' - }; - - languageList.push(languageObject); - languageArray.push(languageObject); - languageFuse = new Fuse(languageList, options); - } - } else { - for (const language of toolObject.filters.language) { - // eslint-disable-next-line no-await-in-loop - const languageSearch = await languageFuse.search(language); - - if (languageSearch.length > 0) { - languageArray.push(languageSearch[0].item); - } else { - // adds a new language object in the Fuse list as well as in tool object - // so that it isn't missed out in the UI. - const languageObject = { - name: language, - color: 'bg-[#57f281]', - borderColor: 'border-[#37f069]' - }; - - languageList.push(languageObject); - languageArray.push(languageObject); - languageFuse = new Fuse(languageList, options); - } - } - } - finalObject.filters.language = languageArray; + const { tags, updatedFuse } = await resolveLanguageTags( + toolObject.filters.language, + ); + + finalObject.filters.language = tags; + languageFuse = updatedFuse; } - const technologyArray = []; if (toolObject.filters.technology) { - for (const technology of toolObject.filters.technology) { - // eslint-disable-next-line no-await-in-loop - const technologySearch = await technologyFuse.search(technology); - - if (technologySearch.length > 0) { - technologyArray.push(technologySearch[0].item); - } else { - // adds a new technology object in the Fuse list as well as in tool object - // so that it isn't missed out in the UI. - const technologyObject = { - name: technology, - color: 'bg-[#61d0f2]', - borderColor: 'border-[#40ccf7]' - }; - - technologyList.push(technologyObject); - technologyArray.push(technologyObject); - technologyFuse = new Fuse(technologyList, options); - } - } + const { tags, updatedFuse } = await resolveTechnologyTags( + toolObject.filters.technology, + ); + + finalObject.filters.technology = tags; + technologyFuse = updatedFuse; } - finalObject.filters.technology = technologyArray; return finalObject; } @@ -178,16 +206,18 @@ const processManualTool = async (tool: AsyncAPITool) => { tool: tool.title, source: 'manual-tools.json', errors: validate.errors, - note: 'Script continues execution, error logged for investigation' + note: 'Script continues execution, error logged for investigation', }), null, - 2 + 2, ); return null; } const isAsyncAPIrepo = tool?.links?.repoUrl - ? new URL(tool.links.repoUrl).href.startsWith('https://github.com/asyncapi/') + ? new URL(tool.links.repoUrl).href.startsWith( + 'https://github.com/asyncapi/', + ) : false; const toolObject = await createToolObject(tool, '', '', isAsyncAPIrepo); @@ -197,12 +227,19 @@ const processManualTool = async (tool: AsyncAPITool) => { /** * Checks whether a single ignore entry matches the given tool in the given category. */ -function doesEntryMatchTool(entry: ToolIgnoreEntry, tool: AsyncAPITool, category: string): boolean { +function doesEntryMatchTool( + entry: ToolIgnoreEntry, + tool: AsyncAPITool, + category: string, +): boolean { if (!entry.title && !entry.repoUrl) return false; - if (entry.categories?.length && !entry.categories.includes(category)) return false; + if (entry.categories?.length && !entry.categories.includes(category)) + return false; const titleMatches = entry.title ? tool.title === entry.title : true; - const repoMatches = entry.repoUrl ? tool.links?.repoUrl === entry.repoUrl : true; + const repoMatches = entry.repoUrl + ? tool.links?.repoUrl === entry.repoUrl + : true; return titleMatches && repoMatches; } @@ -219,8 +256,15 @@ function doesEntryMatchTool(entry: ToolIgnoreEntry, tool: AsyncAPITool, category * - Only `repoUrl` provided: any tool with that exact repoUrl matches. * - If `categories` is provided, the match only applies within those categories. */ -function shouldIgnoreTool(tool: AsyncAPITool, category: string, ignoreList: ToolIgnoreEntry[]): ToolIgnoreEntry | null { - return ignoreList.find((entry) => doesEntryMatchTool(entry, tool, category)) ?? null; +function shouldIgnoreTool( + tool: AsyncAPITool, + category: string, + ignoreList: ToolIgnoreEntry[], +): ToolIgnoreEntry | null { + return ( + ignoreList.find((entry) => doesEntryMatchTool(entry, tool, category)) ?? + null + ); } /** @@ -240,44 +284,47 @@ const combineTools = async ( toolsPath: string, tagsPath: string, ignorePath?: string, - ignoredOutputPath?: string + ignoredOutputPath?: string, ): Promise => { try { let ignoreList: ToolIgnoreEntry[] = []; const ignoredTools: IgnoredToolRecord[] = []; if (ignorePath && fs.existsSync(ignorePath)) { - const ignoreFile: ToolsIgnoreFile = JSON.parse(fs.readFileSync(ignorePath, 'utf-8')); + const ignoreFile: ToolsIgnoreFile = JSON.parse( + fs.readFileSync(ignorePath, 'utf-8'), + ); ignoreList = ignoreFile.tools || []; } - // eslint-disable-next-line no-restricted-syntax - for (const key in automatedTools) { - if (Object.prototype.hasOwnProperty.call(automatedTools, key)) { - const filteredAutomated = automatedTools[key].toolsList.filter((tool) => { - const matchedEntry = shouldIgnoreTool(tool, key, ignoreList); - - if (matchedEntry) { - ignoredTools.push({ - title: tool.title, - repoUrl: tool.links?.repoUrl, - reason: matchedEntry.reason, - category: key, - source: 'automated', - ignoredAt: new Date().toISOString() - }); - - return false; - } + for (const key of Object.keys(automatedTools)) { + const filteredAutomated = automatedTools[key].toolsList.filter((tool) => { + const matchedEntry = shouldIgnoreTool(tool, key, ignoreList); + + if (matchedEntry) { + ignoredTools.push({ + title: tool.title, + repoUrl: tool.links?.repoUrl, + reason: matchedEntry.reason, + category: key, + source: 'automated', + ignoredAt: new Date().toISOString(), + }); + + return false; + } - return true; - }); + return true; + }); - // eslint-disable-next-line no-await-in-loop - const automatedResults = await Promise.all(filteredAutomated.map(getFinalTool)); + // eslint-disable-next-line no-await-in-loop + const automatedResults = await Promise.all( + filteredAutomated.map(getFinalTool), + ); - const filteredManual = (manualTools[key]?.toolsList || []).filter((tool) => { + const filteredManual = (manualTools[key]?.toolsList || []).filter( + (tool) => { const matchedEntry = shouldIgnoreTool(tool, key, ignoreList); if (matchedEntry) { @@ -287,34 +334,38 @@ const combineTools = async ( reason: matchedEntry.reason, category: key, source: 'manual', - ignoredAt: new Date().toISOString() + ignoredAt: new Date().toISOString(), }); return false; } return true; - }); + }, + ); - const manualResults = filteredManual.length - ? // eslint-disable-next-line no-await-in-loop - (await Promise.all(filteredManual.map(processManualTool))).filter(Boolean) - : []; + const manualResults = filteredManual.length + ? // eslint-disable-next-line no-await-in-loop + (await Promise.all(filteredManual.map(processManualTool))).filter( + Boolean, + ) + : []; - finalTools[key].toolsList = [...automatedResults, ...manualResults].sort((tool, anotherTool) => { + finalTools[key].toolsList = [...automatedResults, ...manualResults].sort( + (tool, anotherTool) => { if (!tool?.title || !anotherTool?.title) { logger.error({ message: 'Tool title is missing during sort', detail: { tool, anotherTool }, - source: 'combine-tools.ts' + source: 'combine-tools.ts', }); return 0; } return compareToolsDeterministic(tool, anotherTool); - }) as FinalAsyncAPITool[]; - } + }, + ) as FinalAsyncAPITool[]; } fs.writeFileSync(toolsPath, JSON.stringify(finalTools, null, 2)); @@ -323,22 +374,27 @@ const combineTools = async ( JSON.stringify( { languages: sortColorItems(languageList, initialLanguageCount), - technologies: sortColorItems(technologyList, initialTechnologyCount) + technologies: sortColorItems(technologyList, initialTechnologyCount), }, null, - 2 - ) + 2, + ), ); if (ignoredTools.length > 0) { logger.info( `Tools ignored: ${ignoredTools.length} tool(s) removed by ${ignoreList.length} ignore rule(s).\n` + ignoredTools - .map((t) => ` - "${t.title}" (${t.repoUrl || 'no repo'}) from [${t.category}]`) - .join('\n') + .map( + (t) => + ` - "${t.title}" (${t.repoUrl || 'no repo'}) from [${t.category}]`, + ) + .join('\n'), ); } else if (ignoreList.length > 0) { - logger.info(`Tools ignored: 0 (none of the ${ignoreList.length} ignore rule(s) matched any tool).`); + logger.info( + `Tools ignored: 0 (none of the ${ignoreList.length} ignore rule(s) matched any tool).`, + ); } if (ignoredOutputPath && ignoredTools.length > 0) { @@ -346,28 +402,30 @@ const combineTools = async ( ignoredOutputPath, JSON.stringify( { - description: 'Auto-generated audit log of tools ignored during the last combine run.', + description: + 'Auto-generated audit log of tools ignored during the last combine run.', generatedAt: new Date().toISOString(), totalIgnored: ignoredTools.length, - ignoredTools + ignoredTools, }, null, - 2 - ) + 2, + ), ); } else if (ignoredOutputPath && ignoredTools.length === 0) { fs.writeFileSync( ignoredOutputPath, JSON.stringify( { - description: 'Auto-generated audit log of tools ignored during the last combine run.', + description: + 'Auto-generated audit log of tools ignored during the last combine run.', generatedAt: new Date().toISOString(), totalIgnored: 0, - ignoredTools: [] + ignoredTools: [], }, null, - 2 - ) + 2, + ), ); } } catch (err) {