diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..c12f2f4 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,32 @@ +name: Test + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Typecheck + run: pnpm typecheck + + - name: Test + run: pnpm test diff --git a/apps/console/package.json b/apps/console/package.json index 5fcb46d..77fcd74 100644 --- a/apps/console/package.json +++ b/apps/console/package.json @@ -7,6 +7,8 @@ "dev": "vite", "build": "tsc --noEmit && vite build", "lint": "eslint .", + "test": "vitest run", + "test:watch": "vitest", "typecheck": "tsc --noEmit", "preview": "vite preview" }, @@ -29,12 +31,18 @@ }, "devDependencies": { "@tailwindcss/vite": "^4.2.2", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", "@types/js-yaml": "^4.0.9", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", + "@vitest/ui": "^4.1.6", + "jsdom": "^29.1.1", "tailwindcss": "^4.2.2", "typescript": "~6.0.2", - "vite": "^8.0.4" + "vite": "^8.0.4", + "vitest": "^4.1.6" } } diff --git a/apps/console/src/components/SchemaForm.tsx b/apps/console/src/components/SchemaForm.tsx index 5f728c2..88633e4 100644 --- a/apps/console/src/components/SchemaForm.tsx +++ b/apps/console/src/components/SchemaForm.tsx @@ -3,6 +3,7 @@ import Form from "@rjsf/core" import validator from "@rjsf/validator-ajv8" import type { RJSFSchema, UiSchema, TemplatesType } from "@rjsf/utils" import { keysOrderToUiSchema, sanitizeSchema } from "../lib/keys-order.ts" +import { addSensitiveStringWidgets } from "../lib/sensitive-fields.ts" import { customTemplates, customWidgets } from "./rjsf-templates.tsx" import { AdditionalPropertiesField } from "./AdditionalPropertiesField.tsx" import { ResourceQuotasField } from "./ResourceQuotasField.tsx" @@ -187,18 +188,21 @@ export function SchemaForm({ // Automatically add AdditionalPropertiesField for fields with additionalProperties schema const withAdditionalProps = addAdditionalPropertiesWidgets(schema, withVMDisk) + // Mask credential-shaped string fields (access/secret keys, passwords, tokens). + const withSensitive = addSensitiveStringWidgets(schema, withAdditionalProps) + // Override resourceQuotas field with structured quota editor. // Scoped to schemas where resourceQuotas has additionalProperties: {type: "string"} // (the cozystack-tenants chart shape) to avoid activating on unrelated CRDs. const rqSchema = (schema as any).properties?.resourceQuotas if (rqSchema && rqSchema.additionalProperties?.type === "string") { - withAdditionalProps.resourceQuotas = { - ...withAdditionalProps.resourceQuotas, + withSensitive.resourceQuotas = { + ...withSensitive.resourceQuotas, "ui:field": "ResourceQuotasField", } } - return withAdditionalProps + return withSensitive }, [keysOrder, schema]) const customFields = useMemo( diff --git a/apps/console/src/components/SensitiveStringWidget.test.tsx b/apps/console/src/components/SensitiveStringWidget.test.tsx new file mode 100644 index 0000000..f0ff91f --- /dev/null +++ b/apps/console/src/components/SensitiveStringWidget.test.tsx @@ -0,0 +1,174 @@ +import { useState } from "react" +import { describe, it, expect, vi } from "vitest" +import { render, screen } from "@testing-library/react" +import userEvent from "@testing-library/user-event" +import type { WidgetProps } from "@rjsf/utils" +import { SensitiveStringWidget } from "./SensitiveStringWidget.tsx" + +function makeProps(overrides: Partial = {}): WidgetProps { + const base = { + id: "sensitive", + name: "sensitive", + label: "sensitive", + value: undefined as unknown, + onChange: vi.fn(), + onBlur: vi.fn(), + onFocus: vi.fn(), + required: false, + disabled: false, + readonly: false, + autofocus: false, + placeholder: "", + options: {}, + schema: { type: "string" }, + uiSchema: {}, + formContext: {}, + rawErrors: [], + hideError: false, + multiple: false, + registry: {}, + } + return { ...base, ...overrides } as unknown as WidgetProps +} + +function renderWithLabel(props: WidgetProps) { + return render( + <> + + + , + ) +} + +const TOGGLE_NAME = /toggle credential visibility/i + +describe("SensitiveStringWidget", () => { + it("renders a real control associated with its label, regardless of state", () => { + renderWithLabel(makeProps({ value: "super-secret" })) + + const input = screen.getByLabelText("access key") as HTMLInputElement + expect(input.tagName).toBe("INPUT") + expect(input.type).toBe("password") + expect(input.value).toBe("super-secret") + }) + + it("flips the input type from password to text when the toggle is pressed", async () => { + const user = userEvent.setup() + renderWithLabel(makeProps({ value: "super-secret" })) + + const toggle = screen.getByRole("button", { name: TOGGLE_NAME }) + expect(toggle).toHaveAttribute("aria-pressed", "false") + + await user.click(toggle) + + expect(toggle).toHaveAttribute("aria-pressed", "true") + expect((screen.getByLabelText("access key") as HTMLInputElement).type).toBe("text") + }) + + it("keeps the toggle's accessible name stable across state changes", async () => { + const user = userEvent.setup() + renderWithLabel(makeProps({ value: "super-secret" })) + + const toggle = screen.getByRole("button", { name: TOGGLE_NAME }) + await user.click(toggle) + + // Same button is still findable by the same stable name after pressing. + expect(screen.getByRole("button", { name: TOGGLE_NAME })).toBe(toggle) + }) + + it("hides the value again after a second toggle press", async () => { + const user = userEvent.setup() + renderWithLabel(makeProps({ value: "super-secret" })) + + const toggle = screen.getByRole("button", { name: TOGGLE_NAME }) + await user.click(toggle) + await user.click(toggle) + + expect(toggle).toHaveAttribute("aria-pressed", "false") + expect((screen.getByLabelText("access key") as HTMLInputElement).type).toBe("password") + }) + + it("carries the project's input styling in both states (no shape change on toggle)", async () => { + const user = userEvent.setup() + renderWithLabel(makeProps({ value: "super-secret" })) + + const input = screen.getByLabelText("access key") as HTMLInputElement + expect(input).toHaveClass("rounded-md") + expect(input).toHaveClass("border") + + await user.click(screen.getByRole("button", { name: TOGGLE_NAME })) + + expect(input).toHaveClass("rounded-md") + expect(input).toHaveClass("border") + }) + + it("opts the input out of browser password-manager autofill", () => { + renderWithLabel(makeProps({ value: "super-secret" })) + + const input = screen.getByLabelText("access key") as HTMLInputElement + expect(input).toHaveAttribute("autocomplete", "new-password") + }) + + it("forwards typing into onChange without requiring the toggle first", async () => { + const user = userEvent.setup() + const onChange = vi.fn() + + function Host() { + const [value, setValue] = useState("") + return ( + <> + + { + onChange(next) + setValue(next as string | undefined) + }, + })} + /> + + ) + } + render() + + await user.type(screen.getByLabelText("access key"), "abc") + + expect(onChange).toHaveBeenLastCalledWith("abc") + }) + + it("emits undefined when the input is cleared", async () => { + const user = userEvent.setup() + const onChange = vi.fn() + renderWithLabel(makeProps({ value: "secret", onChange })) + + await user.clear(screen.getByLabelText("access key") as HTMLInputElement) + + expect(onChange).toHaveBeenLastCalledWith(undefined) + }) + + it("keeps the reveal toggle usable when the form is readonly", async () => { + const user = userEvent.setup() + renderWithLabel(makeProps({ value: "secret", readonly: true })) + + const input = screen.getByLabelText("access key") as HTMLInputElement + expect(input).toHaveAttribute("readonly") + + await user.click(screen.getByRole("button", { name: TOGGLE_NAME })) + + expect(input.type).toBe("text") + expect(input.value).toBe("secret") + }) + + it("keeps the reveal toggle usable when the form is disabled", async () => { + const user = userEvent.setup() + renderWithLabel(makeProps({ value: "secret", disabled: true })) + + const input = screen.getByLabelText("access key") as HTMLInputElement + expect(input).toBeDisabled() + + await user.click(screen.getByRole("button", { name: TOGGLE_NAME })) + + expect(input.type).toBe("text") + }) +}) diff --git a/apps/console/src/components/SensitiveStringWidget.tsx b/apps/console/src/components/SensitiveStringWidget.tsx new file mode 100644 index 0000000..26e8f61 --- /dev/null +++ b/apps/console/src/components/SensitiveStringWidget.tsx @@ -0,0 +1,56 @@ +import { useState } from "react" +import { Eye, EyeOff } from "lucide-react" +import type { WidgetProps } from "@rjsf/utils" + +// Matches the styling applied to `input[type="text"]` in schema-form.css. +// The CSS rule there is keyed on the `type` attribute, so a `type="password"` +// input would otherwise fall back to native browser chrome and visibly differ +// from neighbouring fields every time the user toggles reveal. +const INPUT_CLASS = + "w-full rounded-md border border-slate-200 bg-white px-2.5 py-1.5 text-sm text-slate-900 shadow-sm outline-none transition-shadow focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 disabled:opacity-50 disabled:cursor-not-allowed" + +const TOGGLE_CLASS = + "flex size-8 shrink-0 items-center justify-center rounded-md text-slate-500 hover:bg-slate-100" + +export function SensitiveStringWidget(props: WidgetProps) { + const { id, value, onChange, required, disabled, readonly, autofocus, placeholder } = props + const [revealed, setRevealed] = useState(false) + + const stringValue = typeof value === "string" ? value : "" + + return ( +
+ { + // Empty string is coerced to undefined so the surrounding form + // drops the key from the spec entirely on clear, matching the + // convention used by the other custom widgets in this folder. + const next = event.target.value + onChange(next === "" ? undefined : next) + }} + className={INPUT_CLASS} + /> + +
+ ) +} diff --git a/apps/console/src/components/rjsf-templates.tsx b/apps/console/src/components/rjsf-templates.tsx index bfd9635..acaba28 100644 --- a/apps/console/src/components/rjsf-templates.tsx +++ b/apps/console/src/components/rjsf-templates.tsx @@ -13,6 +13,7 @@ import { StorageClassWidget } from "./StorageClassWidget.tsx" import { AdditionalPropertiesWidget } from "./AdditionalPropertiesWidget.tsx" import { VMDiskWidget } from "./VMDiskWidget.tsx" import { BackupClassWidget } from "./BackupClassWidget.tsx" +import { SensitiveStringWidget } from "./SensitiveStringWidget.tsx" function IconButton< T = any, @@ -93,4 +94,5 @@ export const customWidgets = { AdditionalPropertiesWidget: AdditionalPropertiesWidget, VMDiskWidget: VMDiskWidget, BackupClassWidget: BackupClassWidget, + SensitiveStringWidget: SensitiveStringWidget, } diff --git a/apps/console/src/lib/sensitive-fields.test.ts b/apps/console/src/lib/sensitive-fields.test.ts new file mode 100644 index 0000000..8a04bf7 --- /dev/null +++ b/apps/console/src/lib/sensitive-fields.test.ts @@ -0,0 +1,271 @@ +import { describe, it, expect } from "vitest" +import type { RJSFSchema, UiSchema } from "@rjsf/utils" +import { addSensitiveStringWidgets, isSensitiveFieldName } from "./sensitive-fields.ts" + +describe("isSensitiveFieldName", () => { + it("matches the canonical S3/postgres credential field names", () => { + expect(isSensitiveFieldName("s3AccessKey")).toBe(true) + expect(isSensitiveFieldName("s3SecretKey")).toBe(true) + expect(isSensitiveFieldName("secretAccessKey")).toBe(true) + expect(isSensitiveFieldName("secretAccessKeyKey")).toBe(true) + expect(isSensitiveFieldName("accessKey")).toBe(true) + expect(isSensitiveFieldName("access_key")).toBe(true) + expect(isSensitiveFieldName("secret_key")).toBe(true) + }) + + it("matches generic password/token/private-key/api-key style names", () => { + expect(isSensitiveFieldName("password")).toBe(true) + expect(isSensitiveFieldName("rootPassword")).toBe(true) + expect(isSensitiveFieldName("passwordConfirmation")).toBe(true) + expect(isSensitiveFieldName("passwd")).toBe(true) + expect(isSensitiveFieldName("token")).toBe(true) + expect(isSensitiveFieldName("bearerToken")).toBe(true) + expect(isSensitiveFieldName("api_token")).toBe(true) + expect(isSensitiveFieldName("apiKey")).toBe(true) + expect(isSensitiveFieldName("api_key")).toBe(true) + expect(isSensitiveFieldName("privateKey")).toBe(true) + expect(isSensitiveFieldName("private_key")).toBe(true) + }) + + it("is case-insensitive", () => { + expect(isSensitiveFieldName("PASSWORD")).toBe(true) + expect(isSensitiveFieldName("S3SECRETKEY")).toBe(true) + }) + + it("does not match neighbouring/non-secret names", () => { + expect(isSensitiveFieldName("storageClass")).toBe(false) + expect(isSensitiveFieldName("backupClassName")).toBe(false) + expect(isSensitiveFieldName("bucket")).toBe(false) + expect(isSensitiveFieldName("region")).toBe(false) + expect(isSensitiveFieldName("publicKey")).toBe(false) + expect(isSensitiveFieldName("keyName")).toBe(false) + expect(isSensitiveFieldName("name")).toBe(false) + }) + + it("respects word boundaries to avoid false positives on token/passwd", () => { + // `token` is matched only as the trailing word. + expect(isSensitiveFieldName("tokenAudience")).toBe(false) + expect(isSensitiveFieldName("tokenizer")).toBe(false) + expect(isSensitiveFieldName("csrfTokenName")).toBe(false) + // `passwd` likewise — `passwdFile` is a path, not a credential. + expect(isSensitiveFieldName("passwdFile")).toBe(false) + expect(isSensitiveFieldName("etcPasswdPath")).toBe(false) + }) +}) + +describe("addSensitiveStringWidgets", () => { + it("adds the widget to a top-level sensitive string field", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + s3AccessKey: { type: "string" }, + bucket: { type: "string" }, + }, + } + const result = addSensitiveStringWidgets(schema, {}) + expect(result.s3AccessKey).toEqual({ "ui:widget": "SensitiveStringWidget" }) + expect(result.bucket).toBeUndefined() + }) + + it("recurses into nested object schemas", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + backup: { + type: "object", + properties: { + s3SecretKey: { type: "string" }, + bucket: { type: "string" }, + }, + }, + }, + } + const result = addSensitiveStringWidgets(schema, {}) as Record + expect(result.backup?.s3SecretKey).toEqual({ "ui:widget": "SensitiveStringWidget" }) + expect(result.backup?.bucket).toBeUndefined() + }) + + it("masks individual string items when the array key itself is sensitive", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + apiKey: { type: "array", items: { type: "string" } }, + }, + } + const result = addSensitiveStringWidgets(schema, {}) as Record + const itemsUi = (result.apiKey as { items?: UiSchema } | undefined)?.items + expect(itemsUi?.["ui:widget"]).toBe("SensitiveStringWidget") + }) + + it("leaves array-of-strings unmasked when the array key is not sensitive", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + labels: { type: "array", items: { type: "string" } }, + }, + } + const result = addSensitiveStringWidgets(schema, {}) + expect(result.labels).toBeUndefined() + }) + + it("recurses into array items expressed as a tuple of schemas", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + rotations: { + type: "array", + items: [ + { + type: "object", + properties: { + password: { type: "string" }, + }, + }, + ], + }, + }, + } + const result = addSensitiveStringWidgets(schema, {}) as Record + const itemsUi = (result.rotations as { items?: Record } | undefined)?.items + expect(itemsUi?.password).toEqual({ "ui:widget": "SensitiveStringWidget" }) + }) + + it("recurses into array item objects", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + users: { + type: "array", + items: { + type: "object", + properties: { + name: { type: "string" }, + password: { type: "string" }, + }, + }, + }, + }, + } + const result = addSensitiveStringWidgets(schema, {}) as Record + const itemsUi = (result.users as { items?: Record } | undefined)?.items + expect(itemsUi?.password).toEqual({ "ui:widget": "SensitiveStringWidget" }) + expect(itemsUi?.name).toBeUndefined() + }) + + it("ignores non-string fields even with sensitive names", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + accessKey: { type: "integer" }, + password: { type: "boolean" }, + }, + } + const result = addSensitiveStringWidgets(schema, {}) + expect(result.accessKey).toBeUndefined() + expect(result.password).toBeUndefined() + }) + + it("ignores string fields with an enum (rendered as select)", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + password: { type: "string", enum: ["weak", "strong"] }, + }, + } + const result = addSensitiveStringWidgets(schema, {}) + expect(result.password).toBeUndefined() + }) + + it("does not overwrite an existing ui:widget on the same field", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + accessKey: { type: "string" }, + }, + } + const initial: UiSchema = { + accessKey: { "ui:widget": "SomeOtherWidget" }, + } + const result = addSensitiveStringWidgets(schema, initial) + expect(result.accessKey).toEqual({ "ui:widget": "SomeOtherWidget" }) + }) + + it("preserves unrelated uiSchema entries while adding the widget", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + s3AccessKey: { type: "string" }, + bucket: { type: "string" }, + }, + } + const initial: UiSchema = { + bucket: { "ui:placeholder": "my-bucket" }, + } + const result = addSensitiveStringWidgets(schema, initial) + expect(result.s3AccessKey).toEqual({ "ui:widget": "SensitiveStringWidget" }) + expect(result.bucket).toEqual({ "ui:placeholder": "my-bucket" }) + }) + + it("returns the input uiSchema untouched when the schema has no properties", () => { + const initial: UiSchema = { "ui:order": ["a", "b"] } + expect(addSensitiveStringWidgets({} as RJSFSchema, initial)).toEqual(initial) + }) + + it("does not mutate the input uiSchema when array items propagate the widget", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + apiKey: { type: "array", items: { type: "string" } }, + }, + } + const initial: UiSchema = { apiKey: { items: { "ui:placeholder": "foo" } } } + const snapshot: UiSchema = JSON.parse(JSON.stringify(initial)) + addSensitiveStringWidgets(schema, initial) + expect(initial).toEqual(snapshot) + }) + + it("does not mutate the input uiSchema when array items contain a sensitive object field", () => { + const schema: RJSFSchema = { + type: "object", + properties: { + users: { + type: "array", + items: { + type: "object", + properties: { + name: { type: "string" }, + password: { type: "string" }, + }, + }, + }, + }, + } + const initial: UiSchema = { + users: { items: { name: { "ui:placeholder": "alice" } } }, + } + const snapshot: UiSchema = JSON.parse(JSON.stringify(initial)) + addSensitiveStringWidgets(schema, initial) + expect(initial).toEqual(snapshot) + }) + + it("pins the current 'oneOf branches are not walked' limitation", () => { + // FIXME: extend addSensitiveStringWidgets to recurse into oneOf/anyOf/allOf. + // Once that is implemented, flip this test to expect the inner field to + // be masked instead of being left untouched. + const schema: RJSFSchema = { + type: "object", + properties: { + auth: { + oneOf: [ + { + type: "object", + properties: { password: { type: "string" } }, + }, + ], + }, + }, + } as RJSFSchema + const result = addSensitiveStringWidgets(schema, {}) + expect(result.auth).toBeUndefined() + }) +}) diff --git a/apps/console/src/lib/sensitive-fields.ts b/apps/console/src/lib/sensitive-fields.ts new file mode 100644 index 0000000..104e3e9 --- /dev/null +++ b/apps/console/src/lib/sensitive-fields.ts @@ -0,0 +1,169 @@ +import type { RJSFSchema, UiSchema } from "@rjsf/utils" + +// Word tokens that look like credentials wherever they appear in the +// field name. `password` covers `password`, `rootPassword`, +// `passwordConfirmation`; the `*key` entries cover S3-style credentials +// in any prefix (`s3AccessKey`, `myApiKey`, etc.). +const ANYWHERE_TOKENS: ReadonlySet = new Set([ + "accesskey", + "secretkey", + "secretaccesskey", + "password", + "apikey", + "privatekey", +]) + +// Word tokens that look like credentials ONLY when they are the last +// word of the field name, to avoid masking unrelated identifiers that +// happen to contain the word. `bearerToken`/`api_token` match; +// `tokenAudience`/`csrfTokenName`/`passwdFile` do not. +const SUFFIX_ONLY_TOKENS: ReadonlySet = new Set([ + "token", + "passwd", +]) + +/** + * Split a field name into lower-case word parts, treating both + * camelCase boundaries and `_` / `-` / `.` as word separators. Digits + * stay attached to the preceding letter (`s3` ⇒ `s3`). + */ +function splitWords(name: string): string[] { + return name + .replace(/([a-z0-9])([A-Z])/g, "$1 $2") + .split(/[\s_\-.]+/) + .filter(Boolean) + .map((part) => part.toLowerCase()) +} + +/** + * Return every joined run of consecutive word parts, so that + * `secretAccessKey` and `secret_access_key` both produce the chunk + * `secretaccesskey` for matching, without the detector having to + * enumerate every casing/concatenation variant. + */ +function chunks(words: string[]): string[] { + const out: string[] = [] + for (let start = 0; start < words.length; start++) { + let chunk = "" + for (let end = start; end < words.length; end++) { + chunk += words[end] + out.push(chunk) + } + } + return out +} + +/** + * Returns true when a field name looks like a credential by convention. + * Cozystack chart schemas do not annotate sensitive fields with a + * `format: password` hint, so detection is purely name-based. The match + * is on word boundaries (camelCase / `_` / `-` / `.`) rather than a raw + * substring, so neighbours like `tokenAudience` or `passwdFile` stay + * out of the set. + */ +export function isSensitiveFieldName(name: string): boolean { + const words = splitWords(name) + if (words.length === 0) return false + + for (const chunk of chunks(words)) { + if (ANYWHERE_TOKENS.has(chunk)) return true + } + + const last = words[words.length - 1] + return SUFFIX_ONLY_TOKENS.has(last) +} + +interface JsonSchemaLike { + type?: string + enum?: unknown[] + properties?: Record + // JSON Schema allows `items` to be a single schema or a tuple of schemas. + items?: JsonSchemaLike | JsonSchemaLike[] +} + +/** + * Recursively walks `schema` and, for every string field whose name matches + * a sensitive pattern, sets `ui:widget` to `SensitiveStringWidget` on the + * corresponding entry in `uiSchema`. Existing widget bindings are preserved. + * + * The input `uiSchema` is not mutated; the returned object is independent. + * + * Limitation: only `properties` and `items` subtrees are walked. Fields + * inside `oneOf` / `anyOf` / `allOf` branches are NOT inspected — see the + * "pin broken behaviour" test in sensitive-fields.test.ts. Extend when a + * chart in the catalogue starts using that shape. + */ +export function addSensitiveStringWidgets( + schema: RJSFSchema, + uiSchema: UiSchema = {}, +): UiSchema { + if (!schema || typeof schema !== "object") return uiSchema + const properties = (schema as JsonSchemaLike).properties + if (!properties) return uiSchema + + const result: UiSchema = { ...uiSchema } + + for (const [key, fieldSchema] of Object.entries(properties)) { + if (!fieldSchema || typeof fieldSchema !== "object") continue + + const existing = (result[key] ?? {}) as UiSchema + const alreadyHasWidget = typeof existing["ui:widget"] === "string" + + const isLeafString = + fieldSchema.type === "string" && !Array.isArray(fieldSchema.enum) + + if (isLeafString && isSensitiveFieldName(key) && !alreadyHasWidget) { + result[key] = { ...existing, "ui:widget": "SensitiveStringWidget" } + continue + } + + if (fieldSchema.properties) { + result[key] = addSensitiveStringWidgets( + fieldSchema as RJSFSchema, + existing, + ) + continue + } + + if (fieldSchema.type === "array" && fieldSchema.items) { + const itemSchemas = Array.isArray(fieldSchema.items) + ? fieldSchema.items + : [fieldSchema.items] + // Shallow-copy so we never write through into the caller's items + // sub-object — see "does not mutate the input uiSchema" tests. + const merged: UiSchema = { ...((existing.items ?? {}) as UiSchema) } + let mutated = false + const keyIsSensitive = isSensitiveFieldName(key) + for (const itemSchema of itemSchemas) { + if (!itemSchema) continue + if (itemSchema.properties) { + const next = addSensitiveStringWidgets( + itemSchema as RJSFSchema, + merged, + ) + for (const [k, v] of Object.entries(next)) merged[k] = v + mutated = true + continue + } + // Array of scalar credentials, e.g. `apiKey: { items: {type:"string"} }`. + // The array key itself is the only signal we have for individual items, + // so propagate the widget down only when the array's name matches. + const itemIsLeafString = + itemSchema.type === "string" && !Array.isArray(itemSchema.enum) + if ( + itemIsLeafString && + keyIsSensitive && + typeof merged["ui:widget"] !== "string" + ) { + merged["ui:widget"] = "SensitiveStringWidget" + mutated = true + } + } + if (mutated) { + result[key] = { ...existing, items: merged } + } + } + } + + return result +} diff --git a/apps/console/test/setup.ts b/apps/console/test/setup.ts new file mode 100644 index 0000000..8d02416 --- /dev/null +++ b/apps/console/test/setup.ts @@ -0,0 +1,10 @@ +import "@testing-library/jest-dom/vitest" +import { afterEach } from "vitest" +import { cleanup } from "@testing-library/react" + +// React Testing Library auto-registers cleanup via a global `afterEach` +// hook; that hook only exists when vitest is run with `globals: true`. +// We use explicit imports in tests, so wire cleanup up manually here. +afterEach(() => { + cleanup() +}) diff --git a/apps/console/tsconfig.json b/apps/console/tsconfig.json index bf7ac6d..82e0a3d 100644 --- a/apps/console/tsconfig.json +++ b/apps/console/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", - "types": ["vite/client"], + "types": ["vite/client", "@testing-library/jest-dom"], "noEmit": true, "baseUrl": ".", "paths": { diff --git a/apps/console/vitest.config.ts b/apps/console/vitest.config.ts new file mode 100644 index 0000000..deaa099 --- /dev/null +++ b/apps/console/vitest.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "vitest/config" +import react from "@vitejs/plugin-react" +import path from "path" + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + "@/": path.resolve(__dirname, "./src/"), + }, + }, + test: { + environment: "jsdom", + setupFiles: ["./test/setup.ts"], + include: ["src/**/*.{test,spec}.{ts,tsx}"], + }, +}) diff --git a/package.json b/package.json index f9b6ab8..56a5c38 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build": "pnpm -r build", "build:console": "pnpm --filter @cozystack/console build", "lint": "pnpm -r lint", + "test": "pnpm -r --if-present test", "typecheck": "pnpm -r typecheck" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a779cd4..8014574 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,6 +84,15 @@ importers: '@tailwindcss/vite': specifier: ^4.2.2 version: 4.2.2(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)) + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) '@types/js-yaml': specifier: ^4.0.9 version: 4.0.9 @@ -96,6 +105,12 @@ importers: '@vitejs/plugin-react': specifier: ^6.0.1 version: 6.0.1(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)) + '@vitest/ui': + specifier: ^4.1.6 + version: 4.1.6(vitest@4.1.6) + jsdom: + specifier: ^29.1.1 + version: 29.1.1 tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -105,6 +120,9 @@ importers: vite: specifier: ^8.0.4 version: 8.0.8(@types/node@24.12.2)(jiti@2.6.1) + vitest: + specifier: ^4.1.6 + version: 4.1.6(@types/node@24.12.2)(@vitest/ui@4.1.6)(jsdom@29.1.1)(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)) packages/k8s-client: dependencies: @@ -177,6 +195,24 @@ importers: packages: + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -298,6 +334,46 @@ packages: '@types/react': optional: true + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.0': + resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.0': + resolution: {integrity: sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.3': + resolution: {integrity: sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@emnapi/core@1.9.2': resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} @@ -345,6 +421,15 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.15.0': + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@floating-ui/core@1.7.5': resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} @@ -421,6 +506,9 @@ packages: '@oxc-project/types@0.124.0': resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rjsf/core@5.24.13': resolution: {integrity: sha512-ONTr14s7LFIjx2VRFLuOpagL76sM/HPy6/OhdBfq6UukINmTIs6+aFN0GgcR0aXQHFDXQ7f/fel0o/SO05Htdg==} engines: {node: '>=14'} @@ -541,6 +629,9 @@ packages: '@rolldown/pluginutils@1.0.0-rc.7': resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@tailwindcss/node@4.2.2': resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} @@ -643,9 +734,47 @@ packages: peerDependencies: react: ^18 || ^19 + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + 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 + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -741,6 +870,40 @@ packages: babel-plugin-react-compiler: optional: true + '@vitest/expect@4.1.6': + resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==} + + '@vitest/mocker@4.1.6': + resolution: {integrity: sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.6': + resolution: {integrity: sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==} + + '@vitest/runner@4.1.6': + resolution: {integrity: sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==} + + '@vitest/snapshot@4.1.6': + resolution: {integrity: sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==} + + '@vitest/spy@4.1.6': + resolution: {integrity: sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==} + + '@vitest/ui@4.1.6': + resolution: {integrity: sha512-wiu5em68DfGv/2HFvI1Njr7JI2CHcBlQvereSzVG8my53PRxjTNOCsD9VOkRKrsJBDHmyuXvosxWZw7T91a2mw==} + peerDependencies: + vitest: 4.1.6 + + '@vitest/utils@4.1.6': + resolution: {integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -765,13 +928,32 @@ packages: ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -784,6 +966,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + brace-expansion@1.1.14: resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} @@ -803,6 +988,10 @@ packages: caniuse-lite@1.0.30001788: resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -841,9 +1030,20 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -853,13 +1053,26 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dompurify@3.2.7: resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} @@ -870,6 +1083,13 @@ packages: resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -931,10 +1151,17 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -956,6 +1183,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1005,6 +1235,10 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1021,6 +1255,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1029,6 +1267,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1043,6 +1284,15 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsdom@29.1.1: + resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1174,6 +1424,10 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@11.3.6: + resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1182,6 +1436,10 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1199,6 +1457,13 @@ packages: engines: {node: '>= 18'} hasBin: true + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -1209,6 +1474,10 @@ packages: monaco-editor@0.55.1: resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1227,6 +1496,9 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1243,6 +1515,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1251,6 +1526,9 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1266,6 +1544,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -1281,6 +1563,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -1298,6 +1583,10 @@ packages: resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} engines: {node: '>=0.10.0'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -1314,6 +1603,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -1337,13 +1630,30 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + state-local@1.0.7: resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1352,6 +1662,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} @@ -1365,10 +1678,40 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + engines: {node: '>=18'} + tinyglobby@0.2.16: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.30: + resolution: {integrity: sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==} + + tldts@7.0.30: + resolution: {integrity: sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==} + hasBin: true + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -1397,6 +1740,10 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + engines: {node: '>=20.18.1'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -1469,15 +1816,84 @@ packages: yaml: optional: true + vitest@4.1.6: + resolution: {integrity: sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.6 + '@vitest/browser-preview': 4.1.6 + '@vitest/browser-webdriverio': 4.1.6 + '@vitest/coverage-istanbul': 4.1.6 + '@vitest/coverage-v8': 4.1.6 + '@vitest/ui': 4.1.6 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -1496,6 +1912,28 @@ packages: snapshots: + '@adobe/css-tools@4.4.4': {} + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.1.1': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -1646,6 +2084,34 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.3(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@emnapi/core@1.9.2': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -1708,6 +2174,8 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@exodus/bytes@1.15.0': {} + '@floating-ui/core@1.7.5': dependencies: '@floating-ui/utils': 0.2.11 @@ -1784,6 +2252,8 @@ snapshots: '@oxc-project/types@0.124.0': {} + '@polka/url@1.0.0-next.29': {} + '@rjsf/core@5.24.13(@rjsf/utils@5.24.13(react@19.2.5))(react@19.2.5)': dependencies: '@rjsf/utils': 5.24.13(react@19.2.5) @@ -1863,6 +2333,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.7': {} + '@standard-schema/spec@1.1.0': {} + '@tailwindcss/node@4.2.2': dependencies: '@jridgewell/remapping': 2.3.5 @@ -1938,11 +2410,54 @@ snapshots: '@tanstack/query-core': 5.99.1 react: 19.2.5 + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 optional: true + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/js-yaml@4.0.9': {} @@ -2060,6 +2575,58 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.7 vite: 8.0.8(@types/node@24.12.2)(jiti@2.6.1) + '@vitest/expect@4.1.6': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.6(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1))': + dependencies: + '@vitest/spy': 4.1.6 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.8(@types/node@24.12.2)(jiti@2.6.1) + + '@vitest/pretty-format@4.1.6': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.6': + dependencies: + '@vitest/utils': 4.1.6 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.6': + dependencies: + '@vitest/pretty-format': 4.1.6 + '@vitest/utils': 4.1.6 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.6': {} + + '@vitest/ui@4.1.6(vitest@4.1.6)': + dependencies: + '@vitest/utils': 4.1.6 + fflate: 0.8.2 + flatted: 3.4.2 + pathe: 2.0.3 + sirv: 3.0.2 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vitest: 4.1.6(@types/node@24.12.2)(@vitest/ui@4.1.6)(jsdom@29.1.1)(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)) + + '@vitest/utils@4.1.6': + dependencies: + '@vitest/pretty-format': 4.1.6 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -2084,18 +2651,34 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-regex@5.0.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} baseline-browser-mapping@2.10.20: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 @@ -2117,6 +2700,8 @@ snapshots: caniuse-lite@1.0.30001788: {} + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2159,16 +2744,38 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + csstype@3.2.3: {} + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + deep-is@0.1.4: {} + dequal@2.0.3: {} + detect-libc@2.1.2: {} + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + dompurify@3.2.7: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -2180,6 +2787,10 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.2 + entities@8.0.0: {} + + es-module-lexer@2.1.0: {} + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -2267,8 +2878,14 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} + expect-type@1.3.0: {} + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -2281,6 +2898,8 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fflate@0.8.2: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -2320,6 +2939,12 @@ snapshots: dependencies: hermes-estree: 0.25.1 + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.0 + transitivePeerDependencies: + - '@noble/hashes' + ignore@5.3.2: {} ignore@7.0.5: {} @@ -2331,12 +2956,16 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} jiti@2.6.1: {} @@ -2347,6 +2976,32 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@29.1.1: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@exodus/bytes': 1.15.0 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.3.6 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.25.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -2443,6 +3098,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@11.3.6: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -2451,6 +3108,8 @@ snapshots: dependencies: react: 19.2.5 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2461,6 +3120,10 @@ snapshots: marked@14.0.0: {} + mdn-data@2.27.1: {} + + min-indent@1.0.1: {} + minimatch@10.2.5: dependencies: brace-expansion: 5.0.5 @@ -2474,6 +3137,8 @@ snapshots: dompurify: 3.2.7 marked: 14.0.0 + mrmime@2.0.1: {} + ms@2.1.3: {} nanoid@3.3.11: {} @@ -2484,6 +3149,8 @@ snapshots: object-assign@4.1.1: {} + obug@2.1.1: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2505,10 +3172,16 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@8.0.1: + dependencies: + entities: 8.0.0 + path-exists@4.0.0: {} path-key@3.1.1: {} + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@4.0.4: {} @@ -2521,6 +3194,12 @@ snapshots: prelude-ls@1.2.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -2536,6 +3215,8 @@ snapshots: react-is@16.13.1: {} + react-is@17.0.2: {} + react-is@18.3.1: {} react-router@7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5): @@ -2548,6 +3229,11 @@ snapshots: react@19.2.5: {} + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + require-from-string@2.0.2: {} reselect@5.1.1: {} @@ -2575,6 +3261,10 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15 + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.27.0: {} semver@6.3.1: {} @@ -2589,16 +3279,34 @@ snapshots: shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + source-map-js@1.2.1: {} + stackback@0.0.2: {} + state-local@1.0.7: {} + std-env@4.1.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@3.1.1: {} supports-color@7.2.0: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + tabbable@6.4.0: {} tailwind-merge@3.5.0: {} @@ -2607,11 +3315,33 @@ snapshots: tapable@2.3.2: {} + tinybench@2.9.0: {} + + tinyexec@1.1.2: {} + tinyglobby@0.2.16: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@3.1.0: {} + + tldts-core@7.0.30: {} + + tldts@7.0.30: + dependencies: + tldts-core: 7.0.30 + + totalist@3.0.1: {} + + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.30 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 @@ -2638,6 +3368,8 @@ snapshots: undici-types@7.16.0: {} + undici@7.25.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -2679,12 +3411,66 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 + vitest@4.1.6(@types/node@24.12.2)(@vitest/ui@4.1.6)(jsdom@29.1.1)(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)): + dependencies: + '@vitest/expect': 4.1.6 + '@vitest/mocker': 4.1.6(vite@8.0.8(@types/node@24.12.2)(jiti@2.6.1)) + '@vitest/pretty-format': 4.1.6 + '@vitest/runner': 4.1.6 + '@vitest/snapshot': 4.1.6 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.8(@types/node@24.12.2)(jiti@2.6.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.12.2 + '@vitest/ui': 4.1.6(vitest@4.1.6) + jsdom: 29.1.1 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.0 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + yallist@3.1.1: {} yocto-queue@0.1.0: {}