From 592dacad39fa0b7146b33353aeca0c24606ac959 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 26 Feb 2025 09:50:47 -0700 Subject: [PATCH 01/98] Start adding changes to address json fields --- client/src/components/DynamicJsonForm.tsx | 54 +++++++++++++++++++---- client/src/components/JsonEditor.tsx | 42 +++++++++++++++--- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index a15b57e..709b0d4 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -41,9 +41,6 @@ const DynamicJsonForm = ({ onChange, maxDepth = 3, }: DynamicJsonFormProps) => { - const [isJsonMode, setIsJsonMode] = useState(false); - const [jsonError, setJsonError] = useState(); - const generateDefaultValue = (propSchema: JsonSchemaType): JsonValue => { switch (propSchema.type) { case "string": @@ -69,6 +66,40 @@ const DynamicJsonForm = ({ } }; + const [isJsonMode, setIsJsonMode] = useState(false); + const [jsonError, setJsonError] = useState(); + // Add state for storing raw JSON value + const [rawJsonValue, setRawJsonValue] = useState( + JSON.stringify(value ?? generateDefaultValue(schema), null, 2) + ); + + const validateJsonBeforeSubmit = () => { + if (isJsonMode && rawJsonValue) { + try { + const parsed = JSON.parse(rawJsonValue); + onChange(parsed); + setJsonError(undefined); + return true; + } catch (err) { + setJsonError(err instanceof Error ? err.message : "Invalid JSON"); + return false; + } + } + return true; + }; + + const handleSwitchToFormMode = () => { + if (isJsonMode) { + if (validateJsonBeforeSubmit()) { + setIsJsonMode(false); + } + } else { + // Update raw JSON value when switching to JSON mode + setRawJsonValue(JSON.stringify(value ?? generateDefaultValue(schema), null, 2)); + setIsJsonMode(true); + } + }; + const renderFormFields = ( propSchema: JsonSchemaType, currentValue: JsonValue, @@ -329,7 +360,7 @@ const DynamicJsonForm = ({ @@ -337,13 +368,18 @@ const DynamicJsonForm = ({ {isJsonMode ? ( { + setRawJsonValue(newValue); try { - onChange(JSON.parse(newValue)); - setJsonError(undefined); - } catch (err) { - setJsonError(err instanceof Error ? err.message : "Invalid JSON"); + if (/^\s*[{[].*[}\]]\s*$/.test(newValue)) { + const parsed = JSON.parse(newValue); + onChange(parsed); + setJsonError(undefined); + } + } catch { + // Don't set an error during typing - that will happen when the user + // tries to save or submit the form } }} error={jsonError} diff --git a/client/src/components/JsonEditor.tsx b/client/src/components/JsonEditor.tsx index 2fb7e26..9109088 100644 --- a/client/src/components/JsonEditor.tsx +++ b/client/src/components/JsonEditor.tsx @@ -1,3 +1,4 @@ +import { useState, useEffect } from "react"; import Editor from "react-simple-code-editor"; import Prism from "prismjs"; import "prismjs/components/prism-json"; @@ -10,34 +11,63 @@ interface JsonEditorProps { error?: string; } -const JsonEditor = ({ value, onChange, error }: JsonEditorProps) => { +const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps) => { + const [editorContent, setEditorContent] = useState(value); + const [internalError, setInternalError] = useState(undefined); + + useEffect(() => { + setEditorContent(value); + }, [value]); + const formatJson = (json: string): string => { try { + // Handle empty arrays and objects specifically + if (json.trim() === '[]') return '[]'; + if (json.trim() === '{}') return '{}'; return JSON.stringify(JSON.parse(json), null, 2); } catch { return json; } }; + const handleEditorChange = (newContent: string) => { + setEditorContent(newContent); + setInternalError(undefined); + onChange(newContent); + }; + + const handleFormatJson = () => { + try { + const formatted = formatJson(editorContent); + setEditorContent(formatted); + onChange(formatted); + setInternalError(undefined); + } catch (err) { + setInternalError(err instanceof Error ? err.message : "Invalid JSON"); + } + }; + + const displayError = internalError || externalError; + return (
Prism.highlight(code, Prism.languages.json, "json") } @@ -51,7 +81,7 @@ const JsonEditor = ({ value, onChange, error }: JsonEditorProps) => { className="w-full" />
- {error &&

{error}

} + {displayError &&

{displayError}

}
); }; From 0e29e2c1cf2884df1c7c33f1bb76e8da9e1a332e Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 26 Feb 2025 19:34:33 -0700 Subject: [PATCH 02/98] Resolve issues where JSON fields are not being rendered in form mode --- client/src/components/DynamicJsonForm.tsx | 107 +++++++++++++++------- 1 file changed, 74 insertions(+), 33 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 709b0d4..d76b151 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -73,25 +73,24 @@ const DynamicJsonForm = ({ JSON.stringify(value ?? generateDefaultValue(schema), null, 2) ); - const validateJsonBeforeSubmit = () => { - if (isJsonMode && rawJsonValue) { - try { - const parsed = JSON.parse(rawJsonValue); - onChange(parsed); - setJsonError(undefined); - return true; - } catch (err) { - setJsonError(err instanceof Error ? err.message : "Invalid JSON"); - return false; - } + // Update rawJsonValue when value prop changes + useEffect(() => { + if (!isJsonMode) { + setRawJsonValue(JSON.stringify(value ?? generateDefaultValue(schema), null, 2)); } - return true; - }; + }, [value, schema, isJsonMode]); const handleSwitchToFormMode = () => { if (isJsonMode) { - if (validateJsonBeforeSubmit()) { + // When switching to Form mode, ensure we have valid JSON + try { + const parsed = JSON.parse(rawJsonValue); + // Update the parent component's state with the parsed value + onChange(parsed); + // Switch to form mode setIsJsonMode(false); + } catch (err) { + setJsonError(err instanceof Error ? err.message : "Invalid JSON"); } } else { // Update raw JSON value when switching to JSON mode @@ -160,23 +159,50 @@ const DynamicJsonForm = ({ className="w-4 h-4" /> ); - case "object": - if (!propSchema.properties) return null; - return ( -
- {Object.entries(propSchema.properties).map(([key, prop]) => ( -
- - {renderFormFields( - prop, - (currentValue as JsonObject)?.[key], - [...path, key], - depth + 1, - )} -
- ))} -
- ); + case "object": { + // Handle case where we have a value but no schema properties + const objectValue = currentValue as JsonObject || {}; + + // If we have schema properties, use them to render fields + if (propSchema.properties) { + return ( +
+ {Object.entries(propSchema.properties).map(([key, prop]) => ( +
+ + {renderFormFields( + prop, + objectValue[key], + [...path, key], + depth + 1, + )} +
+ ))} +
+ ); + } + // If we have a value but no schema properties, render fields based on the value + else if (Object.keys(objectValue).length > 0) { + return ( +
+ {Object.entries(objectValue).map(([key, value]) => ( +
+ + + handleFieldChange([...path, key], e.target.value) + } + /> +
+ ))} +
+ ); + } + // If we have neither schema properties nor value, return null + return null; + } case "array": { const arrayValue = Array.isArray(currentValue) ? currentValue : []; if (!propSchema.items) return null; @@ -385,7 +411,22 @@ const DynamicJsonForm = ({ error={jsonError} /> ) : ( - renderFormFields(schema, value) + // If schema type is object but value is not an object or is empty, and we have actual JSON data, + // render a simple representation of the JSON data + schema.type === "object" && + (typeof value !== "object" || value === null || Object.keys(value).length === 0) && + rawJsonValue && + rawJsonValue !== "{}" ? ( +
+

Form view not available for this JSON structure. Using simplified view:

+
+              {rawJsonValue}
+            
+

Use JSON mode for full editing capabilities.

