From 592dacad39fa0b7146b33353aeca0c24606ac959 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 26 Feb 2025 09:50:47 -0700 Subject: [PATCH 01/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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 04a90e8d892b4381ba1f50ee3c4323051ac8378c Mon Sep 17 00:00:00 2001 From: Ryan Rozich Date: Mon, 10 Mar 2025 07:50:02 -0500 Subject: [PATCH 25/63] 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 26/63] 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 27/63] 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 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 28/63] 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 29/63] 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 51049522399322971edf2390471bdc2763a97c01 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 15 Mar 2025 16:37:10 -0400 Subject: [PATCH 30/63] 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 31/63] 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 32/63] 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 33/63] 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 34/63] 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 35/63] 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 36/63] 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 37/63] 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 7ddba51b36c4eac7595903dd68c0da84a1636181 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 17 Mar 2025 06:36:35 -0700 Subject: [PATCH 38/63] 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 39/63] 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 40/63] 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 41/63] 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 029e482e05a57f5610662d080c5aba59f2dd762c Mon Sep 17 00:00:00 2001 From: Nathan Arseneau Date: Wed, 19 Mar 2025 20:15:14 -0400 Subject: [PATCH 42/63] 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 4fdbcee7060f748fe68271414bd9422ee4674fda Mon Sep 17 00:00:00 2001 From: jazminliu Date: Thu, 20 Mar 2025 22:04:59 +0800 Subject: [PATCH 43/63] 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 44/63] 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 45/63] 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 af44efb2362ed399ede821583415d2fa58d3c9f6 Mon Sep 17 00:00:00 2001 From: Abdelkader Boudih Date: Fri, 21 Mar 2025 22:30:34 +0000 Subject: [PATCH 46/63] chore: extract utils escapeUnicode --- client/src/components/ToolsTab.tsx | 18 +------------ .../src/utils/__tests__/escapeUnicode.test.ts | 27 +++++++++++++++++++ client/src/utils/escapeUnicode.ts | 16 +++++++++++ package-lock.json | 1 + package.json | 1 + 5 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 client/src/utils/__tests__/escapeUnicode.test.ts create mode 100644 client/src/utils/escapeUnicode.ts diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index ff4044f..82ebdb0 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -16,23 +16,7 @@ import { import { AlertCircle, Send } from "lucide-react"; import { useEffect, useState } from "react"; import ListPane from "./ListPane"; - -// 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, - ); -} +import { escapeUnicode } from "@/utils/escapeUnicode"; const ToolsTab = ({ tools, diff --git a/client/src/utils/__tests__/escapeUnicode.test.ts b/client/src/utils/__tests__/escapeUnicode.test.ts new file mode 100644 index 0000000..b2eb130 --- /dev/null +++ b/client/src/utils/__tests__/escapeUnicode.test.ts @@ -0,0 +1,27 @@ +import { escapeUnicode } from "../escapeUnicode"; + +describe("escapeUnicode", () => { + it("should escape Unicode characters in a string", () => { + const input = { text: "你好世界" }; + const expected = '{\n "text": "\\\\u4f60\\\\u597d\\\\u4e16\\\\u754c"\n}'; + expect(escapeUnicode(input)).toBe(expected); + }); + + it("should handle empty strings", () => { + const input = { text: "" }; + const expected = '{\n "text": ""\n}'; + expect(escapeUnicode(input)).toBe(expected); + }); + + it("should handle null and undefined values", () => { + const input = { text: null, value: undefined }; + const expected = '{\n "text": null\n}'; + expect(escapeUnicode(input)).toBe(expected); + }); + + it("should handle numbers and booleans", () => { + const input = { number: 123, boolean: true }; + const expected = '{\n "number": 123,\n "boolean": true\n}'; + expect(escapeUnicode(input)).toBe(expected); + }); +}); diff --git a/client/src/utils/escapeUnicode.ts b/client/src/utils/escapeUnicode.ts new file mode 100644 index 0000000..ed6fbd9 --- /dev/null +++ b/client/src/utils/escapeUnicode.ts @@ -0,0 +1,16 @@ +// Utility function to escape Unicode characters +export function escapeUnicode(obj: unknown): 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, + ); +} diff --git a/package-lock.json b/package-lock.json index 21c9c30..68929c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "mcp-inspector": "bin/cli.js" }, "devDependencies": { + "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/shell-quote": "^1.7.5", "prettier": "3.3.3" diff --git a/package.json b/package.json index 3f40d2e..e3bce92 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "ts-node": "^10.9.2" }, "devDependencies": { + "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/shell-quote": "^1.7.5", "prettier": "3.3.3" From 9b0da1f892fe60429b7f72cb1239a0859d68f561 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 08:19:52 -0700 Subject: [PATCH 47/63] Add note on security considerations for proxy server --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a6ab6d4..4eb2467 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,10 @@ For more details on ways to use the inspector, see the [Inspector section of the The inspector supports bearer token authentication for SSE connections. Enter your token in the UI when connecting to an MCP server, and it will be sent in the Authorization header. +### Security Considerations + +The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server. + ### From this repository If you're working on the inspector itself: From ec738314875c0b984dca92df88c2be73c7ef4df2 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 08:30:06 -0700 Subject: [PATCH 48/63] Fix formatting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4eb2467..f1bd97c 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ The inspector supports bearer token authentication for SSE connections. Enter yo ### Security Considerations -The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server. +The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server. ### From this repository From 210975e38531b68be7af1eae72dee7aa0c0c5ab0 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 12:44:30 -0700 Subject: [PATCH 49/63] Add test dependencies --- client/package.json | 4 +- package-lock.json | 272 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+), 1 deletion(-) diff --git a/client/package.json b/client/package.json index 0471765..c3e4ed3 100644 --- a/client/package.json +++ b/client/package.json @@ -24,8 +24,8 @@ }, "dependencies": { "@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", @@ -50,6 +50,8 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/react": "^18.3.10", diff --git a/package-lock.json b/package-lock.json index 68929c5..23bc2a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,8 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/react": "^18.3.10", @@ -87,6 +89,13 @@ "vite": "^5.4.8" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", + "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", + "dev": true, + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -576,6 +585,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -3786,6 +3808,148 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/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", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/jest-dom/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/@testing-library/react": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.2.0.tgz", + "integrity": "sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -3820,6 +3984,14 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "license": "MIT" }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4585,6 +4757,16 @@ "node": ">=10" } }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -5331,6 +5513,13 @@ "node": ">= 8" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5467,6 +5656,16 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -5524,6 +5723,14 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -6796,6 +7003,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7984,6 +8201,17 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -8137,6 +8365,16 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -9151,6 +9389,27 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9837,6 +10096,19 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", From fc76a7c7d49cf2a05c70846387c8da6daabcb454 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 12:46:30 -0700 Subject: [PATCH 50/63] Add setup file and remove old testing mock that no longer exists from moduleNameMapper --- client/jest.config.cjs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 3830e79..73e0d16 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -1,12 +1,11 @@ module.exports = { preset: "ts-jest", testEnvironment: "jsdom", + setupFilesAfterEnv: [ + '/src/setupTests.ts' + ], moduleNameMapper: { - "^@/(.*)$": "/src/$1", - "^../components/DynamicJsonForm$": - "/src/utils/__mocks__/DynamicJsonForm.ts", - "^../../components/DynamicJsonForm$": - "/src/utils/__mocks__/DynamicJsonForm.ts", + "^@/(.*)$": "/src/$1" }, transform: { "^.+\\.tsx?$": [ @@ -14,9 +13,9 @@ module.exports = { { useESM: true, jsx: "react-jsx", - tsconfig: "tsconfig.jest.json", - }, - ], + tsconfig: "tsconfig.jest.json" + } + ] }, extensionsToTreatAsEsm: [".ts", ".tsx"], testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", @@ -25,13 +24,13 @@ module.exports = { "/node_modules/", "/dist/", "/bin/", - "\\.config\\.(js|ts|cjs|mjs)$", + "\\.config\\.(js|ts|cjs|mjs)$" ], // Exclude the same patterns from coverage reports coveragePathIgnorePatterns: [ "/node_modules/", "/dist/", "/bin/", - "\\.config\\.(js|ts|cjs|mjs)$", - ], + "\\.config\\.(js|ts|cjs|mjs)$" + ] }; From 85f0e216791d5c0e6e610bb1b30743de8b4b15a7 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 13:22:13 -0700 Subject: [PATCH 51/63] Use commonjs for jest --- client/jest.config.cjs | 1 - 1 file changed, 1 deletion(-) diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 73e0d16..6a48e7d 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -11,7 +11,6 @@ module.exports = { "^.+\\.tsx?$": [ "ts-jest", { - useESM: true, jsx: "react-jsx", tsconfig: "tsconfig.jest.json" } From 668cc915e4028132dd13254b406919a74b76bfd8 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 13:22:31 -0700 Subject: [PATCH 52/63] Add jest-dom types --- client/tsconfig.app.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json index 980c215..e6620f7 100644 --- a/client/tsconfig.app.json +++ b/client/tsconfig.app.json @@ -24,7 +24,8 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "types": ["jest", "@testing-library/jest-dom", "node"] }, "include": ["src"] } From 451704471c6324007cd263acf557a66ac943f95d Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 13:25:06 -0700 Subject: [PATCH 53/63] Remove setup --- client/jest.config.cjs | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 6a48e7d..949df20 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -1,9 +1,6 @@ module.exports = { preset: "ts-jest", testEnvironment: "jsdom", - setupFilesAfterEnv: [ - '/src/setupTests.ts' - ], moduleNameMapper: { "^@/(.*)$": "/src/$1" }, From 61e229a5529d343134c8333e2fb1700a43b4a79b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 13:35:24 -0700 Subject: [PATCH 54/63] Add sidebar tests --- .../src/components/__tests__/Sidebar.test.tsx | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 client/src/components/__tests__/Sidebar.test.tsx diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx new file mode 100644 index 0000000..765de2f --- /dev/null +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -0,0 +1,278 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, it, beforeEach, jest } from '@jest/globals'; +import Sidebar from '../Sidebar'; + +// Mock theme hook +jest.mock('../../lib/useTheme', () => ({ + __esModule: true, + default: () => ['light', jest.fn()], +})); + +describe('Sidebar Environment Variables', () => { + const defaultProps = { + connectionStatus: 'disconnected' as const, + transportType: 'stdio' as const, + setTransportType: jest.fn(), + command: '', + setCommand: jest.fn(), + args: '', + setArgs: jest.fn(), + sseUrl: '', + setSseUrl: jest.fn(), + env: {}, + setEnv: jest.fn(), + bearerToken: '', + setBearerToken: jest.fn(), + onConnect: jest.fn(), + stdErrNotifications: [], + logLevel: 'info' as const, + sendLogLevelRequest: jest.fn(), + loggingSupported: true, + }; + + const renderSidebar = (props = {}) => { + return render(); + }; + + const openEnvVarsSection = () => { + const button = screen.getByText('Environment Variables'); + fireEvent.click(button); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Basic Operations', () => { + it('should add a new environment variable', () => { + const setEnv = jest.fn(); + renderSidebar({ env: {}, setEnv }); + + openEnvVarsSection(); + + const addButton = screen.getByText('Add Environment Variable'); + fireEvent.click(addButton); + + expect(setEnv).toHaveBeenCalledWith({ '': '' }); + }); + + it('should remove an environment variable', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const removeButton = screen.getByRole('button', { name: '×' }); + fireEvent.click(removeButton); + + expect(setEnv).toHaveBeenCalledWith({}); + }); + + it('should update environment variable value', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const valueInput = screen.getByDisplayValue('test_value'); + fireEvent.change(valueInput, { target: { value: 'new_value' } }); + + expect(setEnv).toHaveBeenCalledWith({ TEST_KEY: 'new_value' }); + }); + + it('should toggle value visibility', () => { + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv }); + + openEnvVarsSection(); + + const valueInput = screen.getByDisplayValue('test_value'); + expect(valueInput).toHaveProperty('type', 'password'); + + const toggleButton = screen.getByRole('button', { name: /show value/i }); + fireEvent.click(toggleButton); + + expect(valueInput).toHaveProperty('type', 'text'); + }); + }); + + describe('Key Editing', () => { + it('should maintain order when editing first key', () => { + const setEnv = jest.fn(); + const initialEnv = { + FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + THIRD_KEY: 'third_value', + }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const firstKeyInput = screen.getByDisplayValue('FIRST_KEY'); + fireEvent.change(firstKeyInput, { target: { value: 'NEW_FIRST_KEY' } }); + + expect(setEnv).toHaveBeenCalledWith({ + NEW_FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + THIRD_KEY: 'third_value', + }); + }); + + it('should maintain order when editing middle key', () => { + const setEnv = jest.fn(); + const initialEnv = { + FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + THIRD_KEY: 'third_value', + }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const middleKeyInput = screen.getByDisplayValue('SECOND_KEY'); + fireEvent.change(middleKeyInput, { target: { value: 'NEW_SECOND_KEY' } }); + + expect(setEnv).toHaveBeenCalledWith({ + FIRST_KEY: 'first_value', + NEW_SECOND_KEY: 'second_value', + THIRD_KEY: 'third_value', + }); + }); + + it('should maintain order when editing last key', () => { + const setEnv = jest.fn(); + const initialEnv = { + FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + THIRD_KEY: 'third_value', + }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const lastKeyInput = screen.getByDisplayValue('THIRD_KEY'); + fireEvent.change(lastKeyInput, { target: { value: 'NEW_THIRD_KEY' } }); + + expect(setEnv).toHaveBeenCalledWith({ + FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + NEW_THIRD_KEY: 'third_value', + }); + }); + }); + + describe('Multiple Operations', () => { + it('should maintain state after multiple key edits', () => { + const setEnv = jest.fn(); + const initialEnv = { + FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + }; + const { rerender } = renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + // First key edit + const firstKeyInput = screen.getByDisplayValue('FIRST_KEY'); + fireEvent.change(firstKeyInput, { target: { value: 'NEW_FIRST_KEY' } }); + + // Get the updated env from the first setEnv call + const updatedEnv = (setEnv.mock.calls[0][0] as Record); + + // Rerender with the updated env + rerender(); + + // Second key edit + const secondKeyInput = screen.getByDisplayValue('SECOND_KEY'); + fireEvent.change(secondKeyInput, { target: { value: 'NEW_SECOND_KEY' } }); + + // Verify the final state matches what we expect + expect(setEnv).toHaveBeenLastCalledWith({ + NEW_FIRST_KEY: 'first_value', + NEW_SECOND_KEY: 'second_value', + }); + }); + + it('should maintain visibility state after key edit', () => { + const initialEnv = { TEST_KEY: 'test_value' }; + const { rerender } = renderSidebar({ env: initialEnv }); + + openEnvVarsSection(); + + // Show the value + const toggleButton = screen.getByRole('button', { name: /show value/i }); + fireEvent.click(toggleButton); + + const valueInput = screen.getByDisplayValue('test_value'); + expect(valueInput).toHaveProperty('type', 'text'); + + // Edit the key + const keyInput = screen.getByDisplayValue('TEST_KEY'); + fireEvent.change(keyInput, { target: { value: 'NEW_KEY' } }); + + // Rerender with updated env + rerender(); + + // Value should still be visible + const updatedValueInput = screen.getByDisplayValue('test_value'); + expect(updatedValueInput).toHaveProperty('type', 'text'); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty key', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const keyInput = screen.getByDisplayValue('TEST_KEY'); + fireEvent.change(keyInput, { target: { value: '' } }); + + expect(setEnv).toHaveBeenCalledWith({ '': 'test_value' }); + }); + + it('should handle special characters in key', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const keyInput = screen.getByDisplayValue('TEST_KEY'); + fireEvent.change(keyInput, { target: { value: 'TEST-KEY@123' } }); + + expect(setEnv).toHaveBeenCalledWith({ 'TEST-KEY@123': 'test_value' }); + }); + + it('should handle unicode characters', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const keyInput = screen.getByDisplayValue('TEST_KEY'); + fireEvent.change(keyInput, { target: { value: 'TEST_🔑' } }); + + expect(setEnv).toHaveBeenCalledWith({ 'TEST_🔑': 'test_value' }); + }); + + it('should handle very long key names', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const keyInput = screen.getByDisplayValue('TEST_KEY'); + const longKey = 'A'.repeat(100); + fireEvent.change(keyInput, { target: { value: longKey } }); + + expect(setEnv).toHaveBeenCalledWith({ [longKey]: 'test_value' }); + }); + }); +}); From cab1ed3dd849a85b37255eb2ec20ee7764565a4c Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 08:28:28 -0700 Subject: [PATCH 55/63] Add some json form tests and handle css in ui tests --- client/jest.config.cjs | 3 +- client/src/__mocks__/styleMock.js | 1 + .../__tests__/DynamicJsonForm.test.tsx | 61 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 client/src/__mocks__/styleMock.js create mode 100644 client/src/components/__tests__/DynamicJsonForm.test.tsx diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 949df20..b87383e 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -2,7 +2,8 @@ module.exports = { preset: "ts-jest", testEnvironment: "jsdom", moduleNameMapper: { - "^@/(.*)$": "/src/$1" + "^@/(.*)$": "/src/$1", + "\\.css$": "/src/__mocks__/styleMock.js" }, transform: { "^.+\\.tsx?$": [ diff --git a/client/src/__mocks__/styleMock.js b/client/src/__mocks__/styleMock.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/client/src/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/client/src/components/__tests__/DynamicJsonForm.test.tsx b/client/src/components/__tests__/DynamicJsonForm.test.tsx new file mode 100644 index 0000000..d140663 --- /dev/null +++ b/client/src/components/__tests__/DynamicJsonForm.test.tsx @@ -0,0 +1,61 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, it, expect, jest } from '@jest/globals'; +import DynamicJsonForm from '../DynamicJsonForm'; +import type { JsonSchemaType } from '../DynamicJsonForm'; + +describe('DynamicJsonForm Integer Fields', () => { + const renderForm = (props = {}) => { + const defaultProps = { + schema: { + type: "integer" as const, + description: "Test integer field" + } satisfies JsonSchemaType, + value: undefined, + onChange: jest.fn() + }; + return render(); + }; + + describe('Basic Operations', () => { + it('should render number input with step=1', () => { + renderForm(); + const input = screen.getByRole('spinbutton'); + expect(input).toHaveProperty('type', 'number'); + expect(input).toHaveProperty('step', '1'); + }); + + it('should pass integer values to onChange', () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole('spinbutton'); + fireEvent.change(input, { target: { value: '42' } }); + + expect(onChange).toHaveBeenCalledWith(42); + // Verify the value is a number, not a string + expect(typeof onChange.mock.calls[0][0]).toBe('number'); + }); + + it('should not pass string values to onChange', () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole('spinbutton'); + fireEvent.change(input, { target: { value: 'abc' } }); + + expect(onChange).not.toHaveBeenCalled(); + }); + }); + + describe('Edge Cases', () => { + it('should handle non-numeric input by not calling onChange', () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole('spinbutton'); + fireEvent.change(input, { target: { value: 'abc' } }); + + expect(onChange).not.toHaveBeenCalled(); + }); + }); +}); From 5735f2347a4d1465033806e4648583a5a35a4e0f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 08:52:45 -0700 Subject: [PATCH 56/63] Add tests related to issues/187 to confirm fixed --- .../__tests__/DynamicJsonForm.test.tsx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/client/src/components/__tests__/DynamicJsonForm.test.tsx b/client/src/components/__tests__/DynamicJsonForm.test.tsx index d140663..f18c15f 100644 --- a/client/src/components/__tests__/DynamicJsonForm.test.tsx +++ b/client/src/components/__tests__/DynamicJsonForm.test.tsx @@ -3,6 +3,40 @@ import { describe, it, expect, jest } from '@jest/globals'; import DynamicJsonForm from '../DynamicJsonForm'; import type { JsonSchemaType } from '../DynamicJsonForm'; +describe('DynamicJsonForm String Fields', () => { + const renderForm = (props = {}) => { + const defaultProps = { + schema: { + type: "string" as const, + description: "Test string field" + } satisfies JsonSchemaType, + value: undefined, + onChange: jest.fn() + }; + return render(); + }; + + describe('Type Validation', () => { + it('should handle numeric input as string type', () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole('textbox'); + fireEvent.change(input, { target: { value: '123321' } }); + + expect(onChange).toHaveBeenCalledWith('123321'); + // Verify the value is a string, not a number + expect(typeof onChange.mock.calls[0][0]).toBe('string'); + }); + + it('should render as text input, not number input', () => { + renderForm(); + const input = screen.getByRole('textbox'); + expect(input).toHaveProperty('type', 'text'); + }); + }); +}); + describe('DynamicJsonForm Integer Fields', () => { const renderForm = (props = {}) => { const defaultProps = { From fa3e2867c97e27c2bb434be7eaacd6698dc8fb4b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 09:07:53 -0700 Subject: [PATCH 57/63] Fix formatting --- client/jest.config.cjs | 14 +- .../__tests__/DynamicJsonForm.test.tsx | 86 ++--- .../src/components/__tests__/Sidebar.test.tsx | 296 +++++++++--------- 3 files changed, 198 insertions(+), 198 deletions(-) diff --git a/client/jest.config.cjs b/client/jest.config.cjs index b87383e..c360e72 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -3,16 +3,16 @@ module.exports = { testEnvironment: "jsdom", moduleNameMapper: { "^@/(.*)$": "/src/$1", - "\\.css$": "/src/__mocks__/styleMock.js" + "\\.css$": "/src/__mocks__/styleMock.js", }, transform: { "^.+\\.tsx?$": [ "ts-jest", { jsx: "react-jsx", - tsconfig: "tsconfig.jest.json" - } - ] + tsconfig: "tsconfig.jest.json", + }, + ], }, extensionsToTreatAsEsm: [".ts", ".tsx"], testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", @@ -21,13 +21,13 @@ module.exports = { "/node_modules/", "/dist/", "/bin/", - "\\.config\\.(js|ts|cjs|mjs)$" + "\\.config\\.(js|ts|cjs|mjs)$", ], // Exclude the same patterns from coverage reports coveragePathIgnorePatterns: [ "/node_modules/", "/dist/", "/bin/", - "\\.config\\.(js|ts|cjs|mjs)$" - ] + "\\.config\\.(js|ts|cjs|mjs)$", + ], }; diff --git a/client/src/components/__tests__/DynamicJsonForm.test.tsx b/client/src/components/__tests__/DynamicJsonForm.test.tsx index f18c15f..fce6014 100644 --- a/client/src/components/__tests__/DynamicJsonForm.test.tsx +++ b/client/src/components/__tests__/DynamicJsonForm.test.tsx @@ -1,94 +1,94 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import { describe, it, expect, jest } from '@jest/globals'; -import DynamicJsonForm from '../DynamicJsonForm'; -import type { JsonSchemaType } from '../DynamicJsonForm'; +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, jest } from "@jest/globals"; +import DynamicJsonForm from "../DynamicJsonForm"; +import type { JsonSchemaType } from "../DynamicJsonForm"; -describe('DynamicJsonForm String Fields', () => { +describe("DynamicJsonForm String Fields", () => { const renderForm = (props = {}) => { const defaultProps = { schema: { type: "string" as const, - description: "Test string field" + description: "Test string field", } satisfies JsonSchemaType, value: undefined, - onChange: jest.fn() + onChange: jest.fn(), }; return render(); }; - describe('Type Validation', () => { - it('should handle numeric input as string type', () => { + describe("Type Validation", () => { + it("should handle numeric input as string type", () => { const onChange = jest.fn(); renderForm({ onChange }); - - const input = screen.getByRole('textbox'); - fireEvent.change(input, { target: { value: '123321' } }); - - expect(onChange).toHaveBeenCalledWith('123321'); + + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "123321" } }); + + expect(onChange).toHaveBeenCalledWith("123321"); // Verify the value is a string, not a number - expect(typeof onChange.mock.calls[0][0]).toBe('string'); + expect(typeof onChange.mock.calls[0][0]).toBe("string"); }); - it('should render as text input, not number input', () => { + it("should render as text input, not number input", () => { renderForm(); - const input = screen.getByRole('textbox'); - expect(input).toHaveProperty('type', 'text'); + const input = screen.getByRole("textbox"); + expect(input).toHaveProperty("type", "text"); }); }); }); -describe('DynamicJsonForm Integer Fields', () => { +describe("DynamicJsonForm Integer Fields", () => { const renderForm = (props = {}) => { const defaultProps = { schema: { type: "integer" as const, - description: "Test integer field" + description: "Test integer field", } satisfies JsonSchemaType, value: undefined, - onChange: jest.fn() + onChange: jest.fn(), }; return render(); }; - describe('Basic Operations', () => { - it('should render number input with step=1', () => { + describe("Basic Operations", () => { + it("should render number input with step=1", () => { renderForm(); - const input = screen.getByRole('spinbutton'); - expect(input).toHaveProperty('type', 'number'); - expect(input).toHaveProperty('step', '1'); + const input = screen.getByRole("spinbutton"); + expect(input).toHaveProperty("type", "number"); + expect(input).toHaveProperty("step", "1"); }); - it('should pass integer values to onChange', () => { + it("should pass integer values to onChange", () => { const onChange = jest.fn(); renderForm({ onChange }); - - const input = screen.getByRole('spinbutton'); - fireEvent.change(input, { target: { value: '42' } }); - + + const input = screen.getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "42" } }); + expect(onChange).toHaveBeenCalledWith(42); // Verify the value is a number, not a string - expect(typeof onChange.mock.calls[0][0]).toBe('number'); + expect(typeof onChange.mock.calls[0][0]).toBe("number"); }); - it('should not pass string values to onChange', () => { + it("should not pass string values to onChange", () => { const onChange = jest.fn(); renderForm({ onChange }); - - const input = screen.getByRole('spinbutton'); - fireEvent.change(input, { target: { value: 'abc' } }); - + + const input = screen.getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "abc" } }); + expect(onChange).not.toHaveBeenCalled(); }); }); - describe('Edge Cases', () => { - it('should handle non-numeric input by not calling onChange', () => { + describe("Edge Cases", () => { + it("should handle non-numeric input by not calling onChange", () => { const onChange = jest.fn(); renderForm({ onChange }); - - const input = screen.getByRole('spinbutton'); - fireEvent.change(input, { target: { value: 'abc' } }); - + + const input = screen.getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "abc" } }); + expect(onChange).not.toHaveBeenCalled(); }); }); diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx index 765de2f..8c0b313 100644 --- a/client/src/components/__tests__/Sidebar.test.tsx +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -1,31 +1,31 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import { describe, it, beforeEach, jest } from '@jest/globals'; -import Sidebar from '../Sidebar'; +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, beforeEach, jest } from "@jest/globals"; +import Sidebar from "../Sidebar"; // Mock theme hook -jest.mock('../../lib/useTheme', () => ({ +jest.mock("../../lib/useTheme", () => ({ __esModule: true, - default: () => ['light', jest.fn()], + default: () => ["light", jest.fn()], })); -describe('Sidebar Environment Variables', () => { +describe("Sidebar Environment Variables", () => { const defaultProps = { - connectionStatus: 'disconnected' as const, - transportType: 'stdio' as const, + connectionStatus: "disconnected" as const, + transportType: "stdio" as const, setTransportType: jest.fn(), - command: '', + command: "", setCommand: jest.fn(), - args: '', + args: "", setArgs: jest.fn(), - sseUrl: '', + sseUrl: "", setSseUrl: jest.fn(), env: {}, setEnv: jest.fn(), - bearerToken: '', + bearerToken: "", setBearerToken: jest.fn(), onConnect: jest.fn(), stdErrNotifications: [], - logLevel: 'info' as const, + logLevel: "info" as const, sendLogLevelRequest: jest.fn(), loggingSupported: true, }; @@ -35,7 +35,7 @@ describe('Sidebar Environment Variables', () => { }; const openEnvVarsSection = () => { - const button = screen.getByText('Environment Variables'); + const button = screen.getByText("Environment Variables"); fireEvent.click(button); }; @@ -43,236 +43,236 @@ describe('Sidebar Environment Variables', () => { jest.clearAllMocks(); }); - describe('Basic Operations', () => { - it('should add a new environment variable', () => { + describe("Basic Operations", () => { + it("should add a new environment variable", () => { const setEnv = jest.fn(); renderSidebar({ env: {}, setEnv }); - + openEnvVarsSection(); - - const addButton = screen.getByText('Add Environment Variable'); + + const addButton = screen.getByText("Add Environment Variable"); fireEvent.click(addButton); - - expect(setEnv).toHaveBeenCalledWith({ '': '' }); + + expect(setEnv).toHaveBeenCalledWith({ "": "" }); }); - it('should remove an environment variable', () => { + it("should remove an environment variable", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const removeButton = screen.getByRole('button', { name: '×' }); + + const removeButton = screen.getByRole("button", { name: "×" }); fireEvent.click(removeButton); - + expect(setEnv).toHaveBeenCalledWith({}); }); - it('should update environment variable value', () => { + it("should update environment variable value", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const valueInput = screen.getByDisplayValue('test_value'); - fireEvent.change(valueInput, { target: { value: 'new_value' } }); - - expect(setEnv).toHaveBeenCalledWith({ TEST_KEY: 'new_value' }); + + const valueInput = screen.getByDisplayValue("test_value"); + fireEvent.change(valueInput, { target: { value: "new_value" } }); + + expect(setEnv).toHaveBeenCalledWith({ TEST_KEY: "new_value" }); }); - it('should toggle value visibility', () => { - const initialEnv = { TEST_KEY: 'test_value' }; + it("should toggle value visibility", () => { + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv }); - + openEnvVarsSection(); - - const valueInput = screen.getByDisplayValue('test_value'); - expect(valueInput).toHaveProperty('type', 'password'); - - const toggleButton = screen.getByRole('button', { name: /show value/i }); + + const valueInput = screen.getByDisplayValue("test_value"); + expect(valueInput).toHaveProperty("type", "password"); + + const toggleButton = screen.getByRole("button", { name: /show value/i }); fireEvent.click(toggleButton); - - expect(valueInput).toHaveProperty('type', 'text'); + + expect(valueInput).toHaveProperty("type", "text"); }); }); - describe('Key Editing', () => { - it('should maintain order when editing first key', () => { + describe("Key Editing", () => { + it("should maintain order when editing first key", () => { const setEnv = jest.fn(); const initialEnv = { - FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', - THIRD_KEY: 'third_value', + FIRST_KEY: "first_value", + SECOND_KEY: "second_value", + THIRD_KEY: "third_value", }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const firstKeyInput = screen.getByDisplayValue('FIRST_KEY'); - fireEvent.change(firstKeyInput, { target: { value: 'NEW_FIRST_KEY' } }); - + + const firstKeyInput = screen.getByDisplayValue("FIRST_KEY"); + fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } }); + expect(setEnv).toHaveBeenCalledWith({ - NEW_FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', - THIRD_KEY: 'third_value', + NEW_FIRST_KEY: "first_value", + SECOND_KEY: "second_value", + THIRD_KEY: "third_value", }); }); - it('should maintain order when editing middle key', () => { + it("should maintain order when editing middle key", () => { const setEnv = jest.fn(); const initialEnv = { - FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', - THIRD_KEY: 'third_value', + FIRST_KEY: "first_value", + SECOND_KEY: "second_value", + THIRD_KEY: "third_value", }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const middleKeyInput = screen.getByDisplayValue('SECOND_KEY'); - fireEvent.change(middleKeyInput, { target: { value: 'NEW_SECOND_KEY' } }); - + + const middleKeyInput = screen.getByDisplayValue("SECOND_KEY"); + fireEvent.change(middleKeyInput, { target: { value: "NEW_SECOND_KEY" } }); + expect(setEnv).toHaveBeenCalledWith({ - FIRST_KEY: 'first_value', - NEW_SECOND_KEY: 'second_value', - THIRD_KEY: 'third_value', + FIRST_KEY: "first_value", + NEW_SECOND_KEY: "second_value", + THIRD_KEY: "third_value", }); }); - it('should maintain order when editing last key', () => { + it("should maintain order when editing last key", () => { const setEnv = jest.fn(); const initialEnv = { - FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', - THIRD_KEY: 'third_value', + FIRST_KEY: "first_value", + SECOND_KEY: "second_value", + THIRD_KEY: "third_value", }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const lastKeyInput = screen.getByDisplayValue('THIRD_KEY'); - fireEvent.change(lastKeyInput, { target: { value: 'NEW_THIRD_KEY' } }); - + + const lastKeyInput = screen.getByDisplayValue("THIRD_KEY"); + fireEvent.change(lastKeyInput, { target: { value: "NEW_THIRD_KEY" } }); + expect(setEnv).toHaveBeenCalledWith({ - FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', - NEW_THIRD_KEY: 'third_value', + FIRST_KEY: "first_value", + SECOND_KEY: "second_value", + NEW_THIRD_KEY: "third_value", }); }); }); - describe('Multiple Operations', () => { - it('should maintain state after multiple key edits', () => { + describe("Multiple Operations", () => { + it("should maintain state after multiple key edits", () => { const setEnv = jest.fn(); const initialEnv = { - FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', + FIRST_KEY: "first_value", + SECOND_KEY: "second_value", }; const { rerender } = renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - + // First key edit - const firstKeyInput = screen.getByDisplayValue('FIRST_KEY'); - fireEvent.change(firstKeyInput, { target: { value: 'NEW_FIRST_KEY' } }); - + const firstKeyInput = screen.getByDisplayValue("FIRST_KEY"); + fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } }); + // Get the updated env from the first setEnv call - const updatedEnv = (setEnv.mock.calls[0][0] as Record); - + const updatedEnv = setEnv.mock.calls[0][0] as Record; + // Rerender with the updated env rerender(); - + // Second key edit - const secondKeyInput = screen.getByDisplayValue('SECOND_KEY'); - fireEvent.change(secondKeyInput, { target: { value: 'NEW_SECOND_KEY' } }); - + const secondKeyInput = screen.getByDisplayValue("SECOND_KEY"); + fireEvent.change(secondKeyInput, { target: { value: "NEW_SECOND_KEY" } }); + // Verify the final state matches what we expect expect(setEnv).toHaveBeenLastCalledWith({ - NEW_FIRST_KEY: 'first_value', - NEW_SECOND_KEY: 'second_value', + NEW_FIRST_KEY: "first_value", + NEW_SECOND_KEY: "second_value", }); }); - it('should maintain visibility state after key edit', () => { - const initialEnv = { TEST_KEY: 'test_value' }; + it("should maintain visibility state after key edit", () => { + const initialEnv = { TEST_KEY: "test_value" }; const { rerender } = renderSidebar({ env: initialEnv }); - + openEnvVarsSection(); - + // Show the value - const toggleButton = screen.getByRole('button', { name: /show value/i }); + const toggleButton = screen.getByRole("button", { name: /show value/i }); fireEvent.click(toggleButton); - - const valueInput = screen.getByDisplayValue('test_value'); - expect(valueInput).toHaveProperty('type', 'text'); - + + const valueInput = screen.getByDisplayValue("test_value"); + expect(valueInput).toHaveProperty("type", "text"); + // Edit the key - const keyInput = screen.getByDisplayValue('TEST_KEY'); - fireEvent.change(keyInput, { target: { value: 'NEW_KEY' } }); - + const keyInput = screen.getByDisplayValue("TEST_KEY"); + fireEvent.change(keyInput, { target: { value: "NEW_KEY" } }); + // Rerender with updated env - rerender(); - + rerender(); + // Value should still be visible - const updatedValueInput = screen.getByDisplayValue('test_value'); - expect(updatedValueInput).toHaveProperty('type', 'text'); + const updatedValueInput = screen.getByDisplayValue("test_value"); + expect(updatedValueInput).toHaveProperty("type", "text"); }); }); - describe('Edge Cases', () => { - it('should handle empty key', () => { + describe("Edge Cases", () => { + it("should handle empty key", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const keyInput = screen.getByDisplayValue('TEST_KEY'); - fireEvent.change(keyInput, { target: { value: '' } }); - - expect(setEnv).toHaveBeenCalledWith({ '': 'test_value' }); + + const keyInput = screen.getByDisplayValue("TEST_KEY"); + fireEvent.change(keyInput, { target: { value: "" } }); + + expect(setEnv).toHaveBeenCalledWith({ "": "test_value" }); }); - it('should handle special characters in key', () => { + it("should handle special characters in key", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const keyInput = screen.getByDisplayValue('TEST_KEY'); - fireEvent.change(keyInput, { target: { value: 'TEST-KEY@123' } }); - - expect(setEnv).toHaveBeenCalledWith({ 'TEST-KEY@123': 'test_value' }); + + const keyInput = screen.getByDisplayValue("TEST_KEY"); + fireEvent.change(keyInput, { target: { value: "TEST-KEY@123" } }); + + expect(setEnv).toHaveBeenCalledWith({ "TEST-KEY@123": "test_value" }); }); - it('should handle unicode characters', () => { + it("should handle unicode characters", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const keyInput = screen.getByDisplayValue('TEST_KEY'); - fireEvent.change(keyInput, { target: { value: 'TEST_🔑' } }); - - expect(setEnv).toHaveBeenCalledWith({ 'TEST_🔑': 'test_value' }); + + const keyInput = screen.getByDisplayValue("TEST_KEY"); + fireEvent.change(keyInput, { target: { value: "TEST_🔑" } }); + + expect(setEnv).toHaveBeenCalledWith({ "TEST_🔑": "test_value" }); }); - it('should handle very long key names', () => { + it("should handle very long key names", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const keyInput = screen.getByDisplayValue('TEST_KEY'); - const longKey = 'A'.repeat(100); + + const keyInput = screen.getByDisplayValue("TEST_KEY"); + const longKey = "A".repeat(100); fireEvent.change(keyInput, { target: { value: longKey } }); - - expect(setEnv).toHaveBeenCalledWith({ [longKey]: 'test_value' }); + + expect(setEnv).toHaveBeenCalledWith({ [longKey]: "test_value" }); }); }); }); From a7f25153c445769add41e89dfaf15b7f5c60971f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 09:18:58 -0700 Subject: [PATCH 58/63] Add failing ToolsTab test that should get fixed with pull/198 --- .../components/__tests__/ToolsTab.test.tsx | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 client/src/components/__tests__/ToolsTab.test.tsx diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx new file mode 100644 index 0000000..2688b52 --- /dev/null +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -0,0 +1,72 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, jest } from "@jest/globals"; +import ToolsTab from "../ToolsTab"; +import { Tool } from "@modelcontextprotocol/sdk/types.js"; +import { Tabs } from "@/components/ui/tabs"; + +describe("ToolsTab", () => { + const mockTools: Tool[] = [ + { + name: "tool1", + description: "First tool", + inputSchema: { + type: "object" as const, + properties: { + num: { type: "number" as const } + } + } + }, + { + name: "tool2", + description: "Second tool", + inputSchema: { + type: "object" as const, + properties: { + num: { type: "number" as const } + } + } + } + ]; + + const defaultProps = { + tools: mockTools, + listTools: jest.fn(), + clearTools: jest.fn(), + callTool: jest.fn(), + selectedTool: null, + setSelectedTool: jest.fn(), + toolResult: null, + nextCursor: "", + error: null + }; + + const renderToolsTab = (props = {}) => { + return render( + + + + ); + }; + + it("should reset input values when switching tools", () => { + const { rerender } = renderToolsTab({ + selectedTool: mockTools[0] + }); + + // Enter a value in the first tool's input + const input = screen.getByRole("spinbutton") as HTMLInputElement; + fireEvent.change(input, { target: { value: "42" } }); + expect(input.value).toBe("42"); + + // Switch to second tool + rerender( + + + + ); + + // Verify input is reset + const newInput = screen.getByRole("spinbutton") as HTMLInputElement; + expect(newInput.value).toBe(""); + }); +}); From b7fa23676a6255176dad029dd1e79252ccba9cb4 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 09:27:51 -0700 Subject: [PATCH 59/63] Fix formatting --- .../components/__tests__/ToolsTab.test.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 2688b52..2a45065 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -12,9 +12,9 @@ describe("ToolsTab", () => { inputSchema: { type: "object" as const, properties: { - num: { type: "number" as const } - } - } + num: { type: "number" as const }, + }, + }, }, { name: "tool2", @@ -22,10 +22,10 @@ describe("ToolsTab", () => { inputSchema: { type: "object" as const, properties: { - num: { type: "number" as const } - } - } - } + num: { type: "number" as const }, + }, + }, + }, ]; const defaultProps = { @@ -37,20 +37,20 @@ describe("ToolsTab", () => { setSelectedTool: jest.fn(), toolResult: null, nextCursor: "", - error: null + error: null, }; const renderToolsTab = (props = {}) => { return render( - + , ); }; it("should reset input values when switching tools", () => { const { rerender } = renderToolsTab({ - selectedTool: mockTools[0] + selectedTool: mockTools[0], }); // Enter a value in the first tool's input @@ -62,7 +62,7 @@ describe("ToolsTab", () => { rerender( - + , ); // Verify input is reset From 379486b5ea4d4ab2e2faae5a96c79276e374234f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 09:43:48 -0700 Subject: [PATCH 60/63] Add failing test for pull/206 --- .../src/components/__tests__/Sidebar.test.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx index 8c0b313..fc8a5f4 100644 --- a/client/src/components/__tests__/Sidebar.test.tsx +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -161,6 +161,31 @@ describe("Sidebar Environment Variables", () => { NEW_THIRD_KEY: "third_value", }); }); + + it("should maintain order during key editing", () => { + const setEnv = jest.fn(); + const initialEnv = { + KEY1: "value1", + KEY2: "value2" + }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + // Type "NEW_" one character at a time + const key1Input = screen.getByDisplayValue("KEY1"); + "NEW_".split("").forEach((char) => { + fireEvent.change(key1Input, { target: { value: char + "KEY1".slice(1) } }); + }); + + // Verify the last setEnv call maintains the order + const lastCall = setEnv.mock.calls[setEnv.mock.calls.length - 1][0] as Record; + const entries = Object.entries(lastCall); + + // The values should stay with their original keys + expect(entries[0][1]).toBe("value1"); // First entry should still have value1 + expect(entries[1][1]).toBe("value2"); // Second entry should still have value2 + }); }); describe("Multiple Operations", () => { From 65a0d46816aca50e87643e325d80160a4743b33b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 09:47:34 -0700 Subject: [PATCH 61/63] Fix formatting --- client/src/components/__tests__/Sidebar.test.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx index fc8a5f4..710c80d 100644 --- a/client/src/components/__tests__/Sidebar.test.tsx +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -166,7 +166,7 @@ describe("Sidebar Environment Variables", () => { const setEnv = jest.fn(); const initialEnv = { KEY1: "value1", - KEY2: "value2" + KEY2: "value2", }; renderSidebar({ env: initialEnv, setEnv }); @@ -175,13 +175,17 @@ describe("Sidebar Environment Variables", () => { // Type "NEW_" one character at a time const key1Input = screen.getByDisplayValue("KEY1"); "NEW_".split("").forEach((char) => { - fireEvent.change(key1Input, { target: { value: char + "KEY1".slice(1) } }); + fireEvent.change(key1Input, { + target: { value: char + "KEY1".slice(1) }, + }); }); // Verify the last setEnv call maintains the order - const lastCall = setEnv.mock.calls[setEnv.mock.calls.length - 1][0] as Record; + const lastCall = setEnv.mock.calls[ + setEnv.mock.calls.length - 1 + ][0] as Record; const entries = Object.entries(lastCall); - + // The values should stay with their original keys expect(entries[0][1]).toBe("value1"); // First entry should still have value1 expect(entries[1][1]).toBe("value2"); // Second entry should still have value2 From f2f209dbd3ded70d76a181557a1c598a6721dfe5 Mon Sep 17 00:00:00 2001 From: Mark Anthony Cianfrani Date: Sat, 22 Mar 2025 12:52:56 -0400 Subject: [PATCH 62/63] 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 16b38071e7554eb5556491f2f04339e58c5a073c Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 12:36:17 -0700 Subject: [PATCH 63/63] 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)",