diff --git a/.claude/eslint-rules/prefer-dynamic-import-with-feature-guard.js b/.claude/eslint-rules/prefer-dynamic-import-with-feature-guard.js new file mode 100644 index 0000000..43a1471 --- /dev/null +++ b/.claude/eslint-rules/prefer-dynamic-import-with-feature-guard.js @@ -0,0 +1,68 @@ +/** + * ESLint rule: prefer-dynamic-import-with-feature-guard + * + * Warns when a file has both static non-framework imports and a browser feature + * detection guard (e.g. `if (!navigator.xxx) return`), which suggests the + * import should be dynamic to avoid loading the module when the feature is unavailable. + */ + +const FRAMEWORK_PACKAGES = /^(react|react-dom|next|next-cloudinary|nextra)($|\/)/ + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: + 'Prefer dynamic imports when the module is only used inside a browser feature guard.', + }, + messages: { + preferDynamic: + 'Static import "{{ source }}" may be loaded unnecessarily. ' + + 'Consider a dynamic import inside the feature guard (e.g. `if (!navigator.xxx) return`).', + }, + }, + + create(context) { + const projectImports = [] + let hasNavigatorGuard = false + + function isNavigatorGuard(node) { + // Matches: !navigator.foo or !navigator?.foo + if (node.type !== 'UnaryExpression' || node.operator !== '!') return false + const arg = node.argument + if (arg.type === 'MemberExpression') { + return arg.object?.name === 'navigator' + } + if (arg.type === 'ChainExpression') { + return arg.expression?.object?.name === 'navigator' + } + return false + } + + return { + ImportDeclaration(node) { + if (!FRAMEWORK_PACKAGES.test(node.source.value)) { + projectImports.push(node) + } + }, + + IfStatement(node) { + if (isNavigatorGuard(node.test)) { + hasNavigatorGuard = true + } + }, + + 'Program:exit'() { + if (hasNavigatorGuard && projectImports.length > 0) { + projectImports.forEach((imp) => { + context.report({ + node: imp, + messageId: 'preferDynamic', + data: { source: imp.source.value }, + }) + }) + } + }, + } + }, +} diff --git a/.claude/eslint.config.cjs b/.claude/eslint.config.cjs new file mode 100644 index 0000000..548dbef --- /dev/null +++ b/.claude/eslint.config.cjs @@ -0,0 +1,23 @@ +const preferDynamicImport = require('./eslint-rules/prefer-dynamic-import-with-feature-guard.js') + +module.exports = [ + { + plugins: { + local: { + rules: { + 'prefer-dynamic-import-with-feature-guard': preferDynamicImport, + }, + }, + }, + rules: { + 'local/prefer-dynamic-import-with-feature-guard': 'warn', + }, + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module', + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + }, + }, +] diff --git a/.claude/hooks/eslint-check.sh b/.claude/hooks/eslint-check.sh new file mode 100755 index 0000000..a5fd085 --- /dev/null +++ b/.claude/hooks/eslint-check.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# PostToolUse hook: runs ESLint with the prefer-dynamic-import-with-feature-guard +# rule on any JS/JSX file that Claude just wrote or edited. + +INPUT=$(cat) + +FILE_PATH=$(echo "$INPUT" | node -e " + process.stdin.setEncoding('utf8') + let data = '' + process.stdin.on('data', chunk => data += chunk) + process.stdin.on('end', () => { + try { + const parsed = JSON.parse(data) + process.stdout.write(parsed.tool_input?.file_path || '') + } catch {} + }) +") + +# Only lint JS/JSX files +if [[ -z "$FILE_PATH" ]] || [[ ! "$FILE_PATH" =~ \.(js|jsx)$ ]]; then + exit 0 +fi + +if [[ ! -f "$FILE_PATH" ]]; then + exit 0 +fi + +CONFIG="$(cd "$(dirname "$0")/.." && pwd)/eslint.config.cjs" + +result=$(npx eslint --config "$CONFIG" "$FILE_PATH" 2>&1) + +if [[ -n "$result" ]]; then + echo "$result" + exit 1 +fi + +exit 0 diff --git a/components/LazyVideoPlayer.js b/components/LazyVideoPlayer.js new file mode 100644 index 0000000..2f4127f --- /dev/null +++ b/components/LazyVideoPlayer.js @@ -0,0 +1,60 @@ +import { useState } from 'react' +import { CldVideoPlayer } from 'next-cloudinary' + +export function LazyVideoPlayer({ src, width, height, poster }) { + const [playing, setPlaying] = useState(false) + + if (playing) { + return + } + + return ( +
setPlaying(true)} + style={{ + position: 'relative', + cursor: 'pointer', + background: '#000', + aspectRatio: `${width} / ${height}`, + overflow: 'hidden', + }} + > + {poster && ( + Video tutorial preview + )} +
+
+ ▶ +
+
+
+ ) +} diff --git a/components/WebMCP.js b/components/WebMCP.js new file mode 100644 index 0000000..fa351f4 --- /dev/null +++ b/components/WebMCP.js @@ -0,0 +1,95 @@ +import { useEffect } from 'react' + +export function WebMCP() { + useEffect(() => { + if (!navigator.modelContext) return + + import(/* webpackChunkName: "snippets-registry" */ '../lib/snippets-registry').then(({ snippets }) => { + navigator.modelContext.registerTool({ + name: 'list_snippets', + description: + 'List all web performance measurement snippets available on this site. Returns metadata without code.', + inputSchema: { type: 'object', properties: {} }, + execute: () => + snippets.map(({ id, category, title, description, url }) => ({ + id, + category, + title, + description, + url, + })), + }) + + navigator.modelContext.registerTool({ + name: 'get_snippet', + description: + 'Get the full JavaScript code for a specific web performance snippet by its ID.', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: + 'Snippet ID (e.g. "LCP", "CLS", "TTFB", "INP"). Use list_snippets to discover available IDs.', + }, + }, + required: ['id'], + }, + execute: ({ id }) => { + const snippet = snippets.find((s) => s.id === id) + if (!snippet) { + return { error: `Snippet "${id}" not found. Use list_snippets to see available IDs.` } + } + return snippet + }, + }) + + navigator.modelContext.registerTool({ + name: 'search_snippets', + description: + 'Search web performance snippets by category and/or keyword. Returns metadata without code.', + inputSchema: { + type: 'object', + properties: { + category: { + type: 'string', + description: + 'Filter by category. One of: CoreWebVitals, Loading, Interaction, Media, Resources.', + }, + query: { + type: 'string', + description: 'Keyword to filter by title or description.', + }, + }, + }, + execute: ({ category, query } = {}) => { + let results = snippets + + if (category) { + results = results.filter((s) => s.category === category) + } + + if (query) { + const q = query.toLowerCase() + results = results.filter( + (s) => + s.title.toLowerCase().includes(q) || + s.description.toLowerCase().includes(q) || + s.id.toLowerCase().includes(q) + ) + } + + return results.map(({ id, category, title, description, url }) => ({ + id, + category, + title, + description, + url, + })) + }, + }) + }) + }, []) + + return null +} diff --git a/lib/snippets-registry.js b/lib/snippets-registry.js new file mode 100644 index 0000000..697ebeb --- /dev/null +++ b/lib/snippets-registry.js @@ -0,0 +1,286 @@ +import cls from '../snippets/CoreWebVitals/CLS.js?raw' +import inp from '../snippets/CoreWebVitals/INP.js?raw' +import lcp from '../snippets/CoreWebVitals/LCP.js?raw' +import lcpImageEntropy from '../snippets/CoreWebVitals/LCP-Image-Entropy.js?raw' +import lcpSubParts from '../snippets/CoreWebVitals/LCP-Sub-Parts.js?raw' +import lcpTrail from '../snippets/CoreWebVitals/LCP-Trail.js?raw' +import lcpVideoCandidate from '../snippets/CoreWebVitals/LCP-Video-Candidate.js?raw' + +import ttfb from '../snippets/Loading/TTFB.js?raw' +import jsExecutionTimeBreakdown from '../snippets/Loading/JS-Execution-Time-Breakdown.js?raw' +import fcp from '../snippets/Loading/FCP.js?raw' +import backForwardCache from '../snippets/Loading/Back-Forward-Cache.js?raw' +import cssMediaQueriesAnalysis from '../snippets/Loading/CSS-Media-Queries-Analysis.js?raw' +import criticalCSSDetection from '../snippets/Loading/Critical-CSS-Detection.js?raw' +import ssrHydrationDataAnalysis from '../snippets/Loading/SSR-Hydration-Data-Analysis.js?raw' +import validatePreloadAsyncDeferScripts from '../snippets/Loading/Validate-Preload-Async-Defer-Scripts.js?raw' +import resourceHintsValidation from '../snippets/Loading/Resource-Hints-Validation.js?raw' +import prefetchResourceValidation from '../snippets/Loading/Prefetch-Resource-Validation.js?raw' +import priorityHintsAudit from '../snippets/Loading/Priority-Hints-Audit.js?raw' +import serviceWorkerAnalysis from '../snippets/Loading/Service-Worker-Analysis.js?raw' + +import interactions from '../snippets/Interaction/Interactions.js?raw' +import inputLatencyBreakdown from '../snippets/Interaction/Input-Latency-Breakdown.js?raw' +import layoutShiftLoadingAndInteraction from '../snippets/Interaction/Layout-Shift-Loading-and-Interaction.js?raw' +import longAnimationFrames from '../snippets/Interaction/Long-Animation-Frames.js?raw' +import longAnimationFramesScriptAttribution from '../snippets/Interaction/Long-Animation-Frames-Script-Attribution.js?raw' +import longAnimationFramesHelpers from '../snippets/Interaction/Long-Animation-Frames-Helpers.js?raw' +import longTask from '../snippets/Interaction/LongTask.js?raw' +import scrollPerformance from '../snippets/Interaction/Scroll-Performance.js?raw' + +import imageElementAudit from '../snippets/Media/Image-Element-Audit.js?raw' +import videoElementAudit from '../snippets/Media/Video-Element-Audit.js?raw' +import svgEmbeddedBitmapAnalysis from '../snippets/Media/SVG-Embedded-Bitmap-Analysis.js?raw' + +import networkBandwidthConnectionQuality from '../snippets/Resources/Network-Bandwidth-Connection-Quality.js?raw' + +export const snippets = [ + { + id: 'CLS', + category: 'CoreWebVitals', + title: 'Cumulative Layout Shift (CLS)', + description: 'Measures visual stability by tracking unexpected layout shifts during page load', + url: '/CoreWebVitals/CLS', + code: cls, + }, + { + id: 'INP', + category: 'CoreWebVitals', + title: 'Interaction to Next Paint (INP)', + description: 'Measures responsiveness by tracking the latency of all user interactions', + url: '/CoreWebVitals/INP', + code: inp, + }, + { + id: 'LCP', + category: 'CoreWebVitals', + title: 'Largest Contentful Paint (LCP)', + description: 'Measures loading performance by timing when the largest visible content element renders', + url: '/CoreWebVitals/LCP', + code: lcp, + }, + { + id: 'LCP-Image-Entropy', + category: 'CoreWebVitals', + title: 'LCP Image Entropy', + description: 'Analyzes the visual complexity of LCP images to identify optimization opportunities', + url: '/CoreWebVitals/LCP-Image-Entropy', + code: lcpImageEntropy, + }, + { + id: 'LCP-Sub-Parts', + category: 'CoreWebVitals', + title: 'LCP Sub-Parts', + description: 'Breaks down LCP into TTFB, load delay, load time, and render delay phases', + url: '/CoreWebVitals/LCP-Sub-Parts', + code: lcpSubParts, + }, + { + id: 'LCP-Trail', + category: 'CoreWebVitals', + title: 'LCP Trail', + description: 'Traces the chain of LCP candidates over time to identify the final LCP element', + url: '/CoreWebVitals/LCP-Trail', + code: lcpTrail, + }, + { + id: 'LCP-Video-Candidate', + category: 'CoreWebVitals', + title: 'LCP Video Candidate', + description: 'Detects when a video element is the LCP candidate and analyzes its loading', + url: '/CoreWebVitals/LCP-Video-Candidate', + code: lcpVideoCandidate, + }, + { + id: 'TTFB', + category: 'Loading', + title: 'Time to First Byte (TTFB)', + description: 'Measures server response time and network latency for the initial HTML document', + url: '/Loading/TTFB', + code: ttfb, + }, + { + id: 'JS-Execution-Time-Breakdown', + category: 'Loading', + title: 'JS Execution Time Breakdown', + description: 'Profiles JavaScript execution time per script to identify slow-parsing or long-running scripts', + url: '/Loading/JS-Execution-Time-Breakdown', + code: jsExecutionTimeBreakdown, + }, + { + id: 'FCP', + category: 'Loading', + title: 'First Contentful Paint (FCP)', + description: 'Measures when the browser renders the first piece of content from the DOM', + url: '/Loading/FCP', + code: fcp, + }, + { + id: 'Back-Forward-Cache', + category: 'Loading', + title: 'Back/Forward Cache (bfcache)', + description: 'Checks bfcache eligibility and identifies reasons that prevent pages from being cached', + url: '/Loading/Back-Forward-Cache', + code: backForwardCache, + }, + { + id: 'CSS-Media-Queries-Analysis', + category: 'Loading', + title: 'CSS Media Queries Analysis', + description: 'Audits CSS stylesheets for media query usage and render-blocking impact', + url: '/Loading/CSS-Media-Queries-Analysis', + code: cssMediaQueriesAnalysis, + }, + { + id: 'Critical-CSS-Detection', + category: 'Loading', + title: 'Critical CSS Detection', + description: 'Identifies above-the-fold CSS rules to help inline critical styles and defer the rest', + url: '/Loading/Critical-CSS-Detection', + code: criticalCSSDetection, + }, + { + id: 'SSR-Hydration-Data-Analysis', + category: 'Loading', + title: 'SSR Framework Hydration Data Analysis', + description: 'Analyzes server-side rendering hydration payloads (Next.js __NEXT_DATA__, etc.) for size and content', + url: '/Loading/SSR-Hydration-Data-Analysis', + code: ssrHydrationDataAnalysis, + }, + { + id: 'Validate-Preload-Async-Defer-Scripts', + category: 'Loading', + title: 'Validate Preload on Async/Defer Scripts', + description: 'Detects anti-patterns where scripts use both preload and async/defer, causing redundant requests', + url: '/Loading/Validate-Preload-Async-Defer-Scripts', + code: validatePreloadAsyncDeferScripts, + }, + { + id: 'Resource-Hints-Validation', + category: 'Loading', + title: 'Resource Hints Validation', + description: 'Audits dns-prefetch, preconnect, prefetch, and preload hints for correctness and effectiveness', + url: '/Loading/Resource-Hints-Validation', + code: resourceHintsValidation, + }, + { + id: 'Prefetch-Resource-Validation', + category: 'Loading', + title: 'Prefetch Resource Validation', + description: 'Verifies that prefetched resources are actually used and identifies unused prefetches', + url: '/Loading/Prefetch-Resource-Validation', + code: prefetchResourceValidation, + }, + { + id: 'Priority-Hints-Audit', + category: 'Loading', + title: 'Priority Hints Audit', + description: 'Checks fetchpriority attributes on resources to ensure critical assets load first', + url: '/Loading/Priority-Hints-Audit', + code: priorityHintsAudit, + }, + { + id: 'Service-Worker-Analysis', + category: 'Loading', + title: 'Service Worker Analysis', + description: 'Inspects registered service workers, their scope, state, and caching strategies', + url: '/Loading/Service-Worker-Analysis', + code: serviceWorkerAnalysis, + }, + { + id: 'Interactions', + category: 'Interaction', + title: 'Interactions', + description: 'Tracks all user interactions (clicks, key presses, pointer events) with their durations', + url: '/Interaction/Interactions', + code: interactions, + }, + { + id: 'Input-Latency-Breakdown', + category: 'Interaction', + title: 'Input Latency Breakdown', + description: 'Decomposes input delay into processing time and presentation delay for INP debugging', + url: '/Interaction/Input-Latency-Breakdown', + code: inputLatencyBreakdown, + }, + { + id: 'Layout-Shift-Loading-and-Interaction', + category: 'Interaction', + title: 'Layout Shift Loading and Interaction', + description: 'Separates CLS into shifts caused by page loading versus user interactions', + url: '/Interaction/Layout-Shift-Loading-and-Interaction', + code: layoutShiftLoadingAndInteraction, + }, + { + id: 'Long-Animation-Frames', + category: 'Interaction', + title: 'Long Animation Frames', + description: 'Identifies frames that take longer than 50ms to render, causing jank and poor responsiveness', + url: '/Interaction/Long-Animation-Frames', + code: longAnimationFrames, + }, + { + id: 'Long-Animation-Frames-Script-Attribution', + category: 'Interaction', + title: 'Long Animation Frames Script Attribution', + description: 'Attributes long animation frames to specific scripts to identify the root cause of jank', + url: '/Interaction/Long-Animation-Frames-Script-Attribution', + code: longAnimationFramesScriptAttribution, + }, + { + id: 'Long-Animation-Frames-Helpers', + category: 'Interaction', + title: 'Long Animation Frames Helpers', + description: 'Utility functions for filtering and analyzing long animation frame data', + url: '/Interaction/Long-Animation-Frames-Helpers', + code: longAnimationFramesHelpers, + }, + { + id: 'LongTask', + category: 'Interaction', + title: 'Long Task', + description: 'Detects JavaScript tasks that block the main thread for more than 50ms', + url: '/Interaction/LongTask', + code: longTask, + }, + { + id: 'Scroll-Performance', + category: 'Interaction', + title: 'Scroll Performance', + description: 'Measures scroll jank and frame rate during scrolling to diagnose smooth scrolling issues', + url: '/Interaction/Scroll-Performance', + code: scrollPerformance, + }, + { + id: 'Image-Element-Audit', + category: 'Media', + title: 'Image Element Audit', + description: 'Audits all images for format, dimensions, lazy loading, fetchpriority, and sizing issues', + url: '/Media/Image-Element-Audit', + code: imageElementAudit, + }, + { + id: 'Video-Element-Audit', + category: 'Media', + title: 'Video Element Audit', + description: 'Inspects video elements for autoplay, preload strategy, poster images, and performance impact', + url: '/Media/Video-Element-Audit', + code: videoElementAudit, + }, + { + id: 'SVG-Embedded-Bitmap-Analysis', + category: 'Media', + title: 'SVG Embedded Bitmap Analysis', + description: 'Detects SVGs that embed raster images (base64 or URL), defeating SVG size benefits', + url: '/Media/SVG-Embedded-Bitmap-Analysis', + code: svgEmbeddedBitmapAnalysis, + }, + { + id: 'Network-Bandwidth-Connection-Quality', + category: 'Resources', + title: 'Network Bandwidth & Connection Quality', + description: 'Reads Network Information API to detect connection type, bandwidth, RTT, and save-data mode', + url: '/Resources/Network-Bandwidth-Connection-Quality', + code: networkBandwidthConnectionQuality, + }, +] diff --git a/package-lock.json b/package-lock.json index 3453197..54aec94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "eslint": "^10.0.2", "vercel": "^32.4.1" } }, @@ -244,6 +245,152 @@ "node": ">=16" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", + "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.2", + "debug": "^4.3.1", + "minimatch": "^10.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", + "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", + "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", + "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", @@ -270,6 +417,58 @@ "react-dom": "^16 || ^17 || ^18" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1695,6 +1894,13 @@ "@types/ms": "*" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2346,9 +2552,9 @@ "license": "ISC" }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2403,12 +2609,11 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4012,6 +4217,13 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/del": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", @@ -4954,6 +5166,236 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", + "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.2", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.0", + "@eslint/plugin-kit": "^0.6.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.1", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.1", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", + "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", + "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -4967,6 +5409,42 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-util-attach-comments": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-2.1.1.tgz", @@ -5069,6 +5547,16 @@ "@types/estree": "^1.0.0" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -5185,8 +5673,14 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" }, "node_modules/fastq": { "version": "1.20.1", @@ -5223,6 +5717,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -5285,6 +5792,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", + "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "dev": true, + "license": "ISC" + }, "node_modules/flexsearch": { "version": "0.7.43", "resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.7.43.tgz", @@ -6927,6 +7455,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -7304,6 +7842,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -7333,8 +7878,14 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" }, "node_modules/jsonc-parser": { "version": "3.3.1", @@ -7370,6 +7921,16 @@ "katex": "cli.js" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/khroma": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", @@ -7399,6 +7960,20 @@ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", "license": "MIT" }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9254,6 +9829,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -12991,6 +13573,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/os-paths": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/os-paths/-/os-paths-4.4.0.tgz", @@ -13443,6 +14043,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-ms": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", @@ -18919,6 +19529,19 @@ "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", @@ -19593,6 +20216,16 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index 3b34d93..53a8d49 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "eslint": "^10.0.2", "vercel": "^32.4.1" }, "overrides": { diff --git a/pages/_app.js b/pages/_app.js index 2d9d8ae..5644bd2 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,9 +1,11 @@ import Script from "next/script"; import "../styles/globals.css"; +import { WebMCP } from "../components/WebMCP"; function WebPerfSnippets({ Component, pageProps }) { return ( <> +