+
+ ) : ( + renderFormFields(schema, value) + ) )} ); From 0b105b29c1678c36a0a61f77559ccfe5280c741c Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 26 Feb 2025 19:55:01 -0700 Subject: [PATCH 03/98] Extract functions --- client/src/components/DynamicJsonForm.tsx | 134 +------------------ client/src/utils/jsonPathUtils.ts | 152 ++++++++++++++++++++++ client/src/utils/schemaUtils.ts | 72 ++++++++++ 3 files changed, 227 insertions(+), 131 deletions(-) create mode 100644 client/src/utils/jsonPathUtils.ts create mode 100644 client/src/utils/schemaUtils.ts diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index d76b151..4f986a8 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -3,6 +3,8 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import JsonEditor from "./JsonEditor"; +import { updateValueAtPath, JsonObject } from "@/utils/jsonPathUtils"; +import { generateDefaultValue, formatFieldLabel } from "@/utils/schemaUtils"; export type JsonValue = | string @@ -19,8 +21,6 @@ export type JsonSchemaType = { items?: JsonSchemaType; }; -type JsonObject = { [key: string]: JsonValue }; - interface DynamicJsonFormProps { schema: JsonSchemaType; value: JsonValue; @@ -28,12 +28,6 @@ interface DynamicJsonFormProps { maxDepth?: number; } -const formatFieldLabel = (key: string): string => { - return key - .replace(/([A-Z])/g, " $1") // Insert space before capital letters - .replace(/_/g, " ") // Replace underscores with spaces - .replace(/^\w/, (c) => c.toUpperCase()); // Capitalize first letter -}; const DynamicJsonForm = ({ schema, @@ -41,30 +35,6 @@ const DynamicJsonForm = ({ onChange, maxDepth = 3, }: DynamicJsonFormProps) => { - const generateDefaultValue = (propSchema: JsonSchemaType): JsonValue => { - switch (propSchema.type) { - case "string": - return ""; - case "number": - case "integer": - return 0; - case "boolean": - return false; - case "array": - return []; - case "object": { - const obj: JsonObject = {}; - if (propSchema.properties) { - Object.entries(propSchema.properties).forEach(([key, prop]) => { - obj[key] = generateDefaultValue(prop); - }); - } - return obj; - } - default: - return null; - } - }; const [isJsonMode, setIsJsonMode] = useState(false); const [jsonError, setJsonError] = useState(); @@ -272,106 +242,8 @@ const DynamicJsonForm = ({ return; } - const updateArray = ( - array: JsonValue[], - path: string[], - value: JsonValue, - ): JsonValue[] => { - const [index, ...restPath] = path; - const arrayIndex = Number(index); - - // Validate array index - if (isNaN(arrayIndex)) { - console.error(`Invalid array index: ${index}`); - return array; - } - - // Check array bounds - if (arrayIndex < 0) { - console.error(`Array index out of bounds: ${arrayIndex} < 0`); - return array; - } - - const newArray = [...array]; - - if (restPath.length === 0) { - newArray[arrayIndex] = value; - } else { - // Ensure index position exists - if (arrayIndex >= array.length) { - console.warn(`Extending array to index ${arrayIndex}`); - newArray.length = arrayIndex + 1; - newArray.fill(null, array.length, arrayIndex); - } - newArray[arrayIndex] = updateValue( - newArray[arrayIndex], - restPath, - value, - ); - } - return newArray; - }; - - const updateObject = ( - obj: JsonObject, - path: string[], - value: JsonValue, - ): JsonObject => { - const [key, ...restPath] = path; - - // Validate object key - if (typeof key !== "string") { - console.error(`Invalid object key: ${key}`); - return obj; - } - - const newObj = { ...obj }; - - if (restPath.length === 0) { - newObj[key] = value; - } else { - // Ensure key exists - if (!(key in newObj)) { - console.warn(`Creating new key in object: ${key}`); - newObj[key] = {}; - } - newObj[key] = updateValue(newObj[key], restPath, value); - } - return newObj; - }; - - const updateValue = ( - current: JsonValue, - path: string[], - value: JsonValue, - ): JsonValue => { - if (path.length === 0) return value; - - try { - if (!current) { - current = !isNaN(Number(path[0])) ? [] : {}; - } - - // Type checking - if (Array.isArray(current)) { - return updateArray(current, path, value); - } else if (typeof current === "object" && current !== null) { - return updateObject(current, path, value); - } else { - console.error( - `Cannot update path ${path.join(".")} in non-object/array value:`, - current, - ); - return current; - } - } catch (error) { - console.error(`Error updating value at path ${path.join(".")}:`, error); - return current; - } - }; - try { - const newValue = updateValue(value, path, fieldValue); + const newValue = updateValueAtPath(value, path, fieldValue); onChange(newValue); } catch (error) { console.error("Failed to update form value:", error); diff --git a/client/src/utils/jsonPathUtils.ts b/client/src/utils/jsonPathUtils.ts new file mode 100644 index 0000000..bbfe5ef --- /dev/null +++ b/client/src/utils/jsonPathUtils.ts @@ -0,0 +1,152 @@ +import { JsonValue } from "../components/DynamicJsonForm"; + +export type JsonObject = { [key: string]: JsonValue }; + +/** + * Updates a value at a specific path in a nested JSON structure + * @param obj The original JSON value + * @param path Array of keys/indices representing the path to the value + * @param value The new value to set + * @returns A new JSON value with the updated path + */ +export function updateValueAtPath( + obj: JsonValue, + path: string[], + value: JsonValue +): JsonValue { + if (path.length === 0) return value; + + // Initialize if null/undefined + if (obj === null || obj === undefined) { + obj = !isNaN(Number(path[0])) ? [] : {}; + } + + // Handle arrays + if (Array.isArray(obj)) { + return updateArray(obj, path, value); + } + // Handle objects + else if (typeof obj === "object" && obj !== null) { + return updateObject(obj as JsonObject, path, value); + } + // Cannot update primitives + else { + console.error( + `Cannot update path ${path.join(".")} in non-object/array value:`, + obj + ); + return obj; + } +} + +/** + * Updates an array at a specific path + */ +function updateArray( + array: JsonValue[], + path: string[], + value: JsonValue +): JsonValue[] { + const [index, ...restPath] = path; + const arrayIndex = Number(index); + + // Validate array index + if (isNaN(arrayIndex)) { + console.error(`Invalid array index: ${index}`); + return array; + } + + // Check array bounds + if (arrayIndex < 0) { + console.error(`Array index out of bounds: ${arrayIndex} < 0`); + return array; + } + + const newArray = [...array]; + + if (restPath.length === 0) { + newArray[arrayIndex] = value; + } else { + // Ensure index position exists + if (arrayIndex >= array.length) { + console.warn(`Extending array to index ${arrayIndex}`); + newArray.length = arrayIndex + 1; + newArray.fill(null, array.length, arrayIndex); + } + newArray[arrayIndex] = updateValueAtPath( + newArray[arrayIndex], + restPath, + value + ); + } + return newArray; +} + +/** + * Updates an object at a specific path + */ +function updateObject( + obj: JsonObject, + path: string[], + value: JsonValue +): JsonObject { + const [key, ...restPath] = path; + + // Validate object key + if (typeof key !== "string") { + console.error(`Invalid object key: ${key}`); + return obj; + } + + const newObj = { ...obj }; + + if (restPath.length === 0) { + newObj[key] = value; + } else { + // Ensure key exists + if (!(key in newObj)) { + console.warn(`Creating new key in object: ${key}`); + newObj[key] = {}; + } + newObj[key] = updateValueAtPath(newObj[key], restPath, value); + } + return newObj; +} + +/** + * Gets a value at a specific path in a nested JSON structure + * @param obj The JSON value to traverse + * @param path Array of keys/indices representing the path to the value + * @param defaultValue Value to return if path doesn't exist + * @returns The value at the path, or defaultValue if not found + */ +export function getValueAtPath( + obj: JsonValue, + path: string[], + defaultValue: JsonValue = null +): JsonValue { + if (path.length === 0) return obj; + + const [first, ...rest] = path; + + if (obj === null || obj === undefined) { + return defaultValue; + } + + if (Array.isArray(obj)) { + const index = Number(first); + if (isNaN(index) || index < 0 || index >= obj.length) { + return defaultValue; + } + return getValueAtPath(obj[index], rest, defaultValue); + } + + if (typeof obj === "object" && obj !== null) { + if (!(first in obj)) { + return defaultValue; + } + return getValueAtPath((obj as JsonObject)[first], rest, defaultValue); + } + + return defaultValue; +} diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts new file mode 100644 index 0000000..feee4de --- /dev/null +++ b/client/src/utils/schemaUtils.ts @@ -0,0 +1,72 @@ +import { JsonValue, JsonSchemaType } from "../components/DynamicJsonForm"; +import { JsonObject } from "./jsonPathUtils"; + +/** + * Generates a default value based on a JSON schema type + * @param schema The JSON schema definition + * @returns A default value matching the schema type + */ +export function generateDefaultValue(schema: JsonSchemaType): JsonValue { + switch (schema.type) { + case "string": + return ""; + case "number": + case "integer": + return 0; + case "boolean": + return false; + case "array": + return []; + case "object": { + const obj: JsonObject = {}; + if (schema.properties) { + Object.entries(schema.properties).forEach(([key, prop]) => { + obj[key] = generateDefaultValue(prop); + }); + } + return obj; + } + default: + return null; + } +} + +/** + * Formats a field key into a human-readable label + * @param key The field key to format + * @returns A formatted label string + */ +export function formatFieldLabel(key: string): string { + return key + .replace(/([A-Z])/g, " $1") // Insert space before capital letters + .replace(/_/g, " ") // Replace underscores with spaces + .replace(/^\w/, (c) => c.toUpperCase()); // Capitalize first letter +} + +/** + * Validates if a value conforms to a JSON schema + * @param value The value to validate + * @param schema The JSON schema to validate against + * @returns True if valid, false otherwise + */ +export function validateValueAgainstSchema( + value: JsonValue, + schema: JsonSchemaType +): boolean { + // Basic type validation + switch (schema.type) { + case "string": + return typeof value === "string"; + case "number": + case "integer": + return typeof value === "number"; + case "boolean": + return typeof value === "boolean"; + case "array": + return Array.isArray(value); + case "object": + return typeof value === "object" && value !== null && !Array.isArray(value); + default: + return true; + } +} From 07474796945d792c572932d1d6035d6b9fd9c8d7 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 06:40:01 -0700 Subject: [PATCH 04/98] Handle edge case and add tests for functions --- client/jest.config.cjs | 32 + client/package.json | 8 +- client/src/utils/__mocks__/DynamicJsonForm.ts | 18 + .../src/utils/__tests__/jsonPathUtils.test.ts | 169 + .../src/utils/__tests__/schemaUtils.test.ts | 135 + client/src/utils/jsonPathUtils.ts | 30 +- client/tsconfig.jest.json | 10 + package-lock.json | 3381 ++++++++++++++++- 8 files changed, 3744 insertions(+), 39 deletions(-) create mode 100644 client/jest.config.cjs create mode 100644 client/src/utils/__mocks__/DynamicJsonForm.ts create mode 100644 client/src/utils/__tests__/jsonPathUtils.test.ts create mode 100644 client/src/utils/__tests__/schemaUtils.test.ts create mode 100644 client/tsconfig.jest.json diff --git a/client/jest.config.cjs b/client/jest.config.cjs new file mode 100644 index 0000000..bf6a1fc --- /dev/null +++ b/client/jest.config.cjs @@ -0,0 +1,32 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + '^../components/DynamicJsonForm$': '/src/utils/__mocks__/DynamicJsonForm.ts', + '^../../components/DynamicJsonForm$': '/src/utils/__mocks__/DynamicJsonForm.ts' + }, + transform: { + '^.+\\.tsx?$': ['ts-jest', { + useESM: true, + jsx: 'react-jsx', + tsconfig: 'tsconfig.jest.json' + }] + }, + extensionsToTreatAsEsm: ['.ts', '.tsx'], + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', + // Exclude directories and files that don't need to be tested + testPathIgnorePatterns: [ + '/node_modules/', + '/dist/', + '/bin/', + '\\.config\\.(js|ts|cjs|mjs)$' + ], + // Exclude the same patterns from coverage reports + coveragePathIgnorePatterns: [ + '/node_modules/', + '/dist/', + '/bin/', + '\\.config\\.(js|ts|cjs|mjs)$' + ] +}; diff --git a/client/package.json b/client/package.json index 4e95b83..fc125ba 100644 --- a/client/package.json +++ b/client/package.json @@ -18,7 +18,9 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "test": "jest --config jest.config.cjs", + "test:watch": "jest --config jest.config.cjs --watch" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.4.1", @@ -45,6 +47,7 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", @@ -55,8 +58,11 @@ "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.12", "globals": "^15.9.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "postcss": "^8.4.47", "tailwindcss": "^3.4.13", + "ts-jest": "^29.2.6", "typescript": "^5.5.3", "typescript-eslint": "^8.7.0", "vite": "^5.4.8" diff --git a/client/src/utils/__mocks__/DynamicJsonForm.ts b/client/src/utils/__mocks__/DynamicJsonForm.ts new file mode 100644 index 0000000..83a98e8 --- /dev/null +++ b/client/src/utils/__mocks__/DynamicJsonForm.ts @@ -0,0 +1,18 @@ +// Mock for DynamicJsonForm that only exports the types needed for tests +export type JsonValue = + | string + | number + | boolean + | null + | JsonValue[] + | { [key: string]: JsonValue }; + +export type JsonSchemaType = { + type: "string" | "number" | "integer" | "boolean" | "array" | "object"; + description?: string; + properties?: Record; + items?: JsonSchemaType; +}; + +// Default export is not used in the tests +export default {}; diff --git a/client/src/utils/__tests__/jsonPathUtils.test.ts b/client/src/utils/__tests__/jsonPathUtils.test.ts new file mode 100644 index 0000000..bca9661 --- /dev/null +++ b/client/src/utils/__tests__/jsonPathUtils.test.ts @@ -0,0 +1,169 @@ +import { updateValueAtPath, getValueAtPath } from '../jsonPathUtils'; +import { JsonValue } from '../../components/DynamicJsonForm'; + +describe('updateValueAtPath', () => { + // Basic functionality tests + test('returns the new value when path is empty', () => { + expect(updateValueAtPath({ foo: 'bar' }, [], 'newValue')).toBe('newValue'); + }); + + test('initializes an empty object when input is null/undefined and path starts with a string', () => { + expect(updateValueAtPath(null as any, ['foo'], 'bar')).toEqual({ foo: 'bar' }); + expect(updateValueAtPath(undefined as any, ['foo'], 'bar')).toEqual({ foo: 'bar' }); + }); + + test('initializes an empty array when input is null/undefined and path starts with a number', () => { + expect(updateValueAtPath(null as any, ['0'], 'bar')).toEqual(['bar']); + expect(updateValueAtPath(undefined as any, ['0'], 'bar')).toEqual(['bar']); + }); + + // Object update tests + test('updates a simple object property', () => { + const obj = { name: 'John', age: 30 }; + expect(updateValueAtPath(obj, ['age'], 31)).toEqual({ name: 'John', age: 31 }); + }); + + test('updates a nested object property', () => { + const obj = { user: { name: 'John', address: { city: 'New York' } } }; + expect(updateValueAtPath(obj, ['user', 'address', 'city'], 'Boston')).toEqual( + { user: { name: 'John', address: { city: 'Boston' } } } + ); + }); + + test('creates missing object properties', () => { + const obj = { user: { name: 'John' } }; + expect(updateValueAtPath(obj, ['user', 'address', 'city'], 'Boston')).toEqual( + { user: { name: 'John', address: { city: 'Boston' } } } + ); + }); + + // Array update tests + test('updates an array item', () => { + const arr = [1, 2, 3, 4]; + expect(updateValueAtPath(arr, ['2'], 5)).toEqual([1, 2, 5, 4]); + }); + + test('extends an array when index is out of bounds', () => { + const arr = [1, 2, 3]; + const result = updateValueAtPath(arr, ['5'], 'new') as JsonValue[]; + + // Check overall array structure + expect(result).toEqual([1, 2, 3, null, null, 'new']); + + // Explicitly verify that indices 3 and 4 contain null, not undefined + expect(result[3]).toBe(null); + expect(result[4]).toBe(null); + + // Verify these aren't "holes" in the array (important distinction) + expect(3 in result).toBe(true); + expect(4 in result).toBe(true); + + // Verify the array has the correct length + expect(result.length).toBe(6); + + // Verify the array doesn't have holes by checking every index exists + expect(result.every((_, index: number) => index in result)).toBe(true); + }); + + test('updates a nested array item', () => { + const obj = { users: [{ name: 'John' }, { name: 'Jane' }] }; + expect(updateValueAtPath(obj, ['users', '1', 'name'], 'Janet')).toEqual( + { users: [{ name: 'John' }, { name: 'Janet' }] } + ); + }); + + // Error handling tests + test('returns original value when trying to update a primitive with a path', () => { + const spy = jest.spyOn(console, 'error').mockImplementation(); + const result = updateValueAtPath('string', ['foo'], 'bar'); + expect(result).toBe('string'); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + test('returns original array when index is invalid', () => { + const spy = jest.spyOn(console, 'error').mockImplementation(); + const arr = [1, 2, 3]; + expect(updateValueAtPath(arr, ['invalid'], 4)).toEqual(arr); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + test('returns original array when index is negative', () => { + const spy = jest.spyOn(console, 'error').mockImplementation(); + const arr = [1, 2, 3]; + expect(updateValueAtPath(arr, ['-1'], 4)).toEqual(arr); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + test('handles sparse arrays correctly by filling holes with null', () => { + // Create a sparse array by deleting an element + const sparseArr = [1, 2, 3]; + delete sparseArr[1]; // Now sparseArr is [1, <1 empty item>, 3] + + // Update a value beyond the array length + const result = updateValueAtPath(sparseArr, ['5'], 'new') as JsonValue[]; + + // Check overall array structure + expect(result).toEqual([1, null, 3, null, null, 'new']); + + // Explicitly verify that index 1 (the hole) contains null, not undefined + expect(result[1]).toBe(null); + + // Verify this isn't a hole in the array + expect(1 in result).toBe(true); + + // Verify all indices contain null (not undefined) + expect(result[1]).not.toBe(undefined); + expect(result[3]).toBe(null); + expect(result[4]).toBe(null); + }); +}); + +describe('getValueAtPath', () => { + test('returns the original value when path is empty', () => { + const obj = { foo: 'bar' }; + expect(getValueAtPath(obj, [])).toBe(obj); + }); + + test('returns the value at a simple path', () => { + const obj = { name: 'John', age: 30 }; + expect(getValueAtPath(obj, ['name'])).toBe('John'); + }); + + test('returns the value at a nested path', () => { + const obj = { user: { name: 'John', address: { city: 'New York' } } }; + expect(getValueAtPath(obj, ['user', 'address', 'city'])).toBe('New York'); + }); + + test('returns default value when path does not exist', () => { + const obj = { user: { name: 'John' } }; + expect(getValueAtPath(obj, ['user', 'address', 'city'], 'Unknown')).toBe('Unknown'); + }); + + test('returns default value when input is null/undefined', () => { + expect(getValueAtPath(null as any, ['foo'], 'default')).toBe('default'); + expect(getValueAtPath(undefined as any, ['foo'], 'default')).toBe('default'); + }); + + test('handles array indices correctly', () => { + const arr = ['a', 'b', 'c']; + expect(getValueAtPath(arr, ['1'])).toBe('b'); + }); + + test('returns default value for out of bounds array indices', () => { + const arr = ['a', 'b', 'c']; + expect(getValueAtPath(arr, ['5'], 'default')).toBe('default'); + }); + + test('returns default value for invalid array indices', () => { + const arr = ['a', 'b', 'c']; + expect(getValueAtPath(arr, ['invalid'], 'default')).toBe('default'); + }); + + test('navigates through mixed object and array paths', () => { + const obj = { users: [{ name: 'John' }, { name: 'Jane' }] }; + expect(getValueAtPath(obj, ['users', '1', 'name'])).toBe('Jane'); + }); +}); diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts new file mode 100644 index 0000000..beb267e --- /dev/null +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -0,0 +1,135 @@ +import { generateDefaultValue, formatFieldLabel, validateValueAgainstSchema } from '../schemaUtils'; +import { JsonSchemaType } from '../../components/DynamicJsonForm'; + +describe('generateDefaultValue', () => { + test('generates default string', () => { + expect(generateDefaultValue({ type: 'string' })).toBe(''); + }); + + test('generates default number', () => { + expect(generateDefaultValue({ type: 'number' })).toBe(0); + }); + + test('generates default integer', () => { + expect(generateDefaultValue({ type: 'integer' })).toBe(0); + }); + + test('generates default boolean', () => { + expect(generateDefaultValue({ type: 'boolean' })).toBe(false); + }); + + test('generates default array', () => { + expect(generateDefaultValue({ type: 'array' })).toEqual([]); + }); + + test('generates default empty object', () => { + expect(generateDefaultValue({ type: 'object' })).toEqual({}); + }); + + test('generates default null for unknown types', () => { + expect(generateDefaultValue({ type: 'unknown' as any })).toBe(null); + }); + + test('generates object with properties', () => { + const schema: JsonSchemaType = { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + isActive: { type: 'boolean' } + } + }; + expect(generateDefaultValue(schema)).toEqual({ + name: '', + age: 0, + isActive: false + }); + }); + + test('handles nested objects', () => { + const schema: JsonSchemaType = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + address: { + type: 'object', + properties: { + city: { type: 'string' } + } + } + } + } + } + }; + expect(generateDefaultValue(schema)).toEqual({ + user: { + name: '', + address: { + city: '' + } + } + }); + }); +}); + +describe('formatFieldLabel', () => { + test('formats camelCase', () => { + expect(formatFieldLabel('firstName')).toBe('First Name'); + }); + + test('formats snake_case', () => { + expect(formatFieldLabel('first_name')).toBe('First name'); + }); + + test('formats single word', () => { + expect(formatFieldLabel('name')).toBe('Name'); + }); + + test('formats mixed case with underscores', () => { + expect(formatFieldLabel('user_firstName')).toBe('User first Name'); + }); + + test('handles empty string', () => { + expect(formatFieldLabel('')).toBe(''); + }); +}); + +describe('validateValueAgainstSchema', () => { + test('validates string type', () => { + expect(validateValueAgainstSchema('test', { type: 'string' })).toBe(true); + expect(validateValueAgainstSchema(123, { type: 'string' })).toBe(false); + }); + + test('validates number type', () => { + expect(validateValueAgainstSchema(123, { type: 'number' })).toBe(true); + expect(validateValueAgainstSchema('test', { type: 'number' })).toBe(false); + }); + + test('validates integer type', () => { + expect(validateValueAgainstSchema(123, { type: 'integer' })).toBe(true); + expect(validateValueAgainstSchema('test', { type: 'integer' })).toBe(false); + }); + + test('validates boolean type', () => { + expect(validateValueAgainstSchema(true, { type: 'boolean' })).toBe(true); + expect(validateValueAgainstSchema('test', { type: 'boolean' })).toBe(false); + }); + + test('validates array type', () => { + expect(validateValueAgainstSchema([], { type: 'array' })).toBe(true); + expect(validateValueAgainstSchema({}, { type: 'array' })).toBe(false); + }); + + test('validates object type', () => { + expect(validateValueAgainstSchema({}, { type: 'object' })).toBe(true); + expect(validateValueAgainstSchema([], { type: 'object' })).toBe(false); + expect(validateValueAgainstSchema('test', { type: 'object' })).toBe(false); + }); + + test('returns true for unknown types', () => { + expect(validateValueAgainstSchema('anything', { type: 'unknown' as any })).toBe(true); + }); +}); diff --git a/client/src/utils/jsonPathUtils.ts b/client/src/utils/jsonPathUtils.ts index bbfe5ef..ef5a18e 100644 --- a/client/src/utils/jsonPathUtils.ts +++ b/client/src/utils/jsonPathUtils.ts @@ -62,22 +62,27 @@ function updateArray( return array; } - const newArray = [...array]; + // Create a dense copy of the array, filling holes with null + let newArray: JsonValue[] = []; + for (let i = 0; i < array.length; i++) { + newArray[i] = i in array ? array[i] : null; + } + + // If the desired index is out of bounds, build a new array explicitly filled with nulls + if (arrayIndex >= newArray.length) { + console.warn(`Extending array to index ${arrayIndex}`); + const extendedArray: JsonValue[] = new Array(arrayIndex).fill(null); + // Copy over the existing elements (now guaranteed to be dense) + for (let i = 0; i < newArray.length; i++) { + extendedArray[i] = newArray[i]; + } + newArray = extendedArray; + } if (restPath.length === 0) { newArray[arrayIndex] = value; } else { - // Ensure index position exists - if (arrayIndex >= array.length) { - console.warn(`Extending array to index ${arrayIndex}`); - newArray.length = arrayIndex + 1; - newArray.fill(null, array.length, arrayIndex); - } - newArray[arrayIndex] = updateValueAtPath( - newArray[arrayIndex], - restPath, - value - ); + newArray[arrayIndex] = updateValueAtPath(newArray[arrayIndex], restPath, value); } return newArray; } @@ -105,7 +110,6 @@ function updateObject( } else { // Ensure key exists if (!(key in newObj)) { - console.warn(`Creating new key in object: ${key}`); newObj[key] = {}; } newObj[key] = updateValueAtPath(newObj[key], restPath, value); diff --git a/client/tsconfig.jest.json b/client/tsconfig.jest.json new file mode 100644 index 0000000..137fc0a --- /dev/null +++ b/client/tsconfig.jest.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.app.json", + "compilerOptions": { + "jsx": "react-jsx", + "esModuleInterop": true, + "module": "ESNext", + "moduleResolution": "node" + }, + "include": ["src"] +} diff --git a/package-lock.json b/package-lock.json index eb11de6..53cb556 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", @@ -71,8 +72,11 @@ "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.12", "globals": "^15.9.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "postcss": "^8.4.47", "tailwindcss": "^3.4.13", + "ts-jest": "^29.2.6", "typescript": "^5.5.3", "typescript-eslint": "^8.7.0", "vite": "^5.4.8" @@ -296,6 +300,245 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", @@ -386,6 +629,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1153,6 +1403,437 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -2289,6 +2970,43 @@ "win32" ] }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -2422,6 +3140,16 @@ "@types/send": "*" } }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -2429,6 +3157,56 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2540,6 +3318,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.13", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", @@ -2550,6 +3342,23 @@ "@types/node": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", @@ -2807,6 +3616,14 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2832,6 +3649,17 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -2854,6 +3682,19 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2871,6 +3712,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2945,6 +3802,20 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -2983,6 +3854,122 @@ "postcss": "^8.1.0" } }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3110,6 +4097,36 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3138,6 +4155,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3148,6 +4178,16 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -3206,6 +4246,16 @@ "node": ">=8" } }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -3242,6 +4292,29 @@ "node": ">= 6" } }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -3277,6 +4350,24 @@ "node": ">=6" } }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3295,6 +4386,19 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -3391,6 +4495,28 @@ "node": ">= 0.10" } }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -3423,6 +4549,33 @@ "node": ">=4" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3430,6 +4583,21 @@ "devOptional": true, "license": "MIT" }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -3447,6 +4615,28 @@ } } }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3454,6 +4644,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -3471,6 +4671,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3490,6 +4700,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -3511,12 +4731,50 @@ "node": ">=0.3.1" } }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3529,6 +4787,22 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.65", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.65.tgz", @@ -3536,6 +4810,19 @@ "dev": true, "license": "ISC" }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -3551,14 +4838,34 @@ "node": ">= 0.8" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3572,6 +4879,34 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", @@ -3640,6 +4975,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/eslint": { "version": "9.15.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", @@ -3771,6 +5128,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -3835,6 +5206,63 @@ "node": ">=18.0.0" } }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -3954,6 +5382,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3967,6 +5405,39 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4066,6 +5537,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4098,6 +5585,13 @@ "node": ">= 0.6" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4141,16 +5635,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4168,6 +5667,42 @@ "node": ">=6" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-tsconfig": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", @@ -4251,17 +5786,24 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -4290,10 +5832,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4302,11 +5844,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -4326,6 +5872,26 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -4342,6 +5908,45 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -4381,6 +5986,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4391,6 +6016,18 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -4415,6 +6052,13 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4460,6 +6104,16 @@ "node": ">=8" } }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -4481,12 +6135,129 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -4502,6 +6273,677 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/glob": { + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jiti": { "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", @@ -4530,6 +6972,52 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -4550,6 +7038,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4587,6 +7082,26 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4638,6 +7153,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4676,12 +7198,60 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "license": "ISC" }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -4700,6 +7270,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4764,6 +7341,16 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4836,6 +7423,13 @@ "node": ">= 0.6" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -4862,6 +7456,26 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4904,6 +7518,32 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4954,6 +7594,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4973,6 +7623,38 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4992,6 +7674,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", @@ -5086,6 +7778,75 @@ "node": ">=16.20.0" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/postcss": { "version": "8.4.49", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", @@ -5267,6 +8028,34 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prismjs": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", @@ -5276,6 +8065,20 @@ "node": ">=6" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -5289,6 +8092,19 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5299,6 +8115,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -5314,6 +8147,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5395,6 +8235,13 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -5528,6 +8375,13 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -5545,6 +8399,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -5565,6 +8442,16 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -5671,6 +8558,19 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -5908,6 +8808,33 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5917,6 +8844,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/spawn-rx": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.1.2.tgz", @@ -5927,6 +8865,36 @@ "rxjs": "^7.8.1" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -5936,6 +8904,20 @@ "node": ">= 0.8" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -5990,6 +8972,26 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6052,6 +9054,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz", @@ -6108,6 +9117,43 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6129,6 +9175,13 @@ "node": ">=0.8" } }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6150,6 +9203,35 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -6178,6 +9260,68 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/ts-jest": { + "version": "29.2.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.6.tgz", + "integrity": "sha512-yTNZVZqc8lSixm+QGVFcPe6+yj7+TWZwIesuOWvfcn4B9bz5x4NDzVCQQjOs7Hfouu36aEqfEbo9Qpo+gq8dDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.1", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -6266,6 +9410,29 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -6325,6 +9492,16 @@ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "license": "MIT" }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -6375,6 +9552,17 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", @@ -6439,6 +9627,21 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "license": "MIT" }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -6938,6 +10141,89 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6998,6 +10284,34 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -7019,6 +10333,23 @@ } } }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", From d4a64fb5d8938472f3d5395f758e5baf4388e759 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 06:52:19 -0700 Subject: [PATCH 05/98] Add client tests to workflow --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 572e79e..d63f0e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,12 @@ jobs: # Working around https://github.com/npm/cli/issues/4828 # - run: npm ci - run: npm install --no-package-lock + + - name: Run client tests + run: | + cd inspector/client + npm test + - run: npm run build publish: From 90ce62804010d445ff8b5bb9c54829dd781fd244 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 06:54:36 -0700 Subject: [PATCH 06/98] Test workflow in my branch --- .github/workflows/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d63f0e4..c3fefbe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,8 +1,6 @@ on: + # TODO: Revert to only running on main branch pushes after testing push: - branches: - - main - pull_request: release: types: [published] From 426fb87640b8493c68533e56ba8ecbfaaf3eaed2 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:15:31 -0700 Subject: [PATCH 07/98] Remove comment ans trigger workflow run --- client/src/components/DynamicJsonForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 4f986a8..784cfc0 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -247,7 +247,6 @@ const DynamicJsonForm = ({ onChange(newValue); } catch (error) { console.error("Failed to update form value:", error); - // Keep the original value unchanged onChange(value); } }; From 238c22830b4fc82a6b9e3704f374226156466ef6 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:21:04 -0700 Subject: [PATCH 08/98] Fix formatting --- .github/workflows/main.yml | 4 +- client/jest.config.cjs | 47 +++-- client/src/components/DynamicJsonForm.tsx | 63 +++--- client/src/components/JsonEditor.tsx | 30 +-- .../src/utils/__tests__/jsonPathUtils.test.ts | 179 ++++++++++-------- .../src/utils/__tests__/schemaUtils.test.ts | 152 ++++++++------- client/src/utils/jsonPathUtils.ts | 46 +++-- client/src/utils/schemaUtils.ts | 8 +- 8 files changed, 283 insertions(+), 246 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c3fefbe..d1160c9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,12 +23,12 @@ jobs: # Working around https://github.com/npm/cli/issues/4828 # - run: npm ci - run: npm install --no-package-lock - + - name: Run client tests run: | cd inspector/client npm test - + - run: npm run build publish: diff --git a/client/jest.config.cjs b/client/jest.config.cjs index bf6a1fc..3830e79 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -1,32 +1,37 @@ module.exports = { - preset: 'ts-jest', - testEnvironment: 'jsdom', + preset: "ts-jest", + testEnvironment: "jsdom", moduleNameMapper: { - '^@/(.*)$': '/src/$1', - '^../components/DynamicJsonForm$': '/src/utils/__mocks__/DynamicJsonForm.ts', - '^../../components/DynamicJsonForm$': '/src/utils/__mocks__/DynamicJsonForm.ts' + "^@/(.*)$": "/src/$1", + "^../components/DynamicJsonForm$": + "/src/utils/__mocks__/DynamicJsonForm.ts", + "^../../components/DynamicJsonForm$": + "/src/utils/__mocks__/DynamicJsonForm.ts", }, transform: { - '^.+\\.tsx?$': ['ts-jest', { - useESM: true, - jsx: 'react-jsx', - tsconfig: 'tsconfig.jest.json' - }] + "^.+\\.tsx?$": [ + "ts-jest", + { + useESM: true, + jsx: "react-jsx", + tsconfig: "tsconfig.jest.json", + }, + ], }, - extensionsToTreatAsEsm: ['.ts', '.tsx'], - testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', + extensionsToTreatAsEsm: [".ts", ".tsx"], + testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", // Exclude directories and files that don't need to be tested testPathIgnorePatterns: [ - '/node_modules/', - '/dist/', - '/bin/', - '\\.config\\.(js|ts|cjs|mjs)$' + "/node_modules/", + "/dist/", + "/bin/", + "\\.config\\.(js|ts|cjs|mjs)$", ], // Exclude the same patterns from coverage reports coveragePathIgnorePatterns: [ - '/node_modules/', - '/dist/', - '/bin/', - '\\.config\\.(js|ts|cjs|mjs)$' - ] + "/node_modules/", + "/dist/", + "/bin/", + "\\.config\\.(js|ts|cjs|mjs)$", + ], }; diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 784cfc0..9e865f9 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -28,25 +28,25 @@ interface DynamicJsonFormProps { maxDepth?: number; } - const DynamicJsonForm = ({ schema, value, onChange, maxDepth = 3, }: DynamicJsonFormProps) => { - const [isJsonMode, setIsJsonMode] = useState(false); const [jsonError, setJsonError] = useState(); // Add state for storing raw JSON value const [rawJsonValue, setRawJsonValue] = useState( - JSON.stringify(value ?? generateDefaultValue(schema), null, 2) + JSON.stringify(value ?? generateDefaultValue(schema), null, 2), ); // Update rawJsonValue when value prop changes useEffect(() => { if (!isJsonMode) { - setRawJsonValue(JSON.stringify(value ?? generateDefaultValue(schema), null, 2)); + setRawJsonValue( + JSON.stringify(value ?? generateDefaultValue(schema), null, 2), + ); } }, [value, schema, isJsonMode]); @@ -64,7 +64,9 @@ const DynamicJsonForm = ({ } } else { // Update raw JSON value when switching to JSON mode - setRawJsonValue(JSON.stringify(value ?? generateDefaultValue(schema), null, 2)); + setRawJsonValue( + JSON.stringify(value ?? generateDefaultValue(schema), null, 2), + ); setIsJsonMode(true); } }; @@ -131,8 +133,8 @@ const DynamicJsonForm = ({ ); case "object": { // Handle case where we have a value but no schema properties - const objectValue = currentValue as JsonObject || {}; - + const objectValue = (currentValue as JsonObject) || {}; + // If we have schema properties, use them to render fields if (propSchema.properties) { return ( @@ -150,7 +152,7 @@ const DynamicJsonForm = ({ ))} ); - } + } // If we have a value but no schema properties, render fields based on the value else if (Object.keys(objectValue).length > 0) { return ( @@ -161,7 +163,7 @@ const DynamicJsonForm = ({ + onChange={(e) => handleFieldChange([...path, key], e.target.value) } /> @@ -254,11 +256,7 @@ const DynamicJsonForm = ({ return (
-
@@ -281,23 +279,28 @@ const DynamicJsonForm = ({ }} error={jsonError} /> - ) : ( - // If schema type is object but value is not an object or is empty, and we have actual JSON data, - // render a simple representation of the JSON data - schema.type === "object" && - (typeof value !== "object" || value === null || Object.keys(value).length === 0) && - rawJsonValue && + ) : // If schema type is object but value is not an object or is empty, and we have actual JSON data, + // render a simple representation of the JSON data + schema.type === "object" && + (typeof value !== "object" || + value === null || + Object.keys(value).length === 0) && + rawJsonValue && rawJsonValue !== "{}" ? ( -
-

Form view not available for this JSON structure. Using simplified view:

-
-              {rawJsonValue}
-            
-

Use JSON mode for full editing capabilities.

-
- ) : ( - renderFormFields(schema, value) - ) +
+

+ Form view not available for this JSON structure. Using simplified + view: +

+
+            {rawJsonValue}
+          
+

+ Use JSON mode for full editing capabilities. +

+
+ ) : ( + renderFormFields(schema, value) )}
); diff --git a/client/src/components/JsonEditor.tsx b/client/src/components/JsonEditor.tsx index 9109088..87a6367 100644 --- a/client/src/components/JsonEditor.tsx +++ b/client/src/components/JsonEditor.tsx @@ -11,10 +11,16 @@ interface JsonEditorProps { error?: string; } -const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps) => { +const JsonEditor = ({ + value, + onChange, + error: externalError, +}: JsonEditorProps) => { const [editorContent, setEditorContent] = useState(value); - const [internalError, setInternalError] = useState(undefined); - + const [internalError, setInternalError] = useState( + undefined, + ); + useEffect(() => { setEditorContent(value); }, [value]); @@ -22,8 +28,8 @@ const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps) const formatJson = (json: string): string => { try { // Handle empty arrays and objects specifically - if (json.trim() === '[]') return '[]'; - if (json.trim() === '{}') return '{}'; + if (json.trim() === "[]") return "[]"; + if (json.trim() === "{}") return "{}"; return JSON.stringify(JSON.parse(json), null, 2); } catch { return json; @@ -52,17 +58,15 @@ const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps) return (
-
- {displayError &&

{displayError}

} + {displayError && ( +

{displayError}

+ )}
); }; diff --git a/client/src/utils/__tests__/jsonPathUtils.test.ts b/client/src/utils/__tests__/jsonPathUtils.test.ts index bca9661..b6ef7e9 100644 --- a/client/src/utils/__tests__/jsonPathUtils.test.ts +++ b/client/src/utils/__tests__/jsonPathUtils.test.ts @@ -1,119 +1,126 @@ -import { updateValueAtPath, getValueAtPath } from '../jsonPathUtils'; -import { JsonValue } from '../../components/DynamicJsonForm'; +import { updateValueAtPath, getValueAtPath } from "../jsonPathUtils"; +import { JsonValue } from "../../components/DynamicJsonForm"; -describe('updateValueAtPath', () => { +describe("updateValueAtPath", () => { // Basic functionality tests - test('returns the new value when path is empty', () => { - expect(updateValueAtPath({ foo: 'bar' }, [], 'newValue')).toBe('newValue'); + test("returns the new value when path is empty", () => { + expect(updateValueAtPath({ foo: "bar" }, [], "newValue")).toBe("newValue"); }); - test('initializes an empty object when input is null/undefined and path starts with a string', () => { - expect(updateValueAtPath(null as any, ['foo'], 'bar')).toEqual({ foo: 'bar' }); - expect(updateValueAtPath(undefined as any, ['foo'], 'bar')).toEqual({ foo: 'bar' }); + test("initializes an empty object when input is null/undefined and path starts with a string", () => { + expect(updateValueAtPath(null as any, ["foo"], "bar")).toEqual({ + foo: "bar", + }); + expect(updateValueAtPath(undefined as any, ["foo"], "bar")).toEqual({ + foo: "bar", + }); }); - test('initializes an empty array when input is null/undefined and path starts with a number', () => { - expect(updateValueAtPath(null as any, ['0'], 'bar')).toEqual(['bar']); - expect(updateValueAtPath(undefined as any, ['0'], 'bar')).toEqual(['bar']); + test("initializes an empty array when input is null/undefined and path starts with a number", () => { + expect(updateValueAtPath(null as any, ["0"], "bar")).toEqual(["bar"]); + expect(updateValueAtPath(undefined as any, ["0"], "bar")).toEqual(["bar"]); }); // Object update tests - test('updates a simple object property', () => { - const obj = { name: 'John', age: 30 }; - expect(updateValueAtPath(obj, ['age'], 31)).toEqual({ name: 'John', age: 31 }); + test("updates a simple object property", () => { + const obj = { name: "John", age: 30 }; + expect(updateValueAtPath(obj, ["age"], 31)).toEqual({ + name: "John", + age: 31, + }); }); - test('updates a nested object property', () => { - const obj = { user: { name: 'John', address: { city: 'New York' } } }; - expect(updateValueAtPath(obj, ['user', 'address', 'city'], 'Boston')).toEqual( - { user: { name: 'John', address: { city: 'Boston' } } } - ); + test("updates a nested object property", () => { + const obj = { user: { name: "John", address: { city: "New York" } } }; + expect( + updateValueAtPath(obj, ["user", "address", "city"], "Boston"), + ).toEqual({ user: { name: "John", address: { city: "Boston" } } }); }); - test('creates missing object properties', () => { - const obj = { user: { name: 'John' } }; - expect(updateValueAtPath(obj, ['user', 'address', 'city'], 'Boston')).toEqual( - { user: { name: 'John', address: { city: 'Boston' } } } - ); + test("creates missing object properties", () => { + const obj = { user: { name: "John" } }; + expect( + updateValueAtPath(obj, ["user", "address", "city"], "Boston"), + ).toEqual({ user: { name: "John", address: { city: "Boston" } } }); }); // Array update tests - test('updates an array item', () => { + test("updates an array item", () => { const arr = [1, 2, 3, 4]; - expect(updateValueAtPath(arr, ['2'], 5)).toEqual([1, 2, 5, 4]); + expect(updateValueAtPath(arr, ["2"], 5)).toEqual([1, 2, 5, 4]); }); - test('extends an array when index is out of bounds', () => { + test("extends an array when index is out of bounds", () => { const arr = [1, 2, 3]; - const result = updateValueAtPath(arr, ['5'], 'new') as JsonValue[]; - + const result = updateValueAtPath(arr, ["5"], "new") as JsonValue[]; + // Check overall array structure - expect(result).toEqual([1, 2, 3, null, null, 'new']); - + expect(result).toEqual([1, 2, 3, null, null, "new"]); + // Explicitly verify that indices 3 and 4 contain null, not undefined expect(result[3]).toBe(null); expect(result[4]).toBe(null); - + // Verify these aren't "holes" in the array (important distinction) expect(3 in result).toBe(true); expect(4 in result).toBe(true); - + // Verify the array has the correct length expect(result.length).toBe(6); - + // Verify the array doesn't have holes by checking every index exists expect(result.every((_, index: number) => index in result)).toBe(true); }); - test('updates a nested array item', () => { - const obj = { users: [{ name: 'John' }, { name: 'Jane' }] }; - expect(updateValueAtPath(obj, ['users', '1', 'name'], 'Janet')).toEqual( - { users: [{ name: 'John' }, { name: 'Janet' }] } - ); + test("updates a nested array item", () => { + const obj = { users: [{ name: "John" }, { name: "Jane" }] }; + expect(updateValueAtPath(obj, ["users", "1", "name"], "Janet")).toEqual({ + users: [{ name: "John" }, { name: "Janet" }], + }); }); // Error handling tests - test('returns original value when trying to update a primitive with a path', () => { - const spy = jest.spyOn(console, 'error').mockImplementation(); - const result = updateValueAtPath('string', ['foo'], 'bar'); - expect(result).toBe('string'); + test("returns original value when trying to update a primitive with a path", () => { + const spy = jest.spyOn(console, "error").mockImplementation(); + const result = updateValueAtPath("string", ["foo"], "bar"); + expect(result).toBe("string"); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); - test('returns original array when index is invalid', () => { - const spy = jest.spyOn(console, 'error').mockImplementation(); + test("returns original array when index is invalid", () => { + const spy = jest.spyOn(console, "error").mockImplementation(); const arr = [1, 2, 3]; - expect(updateValueAtPath(arr, ['invalid'], 4)).toEqual(arr); + expect(updateValueAtPath(arr, ["invalid"], 4)).toEqual(arr); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); - test('returns original array when index is negative', () => { - const spy = jest.spyOn(console, 'error').mockImplementation(); + test("returns original array when index is negative", () => { + const spy = jest.spyOn(console, "error").mockImplementation(); const arr = [1, 2, 3]; - expect(updateValueAtPath(arr, ['-1'], 4)).toEqual(arr); + expect(updateValueAtPath(arr, ["-1"], 4)).toEqual(arr); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); - test('handles sparse arrays correctly by filling holes with null', () => { + test("handles sparse arrays correctly by filling holes with null", () => { // Create a sparse array by deleting an element const sparseArr = [1, 2, 3]; delete sparseArr[1]; // Now sparseArr is [1, <1 empty item>, 3] - + // Update a value beyond the array length - const result = updateValueAtPath(sparseArr, ['5'], 'new') as JsonValue[]; - + const result = updateValueAtPath(sparseArr, ["5"], "new") as JsonValue[]; + // Check overall array structure - expect(result).toEqual([1, null, 3, null, null, 'new']); - + expect(result).toEqual([1, null, 3, null, null, "new"]); + // Explicitly verify that index 1 (the hole) contains null, not undefined expect(result[1]).toBe(null); - + // Verify this isn't a hole in the array expect(1 in result).toBe(true); - + // Verify all indices contain null (not undefined) expect(result[1]).not.toBe(undefined); expect(result[3]).toBe(null); @@ -121,49 +128,53 @@ describe('updateValueAtPath', () => { }); }); -describe('getValueAtPath', () => { - test('returns the original value when path is empty', () => { - const obj = { foo: 'bar' }; +describe("getValueAtPath", () => { + test("returns the original value when path is empty", () => { + const obj = { foo: "bar" }; expect(getValueAtPath(obj, [])).toBe(obj); }); - test('returns the value at a simple path', () => { - const obj = { name: 'John', age: 30 }; - expect(getValueAtPath(obj, ['name'])).toBe('John'); + test("returns the value at a simple path", () => { + const obj = { name: "John", age: 30 }; + expect(getValueAtPath(obj, ["name"])).toBe("John"); }); - test('returns the value at a nested path', () => { - const obj = { user: { name: 'John', address: { city: 'New York' } } }; - expect(getValueAtPath(obj, ['user', 'address', 'city'])).toBe('New York'); + test("returns the value at a nested path", () => { + const obj = { user: { name: "John", address: { city: "New York" } } }; + expect(getValueAtPath(obj, ["user", "address", "city"])).toBe("New York"); }); - test('returns default value when path does not exist', () => { - const obj = { user: { name: 'John' } }; - expect(getValueAtPath(obj, ['user', 'address', 'city'], 'Unknown')).toBe('Unknown'); + test("returns default value when path does not exist", () => { + const obj = { user: { name: "John" } }; + expect(getValueAtPath(obj, ["user", "address", "city"], "Unknown")).toBe( + "Unknown", + ); }); - test('returns default value when input is null/undefined', () => { - expect(getValueAtPath(null as any, ['foo'], 'default')).toBe('default'); - expect(getValueAtPath(undefined as any, ['foo'], 'default')).toBe('default'); + test("returns default value when input is null/undefined", () => { + expect(getValueAtPath(null as any, ["foo"], "default")).toBe("default"); + expect(getValueAtPath(undefined as any, ["foo"], "default")).toBe( + "default", + ); }); - test('handles array indices correctly', () => { - const arr = ['a', 'b', 'c']; - expect(getValueAtPath(arr, ['1'])).toBe('b'); + test("handles array indices correctly", () => { + const arr = ["a", "b", "c"]; + expect(getValueAtPath(arr, ["1"])).toBe("b"); }); - test('returns default value for out of bounds array indices', () => { - const arr = ['a', 'b', 'c']; - expect(getValueAtPath(arr, ['5'], 'default')).toBe('default'); + test("returns default value for out of bounds array indices", () => { + const arr = ["a", "b", "c"]; + expect(getValueAtPath(arr, ["5"], "default")).toBe("default"); }); - test('returns default value for invalid array indices', () => { - const arr = ['a', 'b', 'c']; - expect(getValueAtPath(arr, ['invalid'], 'default')).toBe('default'); + test("returns default value for invalid array indices", () => { + const arr = ["a", "b", "c"]; + expect(getValueAtPath(arr, ["invalid"], "default")).toBe("default"); }); - test('navigates through mixed object and array paths', () => { - const obj = { users: [{ name: 'John' }, { name: 'Jane' }] }; - expect(getValueAtPath(obj, ['users', '1', 'name'])).toBe('Jane'); + test("navigates through mixed object and array paths", () => { + const obj = { users: [{ name: "John" }, { name: "Jane" }] }; + expect(getValueAtPath(obj, ["users", "1", "name"])).toBe("Jane"); }); }); diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index beb267e..0a10af4 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -1,135 +1,141 @@ -import { generateDefaultValue, formatFieldLabel, validateValueAgainstSchema } from '../schemaUtils'; -import { JsonSchemaType } from '../../components/DynamicJsonForm'; +import { + generateDefaultValue, + formatFieldLabel, + validateValueAgainstSchema, +} from "../schemaUtils"; +import { JsonSchemaType } from "../../components/DynamicJsonForm"; -describe('generateDefaultValue', () => { - test('generates default string', () => { - expect(generateDefaultValue({ type: 'string' })).toBe(''); +describe("generateDefaultValue", () => { + test("generates default string", () => { + expect(generateDefaultValue({ type: "string" })).toBe(""); }); - test('generates default number', () => { - expect(generateDefaultValue({ type: 'number' })).toBe(0); + test("generates default number", () => { + expect(generateDefaultValue({ type: "number" })).toBe(0); }); - test('generates default integer', () => { - expect(generateDefaultValue({ type: 'integer' })).toBe(0); + test("generates default integer", () => { + expect(generateDefaultValue({ type: "integer" })).toBe(0); }); - test('generates default boolean', () => { - expect(generateDefaultValue({ type: 'boolean' })).toBe(false); + test("generates default boolean", () => { + expect(generateDefaultValue({ type: "boolean" })).toBe(false); }); - test('generates default array', () => { - expect(generateDefaultValue({ type: 'array' })).toEqual([]); + test("generates default array", () => { + expect(generateDefaultValue({ type: "array" })).toEqual([]); }); - test('generates default empty object', () => { - expect(generateDefaultValue({ type: 'object' })).toEqual({}); + test("generates default empty object", () => { + expect(generateDefaultValue({ type: "object" })).toEqual({}); }); - test('generates default null for unknown types', () => { - expect(generateDefaultValue({ type: 'unknown' as any })).toBe(null); + test("generates default null for unknown types", () => { + expect(generateDefaultValue({ type: "unknown" as any })).toBe(null); }); - test('generates object with properties', () => { + test("generates object with properties", () => { const schema: JsonSchemaType = { - type: 'object', + type: "object", properties: { - name: { type: 'string' }, - age: { type: 'number' }, - isActive: { type: 'boolean' } - } + name: { type: "string" }, + age: { type: "number" }, + isActive: { type: "boolean" }, + }, }; expect(generateDefaultValue(schema)).toEqual({ - name: '', + name: "", age: 0, - isActive: false + isActive: false, }); }); - test('handles nested objects', () => { + test("handles nested objects", () => { const schema: JsonSchemaType = { - type: 'object', + type: "object", properties: { user: { - type: 'object', + type: "object", properties: { - name: { type: 'string' }, + name: { type: "string" }, address: { - type: 'object', + type: "object", properties: { - city: { type: 'string' } - } - } - } - } - } + city: { type: "string" }, + }, + }, + }, + }, + }, }; expect(generateDefaultValue(schema)).toEqual({ user: { - name: '', + name: "", address: { - city: '' - } - } + city: "", + }, + }, }); }); }); -describe('formatFieldLabel', () => { - test('formats camelCase', () => { - expect(formatFieldLabel('firstName')).toBe('First Name'); +describe("formatFieldLabel", () => { + test("formats camelCase", () => { + expect(formatFieldLabel("firstName")).toBe("First Name"); }); - test('formats snake_case', () => { - expect(formatFieldLabel('first_name')).toBe('First name'); + test("formats snake_case", () => { + expect(formatFieldLabel("first_name")).toBe("First name"); }); - test('formats single word', () => { - expect(formatFieldLabel('name')).toBe('Name'); + test("formats single word", () => { + expect(formatFieldLabel("name")).toBe("Name"); }); - test('formats mixed case with underscores', () => { - expect(formatFieldLabel('user_firstName')).toBe('User first Name'); + test("formats mixed case with underscores", () => { + expect(formatFieldLabel("user_firstName")).toBe("User first Name"); }); - test('handles empty string', () => { - expect(formatFieldLabel('')).toBe(''); + test("handles empty string", () => { + expect(formatFieldLabel("")).toBe(""); }); }); -describe('validateValueAgainstSchema', () => { - test('validates string type', () => { - expect(validateValueAgainstSchema('test', { type: 'string' })).toBe(true); - expect(validateValueAgainstSchema(123, { type: 'string' })).toBe(false); +describe("validateValueAgainstSchema", () => { + test("validates string type", () => { + expect(validateValueAgainstSchema("test", { type: "string" })).toBe(true); + expect(validateValueAgainstSchema(123, { type: "string" })).toBe(false); }); - test('validates number type', () => { - expect(validateValueAgainstSchema(123, { type: 'number' })).toBe(true); - expect(validateValueAgainstSchema('test', { type: 'number' })).toBe(false); + test("validates number type", () => { + expect(validateValueAgainstSchema(123, { type: "number" })).toBe(true); + expect(validateValueAgainstSchema("test", { type: "number" })).toBe(false); }); - test('validates integer type', () => { - expect(validateValueAgainstSchema(123, { type: 'integer' })).toBe(true); - expect(validateValueAgainstSchema('test', { type: 'integer' })).toBe(false); + test("validates integer type", () => { + expect(validateValueAgainstSchema(123, { type: "integer" })).toBe(true); + expect(validateValueAgainstSchema("test", { type: "integer" })).toBe(false); }); - test('validates boolean type', () => { - expect(validateValueAgainstSchema(true, { type: 'boolean' })).toBe(true); - expect(validateValueAgainstSchema('test', { type: 'boolean' })).toBe(false); + test("validates boolean type", () => { + expect(validateValueAgainstSchema(true, { type: "boolean" })).toBe(true); + expect(validateValueAgainstSchema("test", { type: "boolean" })).toBe(false); }); - test('validates array type', () => { - expect(validateValueAgainstSchema([], { type: 'array' })).toBe(true); - expect(validateValueAgainstSchema({}, { type: 'array' })).toBe(false); + test("validates array type", () => { + expect(validateValueAgainstSchema([], { type: "array" })).toBe(true); + expect(validateValueAgainstSchema({}, { type: "array" })).toBe(false); }); - test('validates object type', () => { - expect(validateValueAgainstSchema({}, { type: 'object' })).toBe(true); - expect(validateValueAgainstSchema([], { type: 'object' })).toBe(false); - expect(validateValueAgainstSchema('test', { type: 'object' })).toBe(false); + test("validates object type", () => { + expect(validateValueAgainstSchema({}, { type: "object" })).toBe(true); + expect(validateValueAgainstSchema([], { type: "object" })).toBe(false); + expect(validateValueAgainstSchema("test", { type: "object" })).toBe(false); }); - test('returns true for unknown types', () => { - expect(validateValueAgainstSchema('anything', { type: 'unknown' as any })).toBe(true); + test("returns true for unknown types", () => { + expect( + validateValueAgainstSchema("anything", { type: "unknown" as any }), + ).toBe(true); }); }); diff --git a/client/src/utils/jsonPathUtils.ts b/client/src/utils/jsonPathUtils.ts index ef5a18e..25b1e0a 100644 --- a/client/src/utils/jsonPathUtils.ts +++ b/client/src/utils/jsonPathUtils.ts @@ -10,9 +10,9 @@ export type JsonObject = { [key: string]: JsonValue }; * @returns A new JSON value with the updated path */ export function updateValueAtPath( - obj: JsonValue, - path: string[], - value: JsonValue + obj: JsonValue, + path: string[], + value: JsonValue, ): JsonValue { if (path.length === 0) return value; @@ -24,16 +24,16 @@ export function updateValueAtPath( // Handle arrays if (Array.isArray(obj)) { return updateArray(obj, path, value); - } + } // Handle objects else if (typeof obj === "object" && obj !== null) { return updateObject(obj as JsonObject, path, value); - } + } // Cannot update primitives else { console.error( `Cannot update path ${path.join(".")} in non-object/array value:`, - obj + obj, ); return obj; } @@ -43,9 +43,9 @@ export function updateValueAtPath( * Updates an array at a specific path */ function updateArray( - array: JsonValue[], - path: string[], - value: JsonValue + array: JsonValue[], + path: string[], + value: JsonValue, ): JsonValue[] { const [index, ...restPath] = path; const arrayIndex = Number(index); @@ -82,7 +82,11 @@ function updateArray( if (restPath.length === 0) { newArray[arrayIndex] = value; } else { - newArray[arrayIndex] = updateValueAtPath(newArray[arrayIndex], restPath, value); + newArray[arrayIndex] = updateValueAtPath( + newArray[arrayIndex], + restPath, + value, + ); } return newArray; } @@ -91,9 +95,9 @@ function updateArray( * Updates an object at a specific path */ function updateObject( - obj: JsonObject, - path: string[], - value: JsonValue + obj: JsonObject, + path: string[], + value: JsonValue, ): JsonObject { const [key, ...restPath] = path; @@ -125,18 +129,18 @@ function updateObject( * @returns The value at the path, or defaultValue if not found */ export function getValueAtPath( - obj: JsonValue, - path: string[], - defaultValue: JsonValue = null + obj: JsonValue, + path: string[], + defaultValue: JsonValue = null, ): JsonValue { if (path.length === 0) return obj; - + const [first, ...rest] = path; - + if (obj === null || obj === undefined) { return defaultValue; } - + if (Array.isArray(obj)) { const index = Number(first); if (isNaN(index) || index < 0 || index >= obj.length) { @@ -144,13 +148,13 @@ export function getValueAtPath( } return getValueAtPath(obj[index], rest, defaultValue); } - + if (typeof obj === "object" && obj !== null) { if (!(first in obj)) { return defaultValue; } return getValueAtPath((obj as JsonObject)[first], rest, defaultValue); } - + return defaultValue; } diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index feee4de..b3c8e48 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -50,8 +50,8 @@ export function formatFieldLabel(key: string): string { * @returns True if valid, false otherwise */ export function validateValueAgainstSchema( - value: JsonValue, - schema: JsonSchemaType + value: JsonValue, + schema: JsonSchemaType, ): boolean { // Basic type validation switch (schema.type) { @@ -65,7 +65,9 @@ export function validateValueAgainstSchema( case "array": return Array.isArray(value); case "object": - return typeof value === "object" && value !== null && !Array.isArray(value); + return ( + typeof value === "object" && value !== null && !Array.isArray(value) + ); default: return true; } From 8ac7ef0985d7bda5ee996cdf1b789699e22a07d2 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:23:40 -0700 Subject: [PATCH 09/98] Fix path to client --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d1160c9..9cb4bf5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,7 +26,7 @@ jobs: - name: Run client tests run: | - cd inspector/client + cd client npm test - run: npm run build From 720480cbbbae517c0f147ab1ed6abee408ccf76b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:28:13 -0700 Subject: [PATCH 10/98] Remove console.warn and extra comments to reduce code noise --- client/src/utils/jsonPathUtils.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/src/utils/jsonPathUtils.ts b/client/src/utils/jsonPathUtils.ts index 25b1e0a..16a4ee9 100644 --- a/client/src/utils/jsonPathUtils.ts +++ b/client/src/utils/jsonPathUtils.ts @@ -16,20 +16,16 @@ export function updateValueAtPath( ): JsonValue { if (path.length === 0) return value; - // Initialize if null/undefined if (obj === null || obj === undefined) { obj = !isNaN(Number(path[0])) ? [] : {}; } - // Handle arrays if (Array.isArray(obj)) { return updateArray(obj, path, value); } - // Handle objects else if (typeof obj === "object" && obj !== null) { return updateObject(obj as JsonObject, path, value); } - // Cannot update primitives else { console.error( `Cannot update path ${path.join(".")} in non-object/array value:`, @@ -50,27 +46,22 @@ function updateArray( const [index, ...restPath] = path; const arrayIndex = Number(index); - // Validate array index if (isNaN(arrayIndex)) { console.error(`Invalid array index: ${index}`); return array; } - // Check array bounds if (arrayIndex < 0) { console.error(`Array index out of bounds: ${arrayIndex} < 0`); return array; } - // Create a dense copy of the array, filling holes with null let newArray: JsonValue[] = []; for (let i = 0; i < array.length; i++) { newArray[i] = i in array ? array[i] : null; } - // If the desired index is out of bounds, build a new array explicitly filled with nulls if (arrayIndex >= newArray.length) { - console.warn(`Extending array to index ${arrayIndex}`); const extendedArray: JsonValue[] = new Array(arrayIndex).fill(null); // Copy over the existing elements (now guaranteed to be dense) for (let i = 0; i < newArray.length; i++) { From d1f5b3b93383dc133f7319e33895e54caf18e75f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:30:38 -0700 Subject: [PATCH 11/98] Fix formatting --- client/src/utils/jsonPathUtils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/src/utils/jsonPathUtils.ts b/client/src/utils/jsonPathUtils.ts index 16a4ee9..ac99940 100644 --- a/client/src/utils/jsonPathUtils.ts +++ b/client/src/utils/jsonPathUtils.ts @@ -22,11 +22,9 @@ export function updateValueAtPath( if (Array.isArray(obj)) { return updateArray(obj, path, value); - } - else if (typeof obj === "object" && obj !== null) { + } else if (typeof obj === "object" && obj !== null) { return updateObject(obj as JsonObject, path, value); - } - else { + } else { console.error( `Cannot update path ${path.join(".")} in non-object/array value:`, obj, From abd4877dae0e1d9e90cd02ef9e8abdcc2cc7809f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:37:47 -0700 Subject: [PATCH 12/98] Revert to only run on main --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9cb4bf5..fb0aa6d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,8 @@ on: - # TODO: Revert to only running on main branch pushes after testing push: + branches: + - main + pull_request: release: types: [published] From 6ec82e21b1f0e19f01fd5883723b7915c30025fa Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:48:19 -0700 Subject: [PATCH 13/98] Remove some fluff --- client/src/components/JsonEditor.tsx | 3 --- package-lock.json | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/client/src/components/JsonEditor.tsx b/client/src/components/JsonEditor.tsx index 87a6367..0d2596e 100644 --- a/client/src/components/JsonEditor.tsx +++ b/client/src/components/JsonEditor.tsx @@ -27,9 +27,6 @@ const JsonEditor = ({ const formatJson = (json: string): string => { try { - // Handle empty arrays and objects specifically - if (json.trim() === "[]") return "[]"; - if (json.trim() === "{}") return "{}"; return JSON.stringify(JSON.parse(json), null, 2); } catch { return json; diff --git a/package-lock.json b/package-lock.json index a83799d..90af99a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,8 +35,8 @@ "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.4.1", - "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.3", From 44982e6c9747e543251f819a0eda47b007b057d1 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 21:33:37 -0700 Subject: [PATCH 14/98] Default to nulls and update tests --- client/package.json | 5 +- client/src/components/DynamicJsonForm.tsx | 71 +++++++++++++++---- .../src/utils/__tests__/schemaUtils.test.ts | 32 +++++---- client/src/utils/schemaUtils.ts | 33 +++++++-- package-lock.json | 12 ++++ 5 files changed, 120 insertions(+), 33 deletions(-) diff --git a/client/package.json b/client/package.json index 4735ffa..038a02a 100644 --- a/client/package.json +++ b/client/package.json @@ -24,8 +24,8 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.4.1", - "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.3", @@ -37,8 +37,8 @@ "clsx": "^2.1.1", "cmdk": "^1.0.4", "lucide-react": "^0.447.0", - "prismjs": "^1.29.0", "pkce-challenge": "^4.1.0", + "prismjs": "^1.29.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-simple-code-editor": "^0.14.1", @@ -57,6 +57,7 @@ "@types/serve-handler": "^6.1.4", "@vitejs/plugin-react": "^4.3.2", "autoprefixer": "^10.4.20", + "co": "^4.6.0", "eslint": "^9.11.1", "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.12", diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 9e865f9..5237513 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -11,12 +11,15 @@ export type JsonValue = | number | boolean | null + | undefined | JsonValue[] | { [key: string]: JsonValue }; export type JsonSchemaType = { - type: "string" | "number" | "integer" | "boolean" | "array" | "object"; + type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "null"; description?: string; + required?: boolean; + default?: JsonValue; properties?: Record; items?: JsonSchemaType; }; @@ -105,21 +108,61 @@ const DynamicJsonForm = ({ switch (propSchema.type) { case "string": + return ( + { + const val = e.target.value; + if (!val && !propSchema.required) { + handleFieldChange(path, undefined); + } else { + handleFieldChange(path, val); + } + }} + placeholder={propSchema.description} + required={propSchema.required} + /> + ); case "number": + return ( + { + const val = e.target.value; + if (!val && !propSchema.required) { + handleFieldChange(path, undefined); + } else { + const num = Number(val); + if (!isNaN(num)) { + handleFieldChange(path, num); + } + } + }} + placeholder={propSchema.description} + required={propSchema.required} + /> + ); case "integer": return ( - handleFieldChange( - path, - propSchema.type === "string" - ? e.target.value - : Number(e.target.value), - ) - } + type="number" + step="1" + value={(currentValue as number)?.toString() ?? ""} + onChange={(e) => { + const val = e.target.value; + if (!val && !propSchema.required) { + handleFieldChange(path, undefined); + } else { + const num = Number(val); + if (!isNaN(num) && Number.isInteger(num)) { + handleFieldChange(path, num); + } + } + }} placeholder={propSchema.description} + required={propSchema.required} /> ); case "boolean": @@ -129,6 +172,7 @@ const DynamicJsonForm = ({ checked={(currentValue as boolean) ?? false} onChange={(e) => handleFieldChange(path, e.target.checked)} className="w-4 h-4" + required={propSchema.required} /> ); case "object": { @@ -216,9 +260,12 @@ const DynamicJsonForm = ({ variant="outline" size="sm" onClick={() => { + const defaultValue = generateDefaultValue( + propSchema.items as JsonSchemaType + ); handleFieldChange(path, [ ...arrayValue, - generateDefaultValue(propSchema.items as JsonSchemaType), + defaultValue ?? null ]); }} title={ diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index 0a10af4..a238798 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -7,40 +7,42 @@ import { JsonSchemaType } from "../../components/DynamicJsonForm"; describe("generateDefaultValue", () => { test("generates default string", () => { - expect(generateDefaultValue({ type: "string" })).toBe(""); + expect(generateDefaultValue({ type: "string", required: true })).toBe(""); }); test("generates default number", () => { - expect(generateDefaultValue({ type: "number" })).toBe(0); + expect(generateDefaultValue({ type: "number", required: true })).toBe(0); }); test("generates default integer", () => { - expect(generateDefaultValue({ type: "integer" })).toBe(0); + expect(generateDefaultValue({ type: "integer", required: true })).toBe(0); }); test("generates default boolean", () => { - expect(generateDefaultValue({ type: "boolean" })).toBe(false); + expect(generateDefaultValue({ type: "boolean", required: true })).toBe(false); }); test("generates default array", () => { - expect(generateDefaultValue({ type: "array" })).toEqual([]); + expect(generateDefaultValue({ type: "array", required: true })).toEqual([]); }); test("generates default empty object", () => { - expect(generateDefaultValue({ type: "object" })).toEqual({}); + expect(generateDefaultValue({ type: "object", required: true })).toEqual({}); }); test("generates default null for unknown types", () => { - expect(generateDefaultValue({ type: "unknown" as any })).toBe(null); + // @ts-expect-error Testing with invalid type + expect(generateDefaultValue({ type: "unknown", required: true })).toBe(null); }); test("generates object with properties", () => { const schema: JsonSchemaType = { type: "object", + required: true, properties: { - name: { type: "string" }, - age: { type: "number" }, - isActive: { type: "boolean" }, + name: { type: "string", required: true }, + age: { type: "number", required: true }, + isActive: { type: "boolean", required: true }, }, }; expect(generateDefaultValue(schema)).toEqual({ @@ -53,15 +55,18 @@ describe("generateDefaultValue", () => { test("handles nested objects", () => { const schema: JsonSchemaType = { type: "object", + required: true, properties: { user: { type: "object", + required: true, properties: { - name: { type: "string" }, + name: { type: "string", required: true }, address: { type: "object", + required: true, properties: { - city: { type: "string" }, + city: { type: "string", required: true }, }, }, }, @@ -135,7 +140,8 @@ describe("validateValueAgainstSchema", () => { test("returns true for unknown types", () => { expect( - validateValueAgainstSchema("anything", { type: "unknown" as any }), + // @ts-expect-error Testing with invalid type + validateValueAgainstSchema("anything", { type: "unknown" }), ).toBe(true); }); }); diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index b3c8e48..9c438e7 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -4,9 +4,19 @@ import { JsonObject } from "./jsonPathUtils"; /** * Generates a default value based on a JSON schema type * @param schema The JSON schema definition - * @returns A default value matching the schema type + * @returns A default value matching the schema type, or null for non-required fields */ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { + + if ("default" in schema) { + // Ensure we don't return undefined even if schema.default is undefined + return schema.default === undefined ? null : schema.default; + } + + if (!schema.required) { + return null; + } + switch (schema.type) { case "string": return ""; @@ -18,12 +28,15 @@ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { case "array": return []; case "object": { + if (!schema.properties) return {}; + const obj: JsonObject = {}; - if (schema.properties) { - Object.entries(schema.properties).forEach(([key, prop]) => { - obj[key] = generateDefaultValue(prop); + Object.entries(schema.properties) + .filter(([, prop]) => prop.required) + .forEach(([key, prop]) => { + const value = generateDefaultValue(prop); + obj[key] = value; }); - } return obj; } default: @@ -53,13 +66,19 @@ export function validateValueAgainstSchema( value: JsonValue, schema: JsonSchemaType, ): boolean { + // Handle undefined values for non-required fields + if (value === undefined && !schema.required) { + return true; + } + // Basic type validation switch (schema.type) { case "string": return typeof value === "string"; case "number": - case "integer": return typeof value === "number"; + case "integer": + return typeof value === "number" && Number.isInteger(value); case "boolean": return typeof value === "boolean"; case "array": @@ -68,6 +87,8 @@ export function validateValueAgainstSchema( return ( typeof value === "object" && value !== null && !Array.isArray(value) ); + case "null": + return value === null; default: return true; } diff --git a/package-lock.json b/package-lock.json index 90af99a..a7df022 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ "@types/serve-handler": "^6.1.4", "@vitejs/plugin-react": "^4.3.2", "autoprefixer": "^10.4.20", + "co": "^4.6.0", "eslint": "^9.11.1", "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.12", @@ -4800,6 +4801,17 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", From 82bbe58a46aba04c87d903621a9f808d94bb2917 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 22:08:04 -0700 Subject: [PATCH 15/98] Fix formatting --- client/src/components/DynamicJsonForm.tsx | 13 ++++++++++--- client/src/utils/__tests__/schemaUtils.test.ts | 12 +++++++++--- client/src/utils/schemaUtils.ts | 3 +-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 5237513..15cfde6 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -16,7 +16,14 @@ export type JsonValue = | { [key: string]: JsonValue }; export type JsonSchemaType = { - type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "null"; + type: + | "string" + | "number" + | "integer" + | "boolean" + | "array" + | "object" + | "null"; description?: string; required?: boolean; default?: JsonValue; @@ -261,11 +268,11 @@ const DynamicJsonForm = ({ size="sm" onClick={() => { const defaultValue = generateDefaultValue( - propSchema.items as JsonSchemaType + propSchema.items as JsonSchemaType, ); handleFieldChange(path, [ ...arrayValue, - defaultValue ?? null + defaultValue ?? null, ]); }} title={ diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index a238798..4eba268 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -19,7 +19,9 @@ describe("generateDefaultValue", () => { }); test("generates default boolean", () => { - expect(generateDefaultValue({ type: "boolean", required: true })).toBe(false); + expect(generateDefaultValue({ type: "boolean", required: true })).toBe( + false, + ); }); test("generates default array", () => { @@ -27,12 +29,16 @@ describe("generateDefaultValue", () => { }); test("generates default empty object", () => { - expect(generateDefaultValue({ type: "object", required: true })).toEqual({}); + expect(generateDefaultValue({ type: "object", required: true })).toEqual( + {}, + ); }); test("generates default null for unknown types", () => { // @ts-expect-error Testing with invalid type - expect(generateDefaultValue({ type: "unknown", required: true })).toBe(null); + expect(generateDefaultValue({ type: "unknown", required: true })).toBe( + null, + ); }); test("generates object with properties", () => { diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index 9c438e7..ab4c920 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -7,7 +7,6 @@ import { JsonObject } from "./jsonPathUtils"; * @returns A default value matching the schema type, or null for non-required fields */ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { - if ("default" in schema) { // Ensure we don't return undefined even if schema.default is undefined return schema.default === undefined ? null : schema.default; @@ -29,7 +28,7 @@ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { return []; case "object": { if (!schema.properties) return {}; - + const obj: JsonObject = {}; Object.entries(schema.properties) .filter(([, prop]) => prop.required) From a1eb343b7924253b090ea4ce2fbf8f31b327381e Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 06:26:04 -0700 Subject: [PATCH 16/98] Remove unused function plus tests --- .../src/utils/__tests__/schemaUtils.test.ts | 41 ------------------- client/src/utils/schemaUtils.ts | 38 ----------------- 2 files changed, 79 deletions(-) diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index 4eba268..20e7832 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -1,7 +1,6 @@ import { generateDefaultValue, formatFieldLabel, - validateValueAgainstSchema, } from "../schemaUtils"; import { JsonSchemaType } from "../../components/DynamicJsonForm"; @@ -111,43 +110,3 @@ describe("formatFieldLabel", () => { expect(formatFieldLabel("")).toBe(""); }); }); - -describe("validateValueAgainstSchema", () => { - test("validates string type", () => { - expect(validateValueAgainstSchema("test", { type: "string" })).toBe(true); - expect(validateValueAgainstSchema(123, { type: "string" })).toBe(false); - }); - - test("validates number type", () => { - expect(validateValueAgainstSchema(123, { type: "number" })).toBe(true); - expect(validateValueAgainstSchema("test", { type: "number" })).toBe(false); - }); - - test("validates integer type", () => { - expect(validateValueAgainstSchema(123, { type: "integer" })).toBe(true); - expect(validateValueAgainstSchema("test", { type: "integer" })).toBe(false); - }); - - test("validates boolean type", () => { - expect(validateValueAgainstSchema(true, { type: "boolean" })).toBe(true); - expect(validateValueAgainstSchema("test", { type: "boolean" })).toBe(false); - }); - - test("validates array type", () => { - expect(validateValueAgainstSchema([], { type: "array" })).toBe(true); - expect(validateValueAgainstSchema({}, { type: "array" })).toBe(false); - }); - - test("validates object type", () => { - expect(validateValueAgainstSchema({}, { type: "object" })).toBe(true); - expect(validateValueAgainstSchema([], { type: "object" })).toBe(false); - expect(validateValueAgainstSchema("test", { type: "object" })).toBe(false); - }); - - test("returns true for unknown types", () => { - expect( - // @ts-expect-error Testing with invalid type - validateValueAgainstSchema("anything", { type: "unknown" }), - ).toBe(true); - }); -}); diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index ab4c920..88a9b53 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -54,41 +54,3 @@ export function formatFieldLabel(key: string): string { .replace(/_/g, " ") // Replace underscores with spaces .replace(/^\w/, (c) => c.toUpperCase()); // Capitalize first letter } - -/** - * Validates if a value conforms to a JSON schema - * @param value The value to validate - * @param schema The JSON schema to validate against - * @returns True if valid, false otherwise - */ -export function validateValueAgainstSchema( - value: JsonValue, - schema: JsonSchemaType, -): boolean { - // Handle undefined values for non-required fields - if (value === undefined && !schema.required) { - return true; - } - - // Basic type validation - switch (schema.type) { - case "string": - return typeof value === "string"; - case "number": - return typeof value === "number"; - case "integer": - return typeof value === "number" && Number.isInteger(value); - case "boolean": - return typeof value === "boolean"; - case "array": - return Array.isArray(value); - case "object": - return ( - typeof value === "object" && value !== null && !Array.isArray(value) - ); - case "null": - return value === null; - default: - return true; - } -} From 0e50b68f96b1c1c693d2a63edaba1dbe96a1af46 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 06:30:23 -0700 Subject: [PATCH 17/98] Fix formatting --- client/src/utils/__tests__/schemaUtils.test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index 20e7832..cd9c288 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -1,7 +1,4 @@ -import { - generateDefaultValue, - formatFieldLabel, -} from "../schemaUtils"; +import { generateDefaultValue, formatFieldLabel } from "../schemaUtils"; import { JsonSchemaType } from "../../components/DynamicJsonForm"; describe("generateDefaultValue", () => { From 36aa7316ea224ee7362a707a635e1bb360587b44 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 07:31:26 -0700 Subject: [PATCH 18/98] Fix issue where array type defaults to object --- client/src/components/ToolsTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 777db80..71c2920 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -214,7 +214,7 @@ const ToolsTab = ({ description: prop.description, items: prop.items, }} - value={(params[key] as JsonValue) ?? {}} + value={(params[key] as JsonValue) ?? (prop.type === "array" ? [] : {})} onChange={(newValue: JsonValue) => { setParams({ ...params, From e7f55f083faca237330ec097afb7209794ee1397 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 07:34:01 -0700 Subject: [PATCH 19/98] Fix formatting --- client/src/components/ToolsTab.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 71c2920..810242f 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -214,7 +214,10 @@ const ToolsTab = ({ description: prop.description, items: prop.items, }} - value={(params[key] as JsonValue) ?? (prop.type === "array" ? [] : {})} + value={ + (params[key] as JsonValue) ?? + (prop.type === "array" ? [] : {}) + } onChange={(newValue: JsonValue) => { setParams({ ...params, From b01e38665957c40b06de8fb059613123031ea439 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 09:06:51 -0700 Subject: [PATCH 20/98] Always use JSON mode if the schema type is object and has no properties --- .gitignore | 1 + client/src/components/DynamicJsonForm.tsx | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/.gitignore b/.gitignore index 0f4928e..9f1d63b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ server/build client/dist client/tsconfig.app.tsbuildinfo client/tsconfig.node.tsbuildinfo +.vscode diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 15cfde6..a57ccba 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -307,6 +307,16 @@ const DynamicJsonForm = ({ } }; + const shouldUseJsonMode = + schema.type === "object" && + (!schema.properties || Object.keys(schema.properties).length === 0); + + useEffect(() => { + if (shouldUseJsonMode && !isJsonMode) { + setIsJsonMode(true); + } + }, [shouldUseJsonMode, isJsonMode]); + return (
From 06773bb6dd09f570d73fc1188502721764e73506 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 09:13:33 -0700 Subject: [PATCH 21/98] Fix formatting --- client/src/components/DynamicJsonForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index a57ccba..8e6f8c2 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -307,8 +307,8 @@ const DynamicJsonForm = ({ } }; - const shouldUseJsonMode = - schema.type === "object" && + const shouldUseJsonMode = + schema.type === "object" && (!schema.properties || Object.keys(schema.properties).length === 0); useEffect(() => { From f9b105c0efe274259ac3a405958f9699611ced95 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 4 Mar 2025 20:54:13 -0700 Subject: [PATCH 22/98] Use debounce instead --- client/src/components/DynamicJsonForm.tsx | 38 ++++++++++++++++------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 8e6f8c2..0e2ee48 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -51,6 +51,28 @@ const DynamicJsonForm = ({ JSON.stringify(value ?? generateDefaultValue(schema), null, 2), ); + // Create a ref to store the timeout ID + const timeoutRef = useRef>(); + + // Create a debounced function to update parent state + const debouncedUpdateParent = useCallback((jsonString: string) => { + // Clear any existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + // Set a new timeout + timeoutRef.current = setTimeout(() => { + try { + const parsed = JSON.parse(jsonString); + onChange(parsed); + setJsonError(undefined); + } catch { + // Don't set error during normal typing + } + }, 300); + }, [onChange, setJsonError]); + // Update rawJsonValue when value prop changes useEffect(() => { if (!isJsonMode) { @@ -329,17 +351,11 @@ const DynamicJsonForm = ({ { + // Always update local state setRawJsonValue(newValue); - try { - if (/^\s*[{[].*[}\]]\s*$/.test(newValue)) { - const parsed = JSON.parse(newValue); - onChange(parsed); - setJsonError(undefined); - } - } catch { - // Don't set an error during typing - that will happen when the user - // tries to save or submit the form - } + + // Use the debounced function to attempt parsing and updating parent + debouncedUpdateParent(newValue); }} error={jsonError} /> From 00836dbf9edce12ef64f1cd6add968b1b4102d3f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 4 Mar 2025 20:55:57 -0700 Subject: [PATCH 23/98] Fix formatting --- client/src/components/DynamicJsonForm.tsx | 37 ++++++++++++----------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 0e2ee48..430b7ba 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -55,23 +55,26 @@ const DynamicJsonForm = ({ const timeoutRef = useRef>(); // Create a debounced function to update parent state - const debouncedUpdateParent = useCallback((jsonString: string) => { - // Clear any existing timeout - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - // Set a new timeout - timeoutRef.current = setTimeout(() => { - try { - const parsed = JSON.parse(jsonString); - onChange(parsed); - setJsonError(undefined); - } catch { - // Don't set error during normal typing + const debouncedUpdateParent = useCallback( + (jsonString: string) => { + // Clear any existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); } - }, 300); - }, [onChange, setJsonError]); + + // Set a new timeout + timeoutRef.current = setTimeout(() => { + try { + const parsed = JSON.parse(jsonString); + onChange(parsed); + setJsonError(undefined); + } catch { + // Don't set error during normal typing + } + }, 300); + }, + [onChange, setJsonError], + ); // Update rawJsonValue when value prop changes useEffect(() => { @@ -353,7 +356,7 @@ const DynamicJsonForm = ({ onChange={(newValue) => { // Always update local state setRawJsonValue(newValue); - + // Use the debounced function to attempt parsing and updating parent debouncedUpdateParent(newValue); }} From b9b116a5f254275225b6235832e21b2a2f5e1bde Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 5 Mar 2025 07:55:15 -0700 Subject: [PATCH 24/98] Remove duplicate react-dialog from merge --- client/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/client/package.json b/client/package.json index 1d40925..d45cdc7 100644 --- a/client/package.json +++ b/client/package.json @@ -26,7 +26,6 @@ "@modelcontextprotocol/sdk": "^1.6.1", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", - "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.3", From 747c0154c51dfc42f001c3f14cdd9484dcdd8d29 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 8 Mar 2025 11:05:13 -0500 Subject: [PATCH 25/98] WIP: Subscribe to resources * In App.tsx - added subscribeToResource() - takes a uri - sends a `resources/subscribe` message with the uri - added unsubscribeFromResource() - takes a uri - sends a `resources/unsubscribe` message with the uri - in ResourcesTab element, - pass subscribeToResource and subscribeToResource invokers to component * In notificationTypes.ts - add ServerNotificationSchema to NotificationSchema to permit server update messages. * In ResourcesTab.tsx - deconstruct subscribeToResource and unsubscribeFromResource and add prop types - Add Subscribe and Unsubscribe buttons to selected resource panel, left of the refresh button. They call the sub and unsub functions that came in on props, passing the selected resource URI. - [WIP]: Will show the appropriate button in a follow up commit. * In useConnection.ts - import ResourceUpdatedNotificationSchema - in the connect function, - set onNotification as the handler for ResourceUpdatedNotificationSchema --- client/src/App.tsx | 33 +++++++++++++++++++++++ client/src/components/ResourcesTab.tsx | 36 ++++++++++++++++++++------ client/src/lib/hooks/useConnection.ts | 6 +++++ client/src/lib/notificationTypes.ts | 7 ++--- 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index a9adea5..f5b1f79 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -308,6 +308,31 @@ const App = () => { setResourceContent(JSON.stringify(response, null, 2)); }; + const subscribeToResource = async (uri: string) => { + + await makeRequest( + { + method: "resources/subscribe" as const, + params: { uri }, + }, + z.object({}), + "resources", + ); + }; + + const unsubscribeFromResource = async (uri: string) => { + + await makeRequest( + { + method: "resources/unsubscribe" as const, + params: { uri }, + }, + z.object({}), + "resources", + ); + }; + + const listPrompts = async () => { const response = await makeRequest( { @@ -485,6 +510,14 @@ const App = () => { clearError("resources"); setSelectedResource(resource); }} + subscribeToResource={(uri) => { + clearError("resources"); + subscribeToResource(uri); + }} + unsubscribeFromResource={(uri) => { + clearError("resources"); + unsubscribeFromResource(uri); + }} handleCompletion={handleCompletion} completionsSupported={completionsSupported} resourceContent={resourceContent} diff --git a/client/src/components/ResourcesTab.tsx b/client/src/components/ResourcesTab.tsx index 93127a9..9d94296 100644 --- a/client/src/components/ResourcesTab.tsx +++ b/client/src/components/ResourcesTab.tsx @@ -26,6 +26,8 @@ const ResourcesTab = ({ readResource, selectedResource, setSelectedResource, + subscribeToResource, + unsubscribeFromResource, handleCompletion, completionsSupported, resourceContent, @@ -52,6 +54,8 @@ const ResourcesTab = ({ nextCursor: ListResourcesResult["nextCursor"]; nextTemplateCursor: ListResourceTemplatesResult["nextCursor"]; error: string | null; + subscribeToResource: (uri: string) => void; + unsubscribeFromResource: (uri: string) => void; }) => { const [selectedTemplate, setSelectedTemplate] = useState(null); @@ -164,14 +168,30 @@ const ResourcesTab = ({ : "Select a resource or template"} {selectedResource && ( - + <> + + + + )}
diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 75b5467..9e7bb11 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -9,6 +9,7 @@ import { CreateMessageRequestSchema, ListRootsRequestSchema, ProgressNotificationSchema, + ResourceUpdatedNotificationSchema, Request, Result, ServerCapabilities, @@ -247,6 +248,11 @@ export function useConnection({ ProgressNotificationSchema, onNotification, ); + + client.setNotificationHandler( + ResourceUpdatedNotificationSchema, + onNotification, + ); } if (onStdErrNotification) { diff --git a/client/src/lib/notificationTypes.ts b/client/src/lib/notificationTypes.ts index 7aa6518..abd714b 100644 --- a/client/src/lib/notificationTypes.ts +++ b/client/src/lib/notificationTypes.ts @@ -1,6 +1,7 @@ import { NotificationSchema as BaseNotificationSchema, ClientNotificationSchema, + ServerNotificationSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; @@ -11,9 +12,9 @@ export const StdErrNotificationSchema = BaseNotificationSchema.extend({ }), }); -export const NotificationSchema = ClientNotificationSchema.or( - StdErrNotificationSchema, -); +export const NotificationSchema = ClientNotificationSchema + .or(StdErrNotificationSchema) + .or(ServerNotificationSchema); export type StdErrNotification = z.infer; export type Notification = z.infer; From a669272fda9f5b2af99bd9a0f2241e779fc947e1 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 8 Mar 2025 13:40:37 -0500 Subject: [PATCH 26/98] Track subscribed resources and show the appropriate subscribe or unsubscribe button on selected resource panel. If the server does not support resource subscriptions, do not show any subscription buttons. * In App.tsx - useState for resourceSubscriptions, setResourceSubscriptions a Set of type string. - in subscribeToResource() - only make the request to subscribe if the uri is not in the resourceSubscriptions set - in unsubscribeFromResource() - only make the request to unsubscribe if the uri is in the resourceSubscriptions set - in ResourceTab element, - pass a boolean resourceSubscriptionsSupported as serverCapabilities.resources.subscribe - pass resourceSubscriptions as a prop * In ResourcesTab.tsx - deconstruct resourceSubscriptions and resourceSubscriptionsSupported from props and add prop type - in selected resource panel - don't show subscribe or unsubscribe buttons unless resourceSubscriptionsSupported is true - only show subscribe button if selected resource uri is not in resourceSubscriptions set - only show unsubscribe button if selected resource uri is in resourceSubscriptions set - wrap buttons in a flex div that is - justified right - has a minimal gap between - 2/5 wide (just big enough to contain two buttons and leave the h3 text 3/5 of the row to render and not overflow. --- client/src/App.tsx | 47 +++++++++++++++++--------- client/src/components/ResourcesTab.tsx | 13 +++++-- package-lock.json | 4 +-- package.json | 4 +-- 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index f5b1f79..382ae03 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -128,6 +128,8 @@ const App = () => { const [selectedResource, setSelectedResource] = useState( null, ); + const [resourceSubscriptions, setResourceSubscriptions] = useState>(new Set()); + const [selectedPrompt, setSelectedPrompt] = useState(null); const [selectedTool, setSelectedTool] = useState(null); const [nextResourceCursor, setNextResourceCursor] = useState< @@ -310,26 +312,37 @@ const App = () => { const subscribeToResource = async (uri: string) => { - await makeRequest( - { - method: "resources/subscribe" as const, - params: { uri }, - }, - z.object({}), - "resources", - ); + if (!resourceSubscriptions.has(uri)) { + await makeRequest( + { + method: "resources/subscribe" as const, + params: { uri }, + }, + z.object({}), + "resources", + ); + const clone = new Set(resourceSubscriptions); + clone.add(uri); + setResourceSubscriptions(clone); + } + }; const unsubscribeFromResource = async (uri: string) => { - await makeRequest( - { - method: "resources/unsubscribe" as const, - params: { uri }, - }, - z.object({}), - "resources", - ); + if (resourceSubscriptions.has(uri)) { + await makeRequest( + { + method: "resources/unsubscribe" as const, + params: { uri }, + }, + z.object({}), + "resources", + ); + const clone = new Set(resourceSubscriptions); + clone.delete(uri); + setResourceSubscriptions(clone); + } }; @@ -510,6 +523,8 @@ const App = () => { clearError("resources"); setSelectedResource(resource); }} + resourceSubscriptionsSupported={serverCapabilities?.resources?.subscribe || false} + resourceSubscriptions={resourceSubscriptions} subscribeToResource={(uri) => { clearError("resources"); subscribeToResource(uri); diff --git a/client/src/components/ResourcesTab.tsx b/client/src/components/ResourcesTab.tsx index 9d94296..317ec85 100644 --- a/client/src/components/ResourcesTab.tsx +++ b/client/src/components/ResourcesTab.tsx @@ -26,6 +26,8 @@ const ResourcesTab = ({ readResource, selectedResource, setSelectedResource, + resourceSubscriptionsSupported, + resourceSubscriptions, subscribeToResource, unsubscribeFromResource, handleCompletion, @@ -54,6 +56,8 @@ const ResourcesTab = ({ nextCursor: ListResourcesResult["nextCursor"]; nextTemplateCursor: ListResourceTemplatesResult["nextCursor"]; error: string | null; + resourceSubscriptionsSupported: boolean; + resourceSubscriptions: Set; subscribeToResource: (uri: string) => void; unsubscribeFromResource: (uri: string) => void; }) => { @@ -168,14 +172,16 @@ const ResourcesTab = ({ : "Select a resource or template"} {selectedResource && ( - <> - + } + { resourceSubscriptionsSupported && resourceSubscriptions.has(selectedResource.uri) && + } - +
)}
diff --git a/package-lock.json b/package-lock.json index 550bb75..ed9bc96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,8 @@ "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "0.4.1", - "@modelcontextprotocol/inspector-server": "0.4.1", + "@modelcontextprotocol/inspector-client": "^0.5.1", + "@modelcontextprotocol/inspector-server": "^0.5.1", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", diff --git a/package.json b/package.json index 3de7ce4..b84fbd6 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { - "@modelcontextprotocol/inspector-client": "0.4.1", - "@modelcontextprotocol/inspector-server": "0.4.1", + "@modelcontextprotocol/inspector-client": "^0.5.1", + "@modelcontextprotocol/inspector-server": "^0.5.1", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", From 952bee2605e4acd25f2d5fc5c2263f81edfd2c20 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 8 Mar 2025 15:08:12 -0500 Subject: [PATCH 27/98] Fix prettier complaints --- client/src/App.tsx | 12 ++++---- client/src/components/ResourcesTab.tsx | 39 +++++++++++++++----------- client/src/lib/notificationTypes.ts | 6 ++-- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 382ae03..e902da9 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -128,7 +128,9 @@ const App = () => { const [selectedResource, setSelectedResource] = useState( null, ); - const [resourceSubscriptions, setResourceSubscriptions] = useState>(new Set()); + const [resourceSubscriptions, setResourceSubscriptions] = useState< + Set + >(new Set()); const [selectedPrompt, setSelectedPrompt] = useState(null); const [selectedTool, setSelectedTool] = useState(null); @@ -311,7 +313,6 @@ const App = () => { }; const subscribeToResource = async (uri: string) => { - if (!resourceSubscriptions.has(uri)) { await makeRequest( { @@ -325,11 +326,9 @@ const App = () => { clone.add(uri); setResourceSubscriptions(clone); } - }; const unsubscribeFromResource = async (uri: string) => { - if (resourceSubscriptions.has(uri)) { await makeRequest( { @@ -345,7 +344,6 @@ const App = () => { } }; - const listPrompts = async () => { const response = await makeRequest( { @@ -523,7 +521,9 @@ const App = () => { clearError("resources"); setSelectedResource(resource); }} - resourceSubscriptionsSupported={serverCapabilities?.resources?.subscribe || false} + resourceSubscriptionsSupported={ + serverCapabilities?.resources?.subscribe || false + } resourceSubscriptions={resourceSubscriptions} subscribeToResource={(uri) => { clearError("resources"); diff --git a/client/src/components/ResourcesTab.tsx b/client/src/components/ResourcesTab.tsx index 317ec85..f000840 100644 --- a/client/src/components/ResourcesTab.tsx +++ b/client/src/components/ResourcesTab.tsx @@ -173,23 +173,28 @@ const ResourcesTab = ({ {selectedResource && (
- { resourceSubscriptionsSupported && !resourceSubscriptions.has(selectedResource.uri) && - } - { resourceSubscriptionsSupported && resourceSubscriptions.has(selectedResource.uri) && - - } + {resourceSubscriptionsSupported && + !resourceSubscriptions.has(selectedResource.uri) && ( + + )} + {resourceSubscriptionsSupported && + resourceSubscriptions.has(selectedResource.uri) && ( + + )}
) : ( -
- - setSseUrl(e.target.value)} - className="font-mono" - /> -
+ <> +
+ + setSseUrl(e.target.value)} + className="font-mono" + /> +
+
+ + {showBearerToken && ( +
+ + setBearerToken(e.target.value)} + className="font-mono" + type="password" + /> +
+ )} +
+ )} {transportType === "stdio" && (
diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 75b5467..468e9ad 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -37,6 +37,7 @@ interface UseConnectionOptions { sseUrl: string; env: Record; proxyServerUrl: string; + bearerToken?: string; requestTimeout?: number; onNotification?: (notification: Notification) => void; onStdErrNotification?: (notification: Notification) => void; @@ -57,6 +58,7 @@ export function useConnection({ sseUrl, env, proxyServerUrl, + bearerToken, requestTimeout = DEFAULT_REQUEST_TIMEOUT_MSEC, onNotification, onStdErrNotification, @@ -228,9 +230,11 @@ export function useConnection({ // Inject auth manually instead of using SSEClientTransport, because we're // proxying through the inspector server first. const headers: HeadersInit = {}; - const tokens = await authProvider.tokens(); - if (tokens) { - headers["Authorization"] = `Bearer ${tokens.access_token}`; + + // Use manually provided bearer token if available, otherwise use OAuth tokens + const token = bearerToken || (await authProvider.tokens())?.access_token; + if (token) { + headers["Authorization"] = `Bearer ${token}`; } const clientTransport = new SSEClientTransport(backendUrl, { From f56961ac62eca78840d28991ce7d0a9a21a7bf53 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Tue, 11 Mar 2025 10:55:14 +0000 Subject: [PATCH 29/98] Bump version --- client/package.json | 4 ++-- package.json | 8 ++++---- server/package.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/package.json b/client/package.json index 1b7d4cb..c3a700d 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.5.1", + "version": "0.6.0", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -64,4 +64,4 @@ "typescript-eslint": "^8.7.0", "vite": "^5.4.8" } -} +} \ No newline at end of file diff --git a/package.json b/package.json index b84fbd6..5407d97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.5.1", + "version": "0.6.0", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -34,8 +34,8 @@ "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.5.1", - "@modelcontextprotocol/inspector-server": "^0.5.1", + "@modelcontextprotocol/inspector-client": "^0.6.0", + "@modelcontextprotocol/inspector-server": "^0.6.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -46,4 +46,4 @@ "@types/shell-quote": "^1.7.5", "prettier": "3.3.3" } -} +} \ No newline at end of file diff --git a/server/package.json b/server/package.json index e64557f..1e22e92 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.5.1", + "version": "0.6.0", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -33,4 +33,4 @@ "ws": "^8.18.0", "zod": "^3.23.8" } -} +} \ No newline at end of file From 0281e5f8219d6f6f73db0509d1a30d85df4b49f8 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Tue, 11 Mar 2025 10:56:53 +0000 Subject: [PATCH 30/98] Fix formatting --- client/package.json | 2 +- package.json | 2 +- server/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/package.json b/client/package.json index c3a700d..b1bd337 100644 --- a/client/package.json +++ b/client/package.json @@ -64,4 +64,4 @@ "typescript-eslint": "^8.7.0", "vite": "^5.4.8" } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 5407d97..3f40d2e 100644 --- a/package.json +++ b/package.json @@ -46,4 +46,4 @@ "@types/shell-quote": "^1.7.5", "prettier": "3.3.3" } -} \ No newline at end of file +} diff --git a/server/package.json b/server/package.json index 1e22e92..5d8839f 100644 --- a/server/package.json +++ b/server/package.json @@ -33,4 +33,4 @@ "ws": "^8.18.0", "zod": "^3.23.8" } -} \ No newline at end of file +} From 397a0f651f93e8d4b4f7cdc22e58cc59ca8ef1ec Mon Sep 17 00:00:00 2001 From: Abdelkader Boudih Date: Tue, 11 Mar 2025 13:33:45 +0000 Subject: [PATCH 31/98] feat: Fetch version from package.json in useConnection hook --- client/src/lib/hooks/useConnection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 4abd5c6..ea9e05a 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -26,6 +26,7 @@ import { SESSION_KEYS } from "../constants"; import { Notification, StdErrNotificationSchema } from "../notificationTypes"; import { auth } from "@modelcontextprotocol/sdk/client/auth.js"; import { authProvider } from "../auth"; +import packageJson from "../../../package.json"; const params = new URLSearchParams(window.location.search); const DEFAULT_REQUEST_TIMEOUT_MSEC = @@ -205,7 +206,7 @@ export function useConnection({ const client = new Client( { name: "mcp-inspector", - version: "0.0.1", + version: packageJson.version, }, { capabilities: { From e5ee00bf89ee44a57f78c1332d02fec4950f8f66 Mon Sep 17 00:00:00 2001 From: leoshimo <56844000+leoshimo@users.noreply.github.com> Date: Tue, 11 Mar 2025 12:59:28 -0700 Subject: [PATCH 32/98] fix: add dark mode support to SamplingTab JSON display (#181) --- client/src/components/SamplingTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/SamplingTab.tsx b/client/src/components/SamplingTab.tsx index d851880..5c45400 100644 --- a/client/src/components/SamplingTab.tsx +++ b/client/src/components/SamplingTab.tsx @@ -43,7 +43,7 @@ const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => {

Recent Requests

{pendingRequests.map((request) => (
-
+            
               {JSON.stringify(request.request, null, 2)}
             
From 04a90e8d892b4381ba1f50ee3c4323051ac8378c Mon Sep 17 00:00:00 2001 From: Ryan Rozich Date: Mon, 10 Mar 2025 07:50:02 -0500 Subject: [PATCH 33/98] Fix environment variable parsing to handle values with equals signs --- bin/cli.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 94348fb..c1ada65 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -27,9 +27,21 @@ async function main() { } if (parsingFlags && arg === "-e" && i + 1 < args.length) { - const [key, value] = args[++i].split("="); - if (key && value) { + // Parse environment variables passed with -e flag + // Format: -e KEY=VALUE + // Example: -e MY_VAR=my_value + // Handles env vars where VALUE can contain "=" signs (e.g., var1=sample=value) + const envVar = args[++i]; + const equalsIndex = envVar.indexOf("="); + + if (equalsIndex !== -1) { + // Split only at the first equals sign + const key = envVar.substring(0, equalsIndex); + const value = envVar.substring(equalsIndex + 1); envVars[key] = value; + } else { + // No equals sign found, use the whole string as key with empty value + envVars[envVar] = ""; } } else if (!command) { command = arg; @@ -113,4 +125,4 @@ main() .catch((e) => { console.error(e); process.exit(1); - }); + }); \ No newline at end of file From fe8b1ee88b7e8a02edefc73cfff7f6722a90284f Mon Sep 17 00:00:00 2001 From: Ryan Rozich Date: Tue, 11 Mar 2025 22:43:44 -0500 Subject: [PATCH 34/98] remove comments --- bin/cli.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index c1ada65..80d42b6 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -27,20 +27,14 @@ async function main() { } if (parsingFlags && arg === "-e" && i + 1 < args.length) { - // Parse environment variables passed with -e flag - // Format: -e KEY=VALUE - // Example: -e MY_VAR=my_value - // Handles env vars where VALUE can contain "=" signs (e.g., var1=sample=value) const envVar = args[++i]; const equalsIndex = envVar.indexOf("="); if (equalsIndex !== -1) { - // Split only at the first equals sign const key = envVar.substring(0, equalsIndex); const value = envVar.substring(equalsIndex + 1); envVars[key] = value; } else { - // No equals sign found, use the whole string as key with empty value envVars[envVar] = ""; } } else if (!command) { From 60c4645eafdfeff02e4642958e28824646e1700c Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 12 Mar 2025 08:20:11 -0700 Subject: [PATCH 35/98] Fix formatting --- bin/cli.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 80d42b6..8e0318d 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -29,7 +29,7 @@ async function main() { if (parsingFlags && arg === "-e" && i + 1 < args.length) { const envVar = args[++i]; const equalsIndex = envVar.indexOf("="); - + if (equalsIndex !== -1) { const key = envVar.substring(0, equalsIndex); const value = envVar.substring(equalsIndex + 1); @@ -119,4 +119,4 @@ main() .catch((e) => { console.error(e); process.exit(1); - }); \ No newline at end of file + }); From d70e6dc0e8a5d8ffb3a84bfdfa04131f7af2dfd8 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Thu, 13 Mar 2025 15:17:17 -0400 Subject: [PATCH 36/98] In useConnection.ts, - import LoggingMessageNotificationSchema - set onNotification as notification handler for LoggingMessageNotificationSchema --- client/src/lib/hooks/useConnection.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index ea9e05a..19b0e9d 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -10,6 +10,7 @@ import { ListRootsRequestSchema, ProgressNotificationSchema, ResourceUpdatedNotificationSchema, + LoggingMessageNotificationSchema, Request, Result, ServerCapabilities, @@ -258,6 +259,11 @@ export function useConnection({ ResourceUpdatedNotificationSchema, onNotification, ); + + client.setNotificationHandler( + LoggingMessageNotificationSchema, + onNotification, + ); } if (onStdErrNotification) { From fb667fd4d08e7bb118b60f2b40018baebc09ad48 Mon Sep 17 00:00:00 2001 From: Max Gerber <89937743+max-stytch@users.noreply.github.com> Date: Fri, 14 Mar 2025 16:16:31 -0700 Subject: [PATCH 37/98] fix: Prefer 127.0.0.1 over localhost --- bin/cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/cli.js b/bin/cli.js index 94348fb..115e118 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -96,7 +96,7 @@ async function main() { await Promise.any([server, client, delay(2 * 1000)]); const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`; console.log( - `\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} 🚀`, + `\n🔍 MCP Inspector is up and running at http://127.0.0.1:${CLIENT_PORT}${portParam} 🚀`, ); try { From cda3905e5a64ebd0c8c3072c852fc777b5d0fd80 Mon Sep 17 00:00:00 2001 From: Maxwell Gerber Date: Fri, 14 Mar 2025 16:33:39 -0700 Subject: [PATCH 38/98] fix: Update URL in CONTRIBUTING.md too --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ad5699..b225713 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ Thanks for your interest in contributing! This guide explains how to get involve 1. Fork the repository and clone it locally 2. Install dependencies with `npm install` 3. Run `npm run dev` to start both client and server in development mode -4. Use the web UI at http://localhost:5173 to interact with the inspector +4. Use the web UI at http://127.0.0.1:5173 to interact with the inspector ## Development Process & Pull Requests From 090b7efdea0162dcaeeef29ae21ab6cf996a14b2 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Tue, 11 Mar 2025 10:36:21 +0800 Subject: [PATCH 39/98] add sse accept header --- server/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/index.ts b/server/src/index.ts index 2874b45..aa8fa9a 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -66,7 +66,9 @@ const createTransport = async (req: express.Request) => { return transport; } else if (transportType === "sse") { const url = query.url as string; - const headers: HeadersInit = {}; + const headers: HeadersInit = { + 'Accept': 'text/event-stream', + }; for (const key of SSE_HEADERS_PASSTHROUGH) { if (req.headers[key] === undefined) { continue; From aeaf32fa45230178ff2a88097110e6dbd9c81075 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Sat, 15 Mar 2025 12:51:44 +0800 Subject: [PATCH 40/98] fix --- server/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/index.ts b/server/src/index.ts index aa8fa9a..2a4fe65 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -67,7 +67,7 @@ const createTransport = async (req: express.Request) => { } else if (transportType === "sse") { const url = query.url as string; const headers: HeadersInit = { - 'Accept': 'text/event-stream', + Accept: "text/event-stream", }; for (const key of SSE_HEADERS_PASSTHROUGH) { if (req.headers[key] === undefined) { From 51049522399322971edf2390471bdc2763a97c01 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 15 Mar 2025 16:37:10 -0400 Subject: [PATCH 41/98] Add log level setting in UI * This fixes #188 * In App.tsx - import LoggingLevel from sdk - add [logLevel, setLogLevel] useState with value of type LoggingLevel initialized to "debug" - add useEffect that stores the new logLevel in localStorage as "logLevel" - added sendLogLevelRequest function that takes a level argument of type LoggingLevel and sends the appropriate request. It calls setLogLevel when done, to update the local UI - pass logLevel and sendLogLevelRequest to Sidebar component as props * In Sidebar.tsx - Import LoggingLevel and LoggingLevelSchema from sdk - add props and prop types for logLevel and sendLogLevelRequest and loggingSupported - add Select component populated with the enum values of LoggingLevelSchema, shown only if loggingSupported is true and connectionStatus is "connected" * --- client/src/App.tsx | 18 +++++++++++++++++ client/src/components/Sidebar.tsx | 32 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/client/src/App.tsx b/client/src/App.tsx index 5650954..0a699b1 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -15,6 +15,7 @@ import { Root, ServerNotification, Tool, + LoggingLevel, } from "@modelcontextprotocol/sdk/types.js"; import React, { Suspense, useEffect, useRef, useState } from "react"; import { useConnection } from "./lib/hooks/useConnection"; @@ -91,6 +92,7 @@ const App = () => { (localStorage.getItem("lastTransportType") as "stdio" | "sse") || "stdio" ); }); + const [logLevel, setLogLevel] = useState("debug"); const [notifications, setNotifications] = useState([]); const [stdErrNotifications, setStdErrNotifications] = useState< StdErrNotification[] @@ -412,6 +414,17 @@ const App = () => { await sendNotification({ method: "notifications/roots/list_changed" }); }; + const sendLogLevelRequest = async (level: LoggingLevel) => { + await makeRequest( + { + method: "logging/setLevel" as const, + params: { level }, + }, + z.object({}), + ); + setLogLevel(level); + }; + return (
{ setBearerToken={setBearerToken} onConnect={connectMcpServer} stdErrNotifications={stdErrNotifications} + logLevel={logLevel} + sendLogLevelRequest={sendLogLevelRequest} + loggingSupported={ + !!serverCapabilities?.logging || false + } />
diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 48c6ff2..aa930e5 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -19,6 +19,10 @@ import { SelectValue, } from "@/components/ui/select"; import { StdErrNotification } from "@/lib/notificationTypes"; +import { + LoggingLevel, + LoggingLevelSchema, +} from "@modelcontextprotocol/sdk/types.js"; import useTheme from "../lib/useTheme"; import { version } from "../../../package.json"; @@ -39,6 +43,9 @@ interface SidebarProps { setBearerToken: (token: string) => void; onConnect: () => void; stdErrNotifications: StdErrNotification[]; + logLevel: LoggingLevel; + sendLogLevelRequest: (level: LoggingLevel) => void; + loggingSupported: boolean; } const Sidebar = ({ @@ -57,6 +64,9 @@ const Sidebar = ({ setBearerToken, onConnect, stdErrNotifications, + logLevel, + sendLogLevelRequest, + loggingSupported, }: SidebarProps) => { const [theme, setTheme] = useTheme(); const [showEnvVars, setShowEnvVars] = useState(false); @@ -290,6 +300,28 @@ const Sidebar = ({ : "Disconnected"}
+ + {loggingSupported && connectionStatus === "connected" && ( +
+ + +
+ )} + {stdErrNotifications.length > 0 && ( <>
From d8b5bdb6135d70dbd18a5fa5ad33e870c9187da3 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 15 Mar 2025 17:03:22 -0400 Subject: [PATCH 42/98] Run prettier --- client/src/App.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 0a699b1..1ae5250 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -445,9 +445,7 @@ const App = () => { stdErrNotifications={stdErrNotifications} logLevel={logLevel} sendLogLevelRequest={sendLogLevelRequest} - loggingSupported={ - !!serverCapabilities?.logging || false - } + loggingSupported={!!serverCapabilities?.logging || false} />
From a3740c4798ad4fcbb7e661dcb124d0a5fb270b62 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:24:34 -0700 Subject: [PATCH 43/98] Remove unneeded DynamicJsonForm.tsx --- client/src/utils/__mocks__/DynamicJsonForm.ts | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 client/src/utils/__mocks__/DynamicJsonForm.ts diff --git a/client/src/utils/__mocks__/DynamicJsonForm.ts b/client/src/utils/__mocks__/DynamicJsonForm.ts deleted file mode 100644 index 83a98e8..0000000 --- a/client/src/utils/__mocks__/DynamicJsonForm.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Mock for DynamicJsonForm that only exports the types needed for tests -export type JsonValue = - | string - | number - | boolean - | null - | JsonValue[] - | { [key: string]: JsonValue }; - -export type JsonSchemaType = { - type: "string" | "number" | "integer" | "boolean" | "array" | "object"; - description?: string; - properties?: Record; - items?: JsonSchemaType; -}; - -// Default export is not used in the tests -export default {}; From 7c4ed6abca9a77f8e838c0a5b134295b6954e11b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:24:48 -0700 Subject: [PATCH 44/98] Use working-directory instead of cd to client --- .github/workflows/main.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fb0aa6d..1f13444 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,9 +27,8 @@ jobs: - run: npm install --no-package-lock - name: Run client tests - run: | - cd client - npm test + working-directory: ./client + run: npm test - run: npm run build From e1b015e40d6c0c1628950f0d23bbe5da980541a8 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:28:07 -0700 Subject: [PATCH 45/98] Add comments explaining extra parsing logic --- client/src/components/DynamicJsonForm.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 430b7ba..7da1762 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -46,15 +46,17 @@ const DynamicJsonForm = ({ }: DynamicJsonFormProps) => { const [isJsonMode, setIsJsonMode] = useState(false); const [jsonError, setJsonError] = useState(); - // Add state for storing raw JSON value + // Store the raw JSON string to allow immediate feedback during typing + // while deferring parsing until the user stops typing const [rawJsonValue, setRawJsonValue] = useState( JSON.stringify(value ?? generateDefaultValue(schema), null, 2), ); - // Create a ref to store the timeout ID + // Use a ref to manage debouncing timeouts to avoid parsing JSON + // on every keystroke which would be inefficient and error-prone const timeoutRef = useRef>(); - // Create a debounced function to update parent state + // Debounce JSON parsing and parent updates to handle typing gracefully const debouncedUpdateParent = useCallback( (jsonString: string) => { // Clear any existing timeout @@ -146,6 +148,8 @@ const DynamicJsonForm = ({ value={(currentValue as string) ?? ""} onChange={(e) => { const val = e.target.value; + // Allow clearing non-required fields by setting undefined + // This preserves the distinction between empty string and unset if (!val && !propSchema.required) { handleFieldChange(path, undefined); } else { @@ -163,6 +167,8 @@ const DynamicJsonForm = ({ value={(currentValue as number)?.toString() ?? ""} onChange={(e) => { const val = e.target.value; + // Allow clearing non-required number fields + // This preserves the distinction between 0 and unset if (!val && !propSchema.required) { handleFieldChange(path, undefined); } else { @@ -184,10 +190,13 @@ const DynamicJsonForm = ({ value={(currentValue as number)?.toString() ?? ""} onChange={(e) => { const val = e.target.value; + // Allow clearing non-required integer fields + // This preserves the distinction between 0 and unset if (!val && !propSchema.required) { handleFieldChange(path, undefined); } else { const num = Number(val); + // Only update if it's a valid integer if (!isNaN(num) && Number.isInteger(num)) { handleFieldChange(path, num); } From 50a65d0c7a16040611d8e5c601a26ad5ef9b098e Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:29:59 -0700 Subject: [PATCH 46/98] Use generateDefaultValue for object and array defaults --- client/src/components/ToolsTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 810242f..30764b2 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -6,6 +6,7 @@ import { Label } from "@/components/ui/label"; import { TabsContent } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; import DynamicJsonForm, { JsonSchemaType, JsonValue } from "./DynamicJsonForm"; +import { generateDefaultValue } from "@/utils/schemaUtils"; import { ListToolsResult, Tool, @@ -215,8 +216,7 @@ const ToolsTab = ({ items: prop.items, }} value={ - (params[key] as JsonValue) ?? - (prop.type === "array" ? [] : {}) + (params[key] as JsonValue) ?? generateDefaultValue(prop) } onChange={(newValue: JsonValue) => { setParams({ From cae7c763582aae784132c381aaa158894b20d2ab Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:31:49 -0700 Subject: [PATCH 47/98] Fix formatting --- client/src/components/ToolsTab.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 30764b2..ee9a4f9 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -216,7 +216,8 @@ const ToolsTab = ({ items: prop.items, }} value={ - (params[key] as JsonValue) ?? generateDefaultValue(prop) + (params[key] as JsonValue) ?? + generateDefaultValue(prop) } onChange={(newValue: JsonValue) => { setParams({ From 28978ea24f8d5e9db2e66c02a500b5fc9a2e0759 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:42:16 -0700 Subject: [PATCH 48/98] Update package lock after re-running npm install --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9b2854..21c9c30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.5.1", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.5.1", + "version": "0.6.0", "license": "MIT", "workspaces": [ "client", "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.5.1", - "@modelcontextprotocol/inspector-server": "^0.5.1", + "@modelcontextprotocol/inspector-client": "^0.6.0", + "@modelcontextprotocol/inspector-server": "^0.6.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -31,7 +31,7 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.5.1", + "version": "0.6.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", @@ -11298,7 +11298,7 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.5.1", + "version": "0.6.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", From dd460bd877eb9df356ce02a35b9e8cc2ef5222ef Mon Sep 17 00:00:00 2001 From: Nathan Arseneau Date: Mon, 17 Mar 2025 02:44:59 -0400 Subject: [PATCH 49/98] Refactor notification handling to include all notifications --- client/src/lib/hooks/useConnection.ts | 23 ++++++----------------- client/src/lib/notificationTypes.ts | 4 +++- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 19b0e9d..d432c28 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -8,9 +8,6 @@ import { ClientRequest, CreateMessageRequestSchema, ListRootsRequestSchema, - ProgressNotificationSchema, - ResourceUpdatedNotificationSchema, - LoggingMessageNotificationSchema, Request, Result, ServerCapabilities, @@ -250,20 +247,12 @@ export function useConnection({ }); if (onNotification) { - client.setNotificationHandler( - ProgressNotificationSchema, - onNotification, - ); - - client.setNotificationHandler( - ResourceUpdatedNotificationSchema, - onNotification, - ); - - client.setNotificationHandler( - LoggingMessageNotificationSchema, - onNotification, - ); + client.fallbackNotificationHandler = ( + notification: Notification, + ): Promise => { + onNotification(notification); + return Promise.resolve(); + }; } if (onStdErrNotification) { diff --git a/client/src/lib/notificationTypes.ts b/client/src/lib/notificationTypes.ts index 82c1fd8..8627ccc 100644 --- a/client/src/lib/notificationTypes.ts +++ b/client/src/lib/notificationTypes.ts @@ -14,7 +14,9 @@ export const StdErrNotificationSchema = BaseNotificationSchema.extend({ export const NotificationSchema = ClientNotificationSchema.or( StdErrNotificationSchema, -).or(ServerNotificationSchema); +) + .or(ServerNotificationSchema) + .or(BaseNotificationSchema); export type StdErrNotification = z.infer; export type Notification = z.infer; From 7ddba51b36c4eac7595903dd68c0da84a1636181 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 17 Mar 2025 06:36:35 -0700 Subject: [PATCH 50/98] Generate empty objects and arrays for non required object and array fields --- client/src/utils/__tests__/schemaUtils.test.ts | 14 ++++++++++++++ client/src/utils/schemaUtils.ts | 2 ++ 2 files changed, 16 insertions(+) diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index cd9c288..00187cd 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -37,6 +37,20 @@ describe("generateDefaultValue", () => { ); }); + test("generates empty array for non-required array", () => { + expect(generateDefaultValue({ type: "array", required: false })).toEqual([]); + }); + + test("generates empty object for non-required object", () => { + expect(generateDefaultValue({ type: "object", required: false })).toEqual({}); + }); + + test("generates null for non-required primitive types", () => { + expect(generateDefaultValue({ type: "string", required: false })).toBe(null); + expect(generateDefaultValue({ type: "number", required: false })).toBe(null); + expect(generateDefaultValue({ type: "boolean", required: false })).toBe(null); + }); + test("generates object with properties", () => { const schema: JsonSchemaType = { type: "object", diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index 88a9b53..819ac99 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -13,6 +13,8 @@ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { } if (!schema.required) { + if (schema.type === "array") return []; + if (schema.type === "object") return {}; return null; } From a85d5e70505cd5e6f519d170244d13983745ffca Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 17 Mar 2025 07:56:23 -0700 Subject: [PATCH 51/98] Fix formatting --- .../src/utils/__tests__/schemaUtils.test.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index 00187cd..e164d0d 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -38,17 +38,27 @@ describe("generateDefaultValue", () => { }); test("generates empty array for non-required array", () => { - expect(generateDefaultValue({ type: "array", required: false })).toEqual([]); + expect(generateDefaultValue({ type: "array", required: false })).toEqual( + [], + ); }); test("generates empty object for non-required object", () => { - expect(generateDefaultValue({ type: "object", required: false })).toEqual({}); + expect(generateDefaultValue({ type: "object", required: false })).toEqual( + {}, + ); }); test("generates null for non-required primitive types", () => { - expect(generateDefaultValue({ type: "string", required: false })).toBe(null); - expect(generateDefaultValue({ type: "number", required: false })).toBe(null); - expect(generateDefaultValue({ type: "boolean", required: false })).toBe(null); + expect(generateDefaultValue({ type: "string", required: false })).toBe( + null, + ); + expect(generateDefaultValue({ type: "number", required: false })).toBe( + null, + ); + expect(generateDefaultValue({ type: "boolean", required: false })).toBe( + null, + ); }); test("generates object with properties", () => { From c463dc58c2cd183b96d5470d92c929442aade203 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 18 Mar 2025 06:23:25 -0700 Subject: [PATCH 52/98] Simplify check for defaults and add another test --- client/src/utils/__tests__/schemaUtils.test.ts | 6 ++++++ client/src/utils/schemaUtils.ts | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index e164d0d..6462e12 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -108,6 +108,12 @@ describe("generateDefaultValue", () => { }, }); }); + + test("uses schema default value when provided", () => { + expect(generateDefaultValue({ type: "string", default: "test" })).toBe( + "test", + ); + }); }); describe("formatFieldLabel", () => { diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index 819ac99..686030f 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -8,8 +8,7 @@ import { JsonObject } from "./jsonPathUtils"; */ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { if ("default" in schema) { - // Ensure we don't return undefined even if schema.default is undefined - return schema.default === undefined ? null : schema.default; + return schema.default; } if (!schema.required) { From 536b7e0a99170b402fec55946b157642bb6f5e74 Mon Sep 17 00:00:00 2001 From: Maxwell Gerber Date: Tue, 18 Mar 2025 09:29:48 -0700 Subject: [PATCH 53/98] fix: Update vite host --- client/vite.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/vite.config.ts b/client/vite.config.ts index b3d0f45..c971d58 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -5,7 +5,9 @@ import { defineConfig } from "vite"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], - server: {}, + server: { + host: "127.0.0.1", + }, resolve: { alias: { "@": path.resolve(__dirname, "./src"), From 27b54104c1523f06e6fa3a9ad54a249a74f9f6b2 Mon Sep 17 00:00:00 2001 From: Nathan Arseneau Date: Wed, 19 Mar 2025 19:51:01 -0400 Subject: [PATCH 54/98] Add support for additional notification schemas in useConnection hook --- client/src/lib/hooks/useConnection.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index d432c28..f0379eb 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -8,6 +8,9 @@ import { ClientRequest, CreateMessageRequestSchema, ListRootsRequestSchema, + ProgressNotificationSchema, + ResourceUpdatedNotificationSchema, + LoggingMessageNotificationSchema, Request, Result, ServerCapabilities, @@ -16,6 +19,10 @@ import { McpError, CompleteResultSchema, ErrorCode, + CancelledNotificationSchema, + ResourceListChangedNotificationSchema, + ToolListChangedNotificationSchema, + PromptListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js"; import { useState } from "react"; import { toast } from "react-toastify"; @@ -247,6 +254,18 @@ export function useConnection({ }); if (onNotification) { + [ + CancelledNotificationSchema, + ProgressNotificationSchema, + LoggingMessageNotificationSchema, + ResourceUpdatedNotificationSchema, + ResourceListChangedNotificationSchema, + ToolListChangedNotificationSchema, + PromptListChangedNotificationSchema, + ].forEach((notificationSchema) => { + client.setNotificationHandler(notificationSchema, onNotification); + }); + client.fallbackNotificationHandler = ( notification: Notification, ): Promise => { From 029e482e05a57f5610662d080c5aba59f2dd762c Mon Sep 17 00:00:00 2001 From: Nathan Arseneau Date: Wed, 19 Mar 2025 20:15:14 -0400 Subject: [PATCH 55/98] fix: set default value for input fields in ToolsTab component --- client/src/components/ToolsTab.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 777db80..dcfae44 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -229,6 +229,7 @@ const ToolsTab = ({ id={key} name={key} placeholder={prop.description} + value={(params[key] as string) ?? ""} onChange={(e) => setParams({ ...params, From ce81fb976be3d34187d01d2fac190376af42e52d Mon Sep 17 00:00:00 2001 From: Shinya Fujino Date: Thu, 20 Mar 2025 22:18:44 +0900 Subject: [PATCH 56/98] Restructure link buttons in sidebar to respect theme --- client/src/components/Sidebar.tsx | 51 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 48c6ff2..74b0fac 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -331,36 +331,37 @@ const Sidebar = ({
From 4fdbcee7060f748fe68271414bd9422ee4674fda Mon Sep 17 00:00:00 2001 From: jazminliu Date: Thu, 20 Mar 2025 22:04:59 +0800 Subject: [PATCH 57/98] Update Vite configuration to enable host access and fix proxy server URL to use the current hostname. --- client/src/App.tsx | 2 +- client/vite.config.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 5650954..fde9d6a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -47,7 +47,7 @@ import ToolsTab from "./components/ToolsTab"; const params = new URLSearchParams(window.location.search); const PROXY_PORT = params.get("proxyPort") ?? "3000"; -const PROXY_SERVER_URL = `http://localhost:${PROXY_PORT}`; +const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`; const App = () => { // Handle OAuth callback route diff --git a/client/vite.config.ts b/client/vite.config.ts index b3d0f45..fa817c7 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -5,7 +5,9 @@ import { defineConfig } from "vite"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], - server: {}, + server: { + host: true, + }, resolve: { alias: { "@": path.resolve(__dirname, "./src"), From dcbd1dad410dc8e6abb6e98cda17688a6c64ce4f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 21 Mar 2025 06:54:46 -0700 Subject: [PATCH 58/98] Bump prismjs from 1.29.0 to 1.30.0 to address --- client/package.json | 2 +- package-lock.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/package.json b/client/package.json index 0471765..700bbee 100644 --- a/client/package.json +++ b/client/package.json @@ -38,7 +38,7 @@ "cmdk": "^1.0.4", "lucide-react": "^0.447.0", "pkce-challenge": "^4.1.0", - "prismjs": "^1.29.0", + "prismjs": "^1.30.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-simple-code-editor": "^0.14.1", diff --git a/package-lock.json b/package-lock.json index 21c9c30..1db7ef2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "cmdk": "^1.0.4", "lucide-react": "^0.447.0", "pkce-challenge": "^4.1.0", - "prismjs": "^1.29.0", + "prismjs": "^1.30.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-simple-code-editor": "^0.14.1", @@ -8841,9 +8841,9 @@ } }, "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", "license": "MIT", "engines": { "node": ">=6" From 3488bdb613fa8d0a5f7aed01ec0911f57c3b8805 Mon Sep 17 00:00:00 2001 From: Abdelkader Boudih Date: Tue, 18 Mar 2025 10:07:43 +0000 Subject: [PATCH 59/98] feat: Add utility function to escape Unicode characters in tool results --- client/src/components/ToolsTab.tsx | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index ee9a4f9..ff4044f 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -8,15 +8,31 @@ import { Textarea } from "@/components/ui/textarea"; import DynamicJsonForm, { JsonSchemaType, JsonValue } from "./DynamicJsonForm"; import { generateDefaultValue } from "@/utils/schemaUtils"; import { + CallToolResultSchema, + CompatibilityCallToolResult, ListToolsResult, Tool, - CallToolResultSchema, } from "@modelcontextprotocol/sdk/types.js"; import { AlertCircle, Send } from "lucide-react"; import { useEffect, useState } from "react"; import ListPane from "./ListPane"; -import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js"; +// Utility function to escape Unicode characters +function escapeUnicode(obj: any): string { + return JSON.stringify( + obj, + (_key: string, value) => { + if (typeof value === "string") { + // Replace non-ASCII characters with their Unicode escape sequences + return value.replace(/[^\0-\x7F]/g, (char) => { + return "\\u" + ("0000" + char.charCodeAt(0).toString(16)).slice(-4); + }); + } + return value; + }, + 2, + ); +} const ToolsTab = ({ tools, @@ -54,7 +70,7 @@ const ToolsTab = ({ <>

Invalid Tool Result:

-              {JSON.stringify(toolResult, null, 2)}
+              {escapeUnicode(toolResult)}
             

Errors:

{parsedResult.error.errors.map((error, idx) => ( @@ -62,7 +78,7 @@ const ToolsTab = ({ key={idx} className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64" > - {JSON.stringify(error, null, 2)} + {escapeUnicode(error)}
))} @@ -101,7 +117,7 @@ const ToolsTab = ({ ) : (
-                    {JSON.stringify(item.resource, null, 2)}
+                    {escapeUnicode(item.resource)}
                   
))}
@@ -113,7 +129,7 @@ const ToolsTab = ({ <>

Tool Result (Legacy):

-            {JSON.stringify(toolResult.toolResult, null, 2)}
+            {escapeUnicode(toolResult.toolResult)}
           
); From 4a23585066c54bd869aa36339df3c9fc4d60578d Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Sat, 22 Mar 2025 20:32:47 +0530 Subject: [PATCH 60/98] Add support in UI to configure request timeout --- client/eslint.config.js | 1 + client/src/App.tsx | 15 +++++ client/src/components/Sidebar.tsx | 86 +++++++++++++++++++++++++++ client/src/lib/constants.ts | 10 ++++ client/src/lib/hooks/useConnection.ts | 8 +-- 5 files changed, 115 insertions(+), 5 deletions(-) diff --git a/client/eslint.config.js b/client/eslint.config.js index 79a552e..ba28812 100644 --- a/client/eslint.config.js +++ b/client/eslint.config.js @@ -19,6 +19,7 @@ export default tseslint.config( }, rules: { ...reactHooks.configs.recommended.rules, + "react-hooks/rules-of-hooks": "off", // Disable hooks dependency order checking "react-refresh/only-export-components": [ "warn", { allowConstantExport: true }, diff --git a/client/src/App.tsx b/client/src/App.tsx index 1ae5250..f88a8e3 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -45,10 +45,13 @@ import RootsTab from "./components/RootsTab"; import SamplingTab, { PendingRequest } from "./components/SamplingTab"; import Sidebar from "./components/Sidebar"; import ToolsTab from "./components/ToolsTab"; +import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants"; +import { InspectorConfig } from "./lib/configurationTypes"; const params = new URLSearchParams(window.location.search); const PROXY_PORT = params.get("proxyPort") ?? "3000"; const PROXY_SERVER_URL = `http://localhost:${PROXY_PORT}`; +const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1"; const App = () => { // Handle OAuth callback route @@ -99,6 +102,11 @@ const App = () => { >([]); const [roots, setRoots] = useState([]); const [env, setEnv] = useState>({}); + + const [config, setConfig] = useState(() => { + const savedConfig = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY); + return savedConfig ? JSON.parse(savedConfig) : DEFAULT_INSPECTOR_CONFIG; + }); const [bearerToken, setBearerToken] = useState(() => { return localStorage.getItem("lastBearerToken") || ""; }); @@ -171,6 +179,7 @@ const App = () => { env, bearerToken, proxyServerUrl: PROXY_SERVER_URL, + requestTimeout: config.MCP_SERVER_REQUEST_TIMEOUT.value as number, onNotification: (notification) => { setNotifications((prev) => [...prev, notification as ServerNotification]); }, @@ -209,6 +218,10 @@ const App = () => { localStorage.setItem("lastBearerToken", bearerToken); }, [bearerToken]); + useEffect(() => { + localStorage.setItem(CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config)); + }, [config]); + // Auto-connect if serverUrl is provided in URL params (e.g. after OAuth callback) useEffect(() => { const serverUrl = params.get("serverUrl"); @@ -439,6 +452,8 @@ const App = () => { setSseUrl={setSseUrl} env={env} setEnv={setEnv} + config={config} + setConfig={setConfig} bearerToken={bearerToken} setBearerToken={setBearerToken} onConnect={connectMcpServer} diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index aa930e5..150ff13 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -8,6 +8,7 @@ import { Github, Eye, EyeOff, + Settings, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -23,6 +24,7 @@ import { LoggingLevel, LoggingLevelSchema, } from "@modelcontextprotocol/sdk/types.js"; +import { InspectorConfig } from "@/lib/configurationTypes"; import useTheme from "../lib/useTheme"; import { version } from "../../../package.json"; @@ -46,6 +48,8 @@ interface SidebarProps { logLevel: LoggingLevel; sendLogLevelRequest: (level: LoggingLevel) => void; loggingSupported: boolean; + config: InspectorConfig; + setConfig: (config: InspectorConfig) => void; } const Sidebar = ({ @@ -67,10 +71,13 @@ const Sidebar = ({ logLevel, sendLogLevelRequest, loggingSupported, + config, + setConfig, }: SidebarProps) => { const [theme, setTheme] = useTheme(); const [showEnvVars, setShowEnvVars] = useState(false); const [showBearerToken, setShowBearerToken] = useState(false); + const [showConfig, setShowConfig] = useState(false); const [shownEnvVars, setShownEnvVars] = useState>(new Set()); return ( @@ -276,6 +283,85 @@ const Sidebar = ({
)} + {/* Configuration */} +
+ + {showConfig && ( +
+ {Object.entries(config).map(([key, configItem]) => { + const configKey = key as keyof InspectorConfig; + return ( +
+ + {typeof configItem.value === "number" ? ( + { + const newConfig = { ...config }; + newConfig[configKey] = { + ...configItem, + value: Number(e.target.value), + }; + setConfig(newConfig); + }} + className="font-mono" + /> + ) : typeof configItem.value === "boolean" ? ( + + ) : ( + { + const newConfig = { ...config }; + newConfig[configKey] = { + ...configItem, + value: e.target.value, + }; + setConfig(newConfig); + }} + className="font-mono" + /> + )} +
+ ); + })} +
+ )} +
+
-                          {JSON.stringify(JSON.parse(request.request), null, 2)}
+                          
                         
{request.response && ( @@ -92,11 +93,7 @@ const HistoryAndNotifications = ({
-                            {JSON.stringify(
-                              JSON.parse(request.response),
-                              null,
-                              2,
-                            )}
+                            
                           
)} @@ -147,7 +144,9 @@ const HistoryAndNotifications = ({
-                        {JSON.stringify(notification, null, 2)}
+                        
                       
)} diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx new file mode 100644 index 0000000..21d65d8 --- /dev/null +++ b/client/src/components/JsonView.tsx @@ -0,0 +1,210 @@ +import { useState, memo } from "react"; +import { JsonValue } from "./DynamicJsonForm"; + +interface JsonViewProps { + data: JsonValue; + name?: string; + initialExpandDepth?: number; +} + +function tryParseJson(str: string): { success: boolean; data: JsonValue } { + const trimmed = str.trim(); + if ( + !(trimmed.startsWith("{") && trimmed.endsWith("}")) && + !(trimmed.startsWith("[") && trimmed.endsWith("]")) + ) { + return { success: false, data: str }; + } + try { + return { success: true, data: JSON.parse(str) }; + } catch { + return { success: false, data: str }; + } +} + +const JsonView = memo( + ({ data, name, initialExpandDepth = 2 }: JsonViewProps) => { + const normalizedData = + typeof data === "string" + ? tryParseJson(data).success + ? tryParseJson(data).data + : data + : data; + + return ( +
+ +
+ ); + }, +); + +JsonView.displayName = "JsonView"; + +interface JsonNodeProps { + data: JsonValue; + name?: string; + depth: number; + initialExpandDepth: number; +} + +const JsonNode = memo( + ({ data, name, depth = 0, initialExpandDepth }: JsonNodeProps) => { + const [isExpanded, setIsExpanded] = useState(depth < initialExpandDepth); + + const getDataType = (value: JsonValue): string => { + if (Array.isArray(value)) return "array"; + if (value === null) return "null"; + return typeof value; + }; + + const dataType = getDataType(data); + + const typeStyleMap: Record = { + number: "text-blue-600", + boolean: "text-amber-600", + null: "text-purple-600", + undefined: "text-gray-600", + string: "text-green-600", + default: "text-gray-700", + }; + + const renderCollapsible = (isArray: boolean) => { + const items = isArray + ? (data as JsonValue[]) + : Object.entries(data as Record); + const itemCount = items.length; + const isEmpty = itemCount === 0; + + const symbolMap = { + open: isArray ? "[" : "{", + close: isArray ? "]" : "}", + collapsed: isArray ? "[ ... ]" : "{ ... }", + empty: isArray ? "[]" : "{}", + }; + + if (isEmpty) { + return ( +
+ {name && {name}:} + {symbolMap.empty} +
+ ); + } + + return ( +
+
setIsExpanded(!isExpanded)} + > + {name && ( + + {name}: + + )} + {isExpanded ? ( + + {symbolMap.open} + + ) : ( + <> + + {symbolMap.collapsed} + + + {itemCount} {itemCount === 1 ? "item" : "items"} + + + )} +
+ {isExpanded && ( + <> +
+ {isArray + ? (items as JsonValue[]).map((item, index) => ( +
+ +
+ )) + : (items as [string, JsonValue][]).map(([key, value]) => ( +
+ +
+ ))} +
+
{symbolMap.close}
+ + )} +
+ ); + }; + + const renderString = (value: string) => { + const maxLength = 100; + const isTooLong = value.length > maxLength; + + if (!isTooLong) { + return ( +
+ {name && {name}:} + "{value}" +
+ ); + } + + return ( +
+ {name && ( + + {name}: + + )} + setIsExpanded(!isExpanded)} + title={isExpanded ? "클릭하여 축소" : "클릭하여 전체 보기"} + > + {isExpanded ? `"${value}"` : `"${value.slice(0, maxLength)}..."`} + +
+ ); + }; + + switch (dataType) { + case "object": + case "array": + return renderCollapsible(dataType === "array"); + case "string": + return renderString(data as string); + default: + return ( +
+ {name && {name}:} + + {data === null ? "null" : String(data)} + +
+ ); + } + }, +); + +JsonNode.displayName = "JsonNode"; + +export default JsonView; diff --git a/client/src/components/ResourcesTab.tsx b/client/src/components/ResourcesTab.tsx index f000840..bc3e39a 100644 --- a/client/src/components/ResourcesTab.tsx +++ b/client/src/components/ResourcesTab.tsx @@ -15,6 +15,7 @@ import { AlertCircle, ChevronRight, FileText, RefreshCw } from "lucide-react"; import ListPane from "./ListPane"; import { useEffect, useState } from "react"; import { useCompletionState } from "@/lib/hooks/useCompletionState"; +import JsonView from "./JsonView"; const ResourcesTab = ({ resources, @@ -215,7 +216,7 @@ const ResourcesTab = ({ ) : selectedResource ? (
-              {resourceContent}
+              
             
) : selectedTemplate ? (
diff --git a/client/src/components/SamplingTab.tsx b/client/src/components/SamplingTab.tsx index 5c45400..d14d4c8 100644 --- a/client/src/components/SamplingTab.tsx +++ b/client/src/components/SamplingTab.tsx @@ -5,6 +5,7 @@ import { CreateMessageRequest, CreateMessageResult, } from "@modelcontextprotocol/sdk/types.js"; +import JsonView from "./JsonView"; export type PendingRequest = { id: number; @@ -44,7 +45,7 @@ const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => { {pendingRequests.map((request) => (
-              {JSON.stringify(request.request, null, 2)}
+              
             
diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 82ebdb0..345f7bd 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -17,6 +17,7 @@ import { AlertCircle, Send } from "lucide-react"; import { useEffect, useState } from "react"; import ListPane from "./ListPane"; import { escapeUnicode } from "@/utils/escapeUnicode"; +import JsonView from "./JsonView"; const ToolsTab = ({ tools, @@ -54,7 +55,7 @@ const ToolsTab = ({ <>

Invalid Tool Result:

-              {escapeUnicode(toolResult)}
+              
             

Errors:

{parsedResult.error.errors.map((error, idx) => ( @@ -62,7 +63,7 @@ const ToolsTab = ({ key={idx} className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64" > - {escapeUnicode(error)} + ))} @@ -80,7 +81,7 @@ const ToolsTab = ({
{item.type === "text" && (
-                  {item.text}
+                  
                 
)} {item.type === "image" && ( @@ -101,7 +102,7 @@ const ToolsTab = ({ ) : (
-                    {escapeUnicode(item.resource)}
+                    
                   
))}
@@ -113,7 +114,7 @@ const ToolsTab = ({ <>

Tool Result (Legacy):

-            {escapeUnicode(toolResult.toolResult)}
+            
           
); From f2f209dbd3ded70d76a181557a1c598a6721dfe5 Mon Sep 17 00:00:00 2001 From: Mark Anthony Cianfrani Date: Sat, 22 Mar 2025 12:52:56 -0400 Subject: [PATCH 79/98] fix(sidebar): maintain order when changing values --- client/src/components/Sidebar.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index aa930e5..4f60a77 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -187,9 +187,17 @@ const Sidebar = ({ value={key} onChange={(e) => { const newKey = e.target.value; - const newEnv = { ...env }; - delete newEnv[key]; - newEnv[newKey] = value; + const newEnv = Object.entries(env).reduce( + (acc, [k, v]) => { + if (k === key) { + acc[newKey] = value; + } else { + acc[k] = v; + } + return acc; + }, + {} as Record, + ); setEnv(newEnv); setShownEnvVars((prev) => { const next = new Set(prev); From d204dd6e7e9b5b8a72232b14a350eccb1e8ceb2a Mon Sep 17 00:00:00 2001 From: cgoing Date: Tue, 25 Mar 2025 01:56:53 +0900 Subject: [PATCH 80/98] feat: json view component - dark color --- client/src/components/JsonView.tsx | 36 +++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index 21d65d8..7d3c586 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -91,7 +91,11 @@ const JsonNode = memo( if (isEmpty) { return (
- {name && {name}:} + {name && ( + + {name}: + + )} {symbolMap.empty}
); @@ -100,24 +104,24 @@ const JsonNode = memo( return (
setIsExpanded(!isExpanded)} > {name && ( - + {name}: )} {isExpanded ? ( - + {symbolMap.open} ) : ( <> - + {symbolMap.collapsed} - + {itemCount} {itemCount === 1 ? "item" : "items"} @@ -125,7 +129,7 @@ const JsonNode = memo(
{isExpanded && ( <> -
+
{isArray ? (items as JsonValue[]).map((item, index) => (
@@ -148,7 +152,9 @@ const JsonNode = memo(
))}
-
{symbolMap.close}
+
+ {symbolMap.close} +
)}
@@ -162,7 +168,11 @@ const JsonNode = memo( if (!isTooLong) { return (
- {name && {name}:} + {name && ( + + {name}: + + )} "{value}"
); @@ -171,7 +181,7 @@ const JsonNode = memo( return (
{name && ( - + {name}: )} @@ -195,7 +205,11 @@ const JsonNode = memo( default: return (
- {name && {name}:} + {name && ( + + {name}: + + )} {data === null ? "null" : String(data)} From 0656e15a22ebda6f0064071185d5163d222ca616 Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Mon, 24 Mar 2025 23:30:35 +0530 Subject: [PATCH 81/98] add configuration types --- client/src/lib/configurationTypes.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 client/src/lib/configurationTypes.ts diff --git a/client/src/lib/configurationTypes.ts b/client/src/lib/configurationTypes.ts new file mode 100644 index 0000000..2310c58 --- /dev/null +++ b/client/src/lib/configurationTypes.ts @@ -0,0 +1,8 @@ +export type ConfigItem = { + description: string; + value: string | number | boolean; +} + +export type InspectorConfig = { + MCP_SERVER_REQUEST_TIMEOUT: ConfigItem; +}; \ No newline at end of file From 16b38071e7554eb5556491f2f04339e58c5a073c Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 12:36:17 -0700 Subject: [PATCH 82/98] Bump version to 0.7.0 --- client/package.json | 2 +- package-lock.json | 12 ++++++------ package.json | 6 +++--- server/package.json | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/package.json b/client/package.json index 0471765..30cf37f 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.6.0", + "version": "0.7.0", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/package-lock.json b/package-lock.json index 68929c5..eec3c95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.6.0", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "workspaces": [ "client", "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.6.0", - "@modelcontextprotocol/inspector-server": "^0.6.0", + "@modelcontextprotocol/inspector-client": "^0.7.0", + "@modelcontextprotocol/inspector-server": "^0.7.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -32,7 +32,7 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", @@ -11299,7 +11299,7 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", diff --git a/package.json b/package.json index e3bce92..66cc1a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.6.0", + "version": "0.7.0", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -34,8 +34,8 @@ "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.6.0", - "@modelcontextprotocol/inspector-server": "^0.6.0", + "@modelcontextprotocol/inspector-client": "^0.7.0", + "@modelcontextprotocol/inspector-server": "^0.7.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", diff --git a/server/package.json b/server/package.json index 5d8839f..732993f 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.6.0", + "version": "0.7.0", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", From 2588f3aeb3c7518d7a3e87e2529692e4bc86841b Mon Sep 17 00:00:00 2001 From: cgoing Date: Wed, 26 Mar 2025 00:02:10 +0900 Subject: [PATCH 83/98] Change tooltip title from Korean to English --- client/src/components/JsonView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index 7d3c586..9add405 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -188,7 +188,7 @@ const JsonNode = memo( setIsExpanded(!isExpanded)} - title={isExpanded ? "클릭하여 축소" : "클릭하여 전체 보기"} + title={isExpanded ? "Click to collapse" : "Click to expand"} > {isExpanded ? `"${value}"` : `"${value.slice(0, maxLength)}..."`} From 03c1ba3092d748f60beeb1ef9b5e78be35b98c84 Mon Sep 17 00:00:00 2001 From: cgoing Date: Wed, 26 Mar 2025 00:11:07 +0900 Subject: [PATCH 84/98] Change JsonView default initialExpandDepth from 2 to 3 --- client/src/components/JsonView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index 9add405..6c348fe 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -23,7 +23,7 @@ function tryParseJson(str: string): { success: boolean; data: JsonValue } { } const JsonView = memo( - ({ data, name, initialExpandDepth = 2 }: JsonViewProps) => { + ({ data, name, initialExpandDepth = 3 }: JsonViewProps) => { const normalizedData = typeof data === "string" ? tryParseJson(data).success From e6f5da838301d7dcc6a263aa97f586ccae56d068 Mon Sep 17 00:00:00 2001 From: cgoing Date: Thu, 27 Mar 2025 10:49:37 +0900 Subject: [PATCH 85/98] Improve JsonView component styling and change to use JsonView in PromptsTab --- client/src/components/JsonView.tsx | 10 +++++++--- client/src/components/PromptsTab.tsx | 11 +++++------ client/src/components/ToolsTab.tsx | 23 ++++++++++------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/client/src/components/JsonView.tsx b/client/src/components/JsonView.tsx index 6c348fe..e20dd5c 100644 --- a/client/src/components/JsonView.tsx +++ b/client/src/components/JsonView.tsx @@ -1,5 +1,6 @@ import { useState, memo } from "react"; import { JsonValue } from "./DynamicJsonForm"; +import clsx from "clsx"; interface JsonViewProps { data: JsonValue; @@ -70,7 +71,7 @@ const JsonNode = memo( boolean: "text-amber-600", null: "text-purple-600", undefined: "text-gray-600", - string: "text-green-600", + string: "text-green-600 break-all", default: "text-gray-700", }; @@ -173,7 +174,7 @@ const JsonNode = memo( {name}: )} - "{value}" +

"{value}"

); } @@ -186,7 +187,10 @@ const JsonNode = memo(
)} setIsExpanded(!isExpanded)} title={isExpanded ? "Click to collapse" : "Click to expand"} > diff --git a/client/src/components/PromptsTab.tsx b/client/src/components/PromptsTab.tsx index f88b16f..b42cf77 100644 --- a/client/src/components/PromptsTab.tsx +++ b/client/src/components/PromptsTab.tsx @@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button"; import { Combobox } from "@/components/ui/combobox"; import { Label } from "@/components/ui/label"; import { TabsContent } from "@/components/ui/tabs"; -import { Textarea } from "@/components/ui/textarea"; + import { ListPromptsResult, PromptReference, @@ -13,6 +13,7 @@ import { AlertCircle } from "lucide-react"; import { useEffect, useState } from "react"; import ListPane from "./ListPane"; import { useCompletionState } from "@/lib/hooks/useCompletionState"; +import JsonView from "./JsonView"; export type Prompt = { name: string; @@ -151,11 +152,9 @@ const PromptsTab = ({ Get Prompt {promptContent && ( -