diff --git a/.github/workflows/push-tagged-release.yml b/.github/workflows/push-tagged-release.yml index 8ab76f4e..f858e948 100644 --- a/.github/workflows/push-tagged-release.yml +++ b/.github/workflows/push-tagged-release.yml @@ -54,6 +54,8 @@ jobs: run: yarn install - name: Create dist JS run: yarn build:all + - name: Check package consumers + run: yarn test:package:consumer - name: Create jest results # only use for final releases because it is necessary only in addition to storybook if: ${{ inputs.onlyNpmPush != true && inputs.sectionChangelog != 'Unreleased' }} diff --git a/.github/workflows/test-code.yml b/.github/workflows/test-code.yml index 33004034..efd36c53 100644 --- a/.github/workflows/test-code.yml +++ b/.github/workflows/test-code.yml @@ -4,12 +4,16 @@ on: pull_request: paths: - ".github/workflows/test-code.yml" + - ".typescript/**" + - "scripts/**" + - "tests/package-consumer/**" - "src/**.js" - "src/**.ts" - "src/**.tsx" - "index.ts" - "**.scss" - package.json + - tsconfig.json - yarn.lock jobs: @@ -24,4 +28,5 @@ jobs: - run: yarn install - run: yarn compile - run: yarn compile-scss + - run: yarn test:package - run: yarn test:ci diff --git a/.github/workflows/test-storybook.yml b/.github/workflows/test-storybook.yml new file mode 100644 index 00000000..1c816978 --- /dev/null +++ b/.github/workflows/test-storybook.yml @@ -0,0 +1,33 @@ +name: "Test: Storybook browser accessibility" + +on: + pull_request: + paths: + - ".github/workflows/test-storybook.yml" + - ".storybook/**" + - ".typescript/**" + - "src/**" + - "scripts/**" + - "package.json" + - "yarn.lock" + +jobs: + browser-tests: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "24.11.1" + - run: yarn install --frozen-lockfile + - run: yarn test:storybook:types + - run: yarn test:storybook:runner + - run: yarn playwright install --with-deps chromium + - run: yarn build-storybook --output-dir .local/storybook + - run: yarn test:storybook:ci --json --outputFile .local/storybook-results.json + - uses: actions/upload-artifact@v4 + if: always() + with: + name: storybook-browser-results + path: .local/storybook-results.json diff --git a/.gitignore b/.gitignore index ba70b2d0..be339e5c 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,4 @@ storybook-static # ignore Typescript assets *.tsbuildinfo +.local/ diff --git a/.storybook/tests/file-upload.mts b/.storybook/tests/file-upload.mts new file mode 100644 index 00000000..67345a94 --- /dev/null +++ b/.storybook/tests/file-upload.mts @@ -0,0 +1,181 @@ +import { Buffer } from "node:buffer"; +import type { Locator, Page } from "playwright"; +import { expect } from "playwright/test"; + +interface FileUploadScenario { + storyId: `forms-fileupload--${string}`; + description: string; + run: (page: Page) => Promise; +} + +function uploadGroup(page: Page): Locator { + return page.getByRole("group", { name: "Upload graph file", exact: true }); +} + +function browseButton(page: Page): Locator { + return uploadGroup(page).getByRole("button", { name: /browse files$/i }); +} + +function fileRow(page: Page, fileName: string): Locator { + return uploadGroup(page) + .getByRole("listitem") + .filter({ has: page.getByText(fileName, { exact: true }) }); +} + +async function testKeyboardSelection(page: Page, key: "Enter" | "Space"): Promise { + // Begin outside the controls so Tab, rather than programmatic focus, reaches Browse. + await uploadGroup(page).focus(); + await page.keyboard.press("Tab"); + await expect(browseButton(page)).toBeFocused(); + + let chooserCount = 0; + const countChooser = () => { + chooserCount += 1; + }; + page.on("filechooser", countChooser); + try { + const chooserPromise = page.waitForEvent("filechooser"); + await page.keyboard.press(key); + const chooser = await chooserPromise; + await chooser.setFiles({ name: "keyboard.ttl", mimeType: "text/turtle", buffer: Buffer.from("data") }); + + await expect(fileRow(page, "keyboard.ttl")).toBeVisible(); + expect(chooserCount, "Each key activation must open exactly one chooser").toBe(1); + await expect(page.getByRole("status")).toContainText("Selected keyboard.ttl"); + } finally { + page.off("filechooser", countChooser); + } +} + +async function testDisabledSelection(page: Page): Promise { + const browse = browseButton(page); + await expect(browse).toBeDisabled(); + + await uploadGroup(page).focus(); + await page.keyboard.press("Tab"); + + await expect(browse).not.toBeFocused(); +} + +async function testKeyboardCancellation(page: Page): Promise { + const group = uploadGroup(page); + await expect(fileRow(page, "graph.ttl")).toHaveAttribute("data-state", "uploading"); + await expect(group).toHaveAttribute("aria-busy", "true"); + + await group.getByRole("button", { name: "Cancel upload", exact: true }).focus(); + await page.keyboard.press("Space"); + + await expect(fileRow(page, "graph.ttl")).toHaveAttribute("data-state", "cancelled"); + await expect(group).not.toHaveAttribute("aria-busy"); + await expect(group.getByRole("listitem"), "Cancellation retains the file").toHaveCount(1); + await expect(group.getByRole("progressbar")).toHaveAttribute("aria-valuetext", "Upload cancelled"); + await expect(group.getByText("Upload cancelled", { exact: true })).toBeVisible(); +} + +async function waitForCancelledFiles(page: Page, fileNames: readonly string[]): Promise { + // CancelledFiles.play completes the first file and stops the remaining uploads. + await expect(fileRow(page, "first.ttl")).toHaveAttribute("data-state", "complete"); + for (const fileName of fileNames) { + await expect(fileRow(page, fileName)).toHaveAttribute("data-state", "cancelled"); + } + await expect(uploadGroup(page)).not.toHaveAttribute("aria-busy"); +} + +async function testFocusAfterRemoval(page: Page): Promise { + await waitForCancelledFiles(page, ["second.ttl", "third.ttl"]); + const removeSecond = fileRow(page, "second.ttl").getByRole("button", { name: "Remove", exact: true }); + const removeThird = fileRow(page, "third.ttl").getByRole("button", { name: "Remove", exact: true }); + + await removeSecond.focus(); + await page.keyboard.press("Enter"); + + await expect(fileRow(page, "second.ttl")).toHaveCount(0); + await expect(removeThird).toBeFocused(); + await expect(uploadGroup(page).getByRole("button", { name: "Remove", exact: true })).toHaveCount(1); + await expect(page.getByRole("status")).toContainText("second.ttl removed"); + + await page.keyboard.press("Space"); + + await expect(fileRow(page, "third.ttl")).toHaveCount(0); + await expect(browseButton(page)).toBeFocused(); + await expect(page.getByRole("status")).toContainText("third.ttl removed"); +} + +async function testContinueRetainedFiles(page: Page): Promise { + await waitForCancelledFiles(page, ["second.ttl"]); + // RemovedBeforeContinue.play also removes the third file before this interaction. + await expect(fileRow(page, "third.ttl")).toHaveCount(0); + await expect( + uploadGroup(page).getByRole("progressbar", { name: "Overall upload progress", exact: true }), + ).toHaveAttribute("aria-valuenow", "50"); + + await uploadGroup(page).getByRole("button", { name: "Continue uploads", exact: true }).focus(); + await page.keyboard.press("Enter"); + + await expect(fileRow(page, "second.ttl")).toHaveAttribute("data-state", "uploading"); + await expect(fileRow(page, "third.ttl")).toHaveCount(0); + await expect(uploadGroup(page).getByRole("listitem")).toHaveCount(2); + await expect(uploadGroup(page)).toHaveAttribute("aria-busy", "true"); +} + +async function testRetryAfterError(page: Page): Promise { + const row = fileRow(page, "retry.ttl"); + await expect(row).toHaveAttribute("data-state", "error"); + await expect(page.getByRole("alert")).toBeVisible(); + + await row.getByRole("button", { name: "Retry", exact: true }).focus(); + await page.keyboard.press("Enter"); + + await expect(row).toHaveAttribute("data-state", "complete"); + await expect(page.getByRole("alert")).toHaveCount(0); + await expect(page.getByRole("status")).toContainText("retry.ttl uploaded"); + await expect(uploadGroup(page)).not.toHaveAttribute("aria-busy"); +} + +export const fileUploadScenarios = [ + { + storyId: "forms-fileupload--keyboard-enter", + description: "Enter opens one file chooser and announces the selection", + run: (page) => testKeyboardSelection(page, "Enter"), + }, + { + storyId: "forms-fileupload--keyboard-space", + description: "Space opens one file chooser and announces the selection", + run: (page) => testKeyboardSelection(page, "Space"), + }, + { + storyId: "forms-fileupload--keyboard-in-dialog", + description: "Enter opens one file chooser inside a dialog", + run: (page) => testKeyboardSelection(page, "Enter"), + }, + { + storyId: "forms-fileupload--disabled", + description: "A disabled upload control skips Browse in the tab order", + run: testDisabledSelection, + }, + { + storyId: "forms-fileupload--selection-disabled", + description: "Disabled file selection skips Browse in the tab order", + run: testDisabledSelection, + }, + { + storyId: "forms-fileupload--uploading", + description: "Space cancels an upload and retains its file", + run: testKeyboardCancellation, + }, + { + storyId: "forms-fileupload--keyboard-removal", + description: "Removing files moves focus to the next Remove button, then Browse", + run: testFocusAfterRemoval, + }, + { + storyId: "forms-fileupload--removed-before-continue", + description: "Continue resumes retained files without restoring a removed file", + run: testContinueRetainedFiles, + }, + { + storyId: "forms-fileupload--retry-after-error", + description: "Enter retries a failed upload and announces success", + run: testRetryAfterError, + }, +] satisfies readonly FileUploadScenario[]; diff --git a/.storybook/tests/runner.test.mts b/.storybook/tests/runner.test.mts new file mode 100644 index 00000000..9ea1366f --- /dev/null +++ b/.storybook/tests/runner.test.mts @@ -0,0 +1,16 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import process from "node:process"; +import { fileURLToPath } from "node:url"; + +const runner = fileURLToPath(new URL("../../scripts/run-storybook-tests.mts", import.meta.url)); + +const invalidArgs = [["--url"], ["--url", ""], ["--url", "--ci"]]; +for (const args of invalidArgs) { + const result = spawnSync(process.execPath, [runner, ...args], { encoding: "utf8", timeout: 10_000 }); + + assert.ifError(result.error); + assert.equal(result.status, 1, `Expected rejection of ${JSON.stringify(args)}`); + assert.match(result.stderr, /--url requires a URL value/); +} +process.stdout.write(`Passed ${invalidArgs.length} Storybook runner argument checks.\n`); diff --git a/.typescript/tsbuild-cjs.json b/.typescript/tsbuild-cjs.json index 7b081393..942e068c 100644 --- a/.typescript/tsbuild-cjs.json +++ b/.typescript/tsbuild-cjs.json @@ -1,7 +1,8 @@ { "extends": "./../tsconfig.json", "compilerOptions": { - "module": "commonjs", + "module": "node20", + "moduleResolution": "node16", "target": "es2015", "noEmit": false, "outDir": "../dist/cjs", diff --git a/.typescript/tscheck-fileupload.json b/.typescript/tscheck-fileupload.json new file mode 100644 index 00000000..0b358270 --- /dev/null +++ b/.typescript/tscheck-fileupload.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["../scripts/type-tests/file-upload.tsx", "../declarations.d.ts"] +} diff --git a/.typescript/tscheck-package-consumer.json b/.typescript/tscheck-package-consumer.json new file mode 100644 index 00000000..b28ccce5 --- /dev/null +++ b/.typescript/tscheck-package-consumer.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "verbatimModuleSyntax": true, + "erasableSyntaxOnly": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["../scripts/test-package-consumer.mts"] +} diff --git a/.typescript/tscheck-storybook.json b/.typescript/tscheck-storybook.json new file mode 100644 index 00000000..7dd8e4ec --- /dev/null +++ b/.typescript/tscheck-storybook.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "erasableSyntaxOnly": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["../.storybook/tests/**/*.mts", "../scripts/run-storybook-tests.mts", "../scripts/test-storybook.mts"] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index fe232e53..dce6a173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Added +- `); expect(screen.getByRole("button")).toHaveTextContent(/default button/i); diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 471e6cb7..ef789b06 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -73,21 +73,31 @@ export type ButtonProps = ExtendedButtonProps & ExtendedAnchorButtonProps; * Display a button element to enable user interaction. * It normally should trigger action when clicked. */ -export const Button = ({ - children, - className = "", - affirmative = false, - disruptive = false, - elevated = false, - icon, - rightIcon, - tooltip = null, - tooltipProps, - badge, - badgeProps = { size: "small", position: "top-right", maxLength: 2 }, - intent, - ...restProps -}: ButtonProps) => { +export const Button = React.forwardRef(function Button( + { + children, + className = "", + affirmative = false, + disruptive = false, + elevated = false, + icon, + rightIcon, + tooltip = null, + tooltipProps, + badge, + badgeProps = { size: "small", position: "top-right", maxLength: 2 }, + intent, + ...restProps + }, + ref, +) { + const setElementRef = React.useCallback( + (element: HTMLButtonElement | HTMLAnchorElement | null) => { + if (typeof ref === "function") ref(element); + else if (ref) ref.current = element; + }, + [ref], + ); let intentByFunction; switch (true) { case affirmative || elevated: @@ -110,6 +120,7 @@ export const Button = ({ const button = ( : icon} @@ -137,7 +148,7 @@ export const Button = ({ ) : ( button ); -}; +}); interface constructBadgePropertiesProps extends Pick, Pick {} diff --git a/src/components/FileUpload/FileUpload.stories.tsx b/src/components/FileUpload/FileUpload.stories.tsx new file mode 100644 index 00000000..aaf052a0 --- /dev/null +++ b/src/components/FileUpload/FileUpload.stories.tsx @@ -0,0 +1,299 @@ +import React from "react"; +import { Meta, StoryFn } from "@storybook/react"; +import { userEvent, waitFor, within } from "storybook/test"; + +import { FileUpload, FileUploadFile, FileUploadProps, SimpleDialog } from "../../index"; + +const defaultArgs: FileUploadProps = { + name: "Upload graph file", + endpoint: "/files", + acceptedFileTypes: [".ttl", ".nt", ".rdf"], + maxFileSize: 10_000_000, + labels: { + cancelFile: "Cancel upload", + continueUpload: "Continue uploads", + dropHereOr: "Drop a graph file here or", + browse: "browse files", + removeFile: "Remove", + removedFile: (file) => `${file.name} removed`, + formatError: ({ kind, error }) => + kind === "response" ? `Invalid response: ${error.message}` : `Upload failed: ${error.message}`, + uploadProgress: "Files", + overallUploadProgress: "Overall upload progress", + retry: "Retry", + completedFiles: (completed, total) => `${completed} of ${total} files completed`, + fileUploadProgress: (file) => `Upload progress for ${file.name}`, + selectedFile: (file) => `Selected ${file.name}`, + uploadedFile: (file) => `${file.name} uploaded`, + stopUploads: "Stop uploads", + uploadCancelled: "Upload cancelled", + }, + instructions: "Turtle, N-Triples or RDF/XML; maximum 10 MB. Press Enter or Space to browse.", +}; + +export default { + title: "Forms/FileUpload", + tags: ["browser-test"], + component: FileUpload, + args: defaultArgs, + parameters: { + a11y: { test: "error" }, + docs: { + description: { + component: + "Uploads start automatically by default. Set autoUpload={false} and call ref.upload() for manual uploads. " + + "beforeUpload accepts a boolean or Promise and runs after selection, including in manual mode. " + + "Use onUploadSuccess for per-file side effects; onComplete reports cumulative retained results. " + + "See src/components/FileUpload/README.md in the package for complete consumer examples and lifecycle guidance.", + }, + }, + }, +} as Meta; + +const Template: StoryFn = (args) => ; + +export const Idle = Template.bind({}); + +export const KeyboardEnter = Template.bind({}); +KeyboardEnter.args = { autoUpload: false }; +export const KeyboardSpace = Template.bind({}); +KeyboardSpace.args = { autoUpload: false }; +export const SelectionDisabled = Template.bind({}); +SelectionDisabled.args = { selectionDisabled: true }; + +export const KeyboardInDialog: StoryFn = (args) => ( + undefined}> + + +); + +const DraggingTemplate: StoryFn = (args) => { + const storyRef = React.useRef(null); + + React.useEffect(() => { + storyRef.current + ?.querySelector('[data-dropzone-for="Files"]') + ?.dispatchEvent(new Event("dragenter", { bubbles: true, cancelable: true })); + }, []); + + return ( +
+ +
+ ); +}; + +export const Dragging = DraggingTemplate.bind({}); + +export const Disabled = Template.bind({}); +Disabled.args = { + disabled: true, +}; + +type RequestOutcome = "uploading" | "complete" | "error"; + +interface StoryUploadState { + progress: number; + outcome: RequestOutcome; +} + +const storyFile = (name: string) => new File([name.repeat(32)], name, { type: "text/turtle" }); + +const createStoryXMLHttpRequest = (stateForFile: (fileName: string) => StoryUploadState) => + class StoryXMLHttpRequest { + response: unknown; + responseText = ""; + responseType: XMLHttpRequestResponseType = ""; + status = 0; + statusText = ""; + withCredentials = false; + onerror: (() => void) | null = null; + onload: (() => void) | null = null; + upload = { onprogress: null as ((event: ProgressEvent) => void) | null }; + + private url = ""; + private timers: number[] = []; + + abort() { + this.timers.forEach((timer) => window.clearTimeout(timer)); + this.timers = []; + } + + open(_method: string, url: string) { + this.url = url; + } + + send() { + const fileName = decodeURIComponent(this.url.split("/").pop() ?? ""); + const state = stateForFile(fileName); + this.schedule(() => { + this.upload.onprogress?.({ + lengthComputable: true, + loaded: state.progress, + total: 100, + } as ProgressEvent); + }, 50); + if (state.outcome === "complete") { + this.schedule(() => { + this.status = 200; + this.responseText = `${fileName}-upload-id`; + this.onload?.(); + }, 100); + } else if (state.outcome === "error") { + this.schedule(() => { + this.status = 400; + this.statusText = "Upload rejected"; + this.onload?.(); + }, 100); + } + } + + setRequestHeader() {} + + private schedule(callback: () => void, delay: number) { + this.timers.push(window.setTimeout(callback, delay)); + } + }; + +interface UploadStateStoryProps { + args: FileUploadProps; + files: File[]; + stateForFile: (fileName: string) => StoryUploadState; +} + +const UploadStateStory = ({ args, files, stateForFile }: UploadStateStoryProps) => { + const [transportReady, setTransportReady] = React.useState(false); + + React.useEffect(() => { + const nativeXMLHttpRequest = window.XMLHttpRequest; + window.XMLHttpRequest = createStoryXMLHttpRequest(stateForFile) as unknown as typeof XMLHttpRequest; + setTransportReady(true); + return () => { + window.XMLHttpRequest = nativeXMLHttpRequest; + }; + }, [stateForFile]); + + return ( +
+ {transportReady && ( + `/storybook-upload/${encodeURIComponent(file.name)}`} + maxNumberOfFiles={Math.max(args.maxNumberOfFiles ?? 1, files.length)} + /> + )} +
+ ); +}; + +const uploadingState = () => ({ outcome: "uploading", progress: 45 }) as const; +const completedState = () => ({ outcome: "complete", progress: 100 }) as const; +const errorState = () => ({ outcome: "error", progress: 25 }) as const; + +const waitForProgress = async (canvasElement: HTMLElement, name: string, value: string) => { + await waitFor( + () => { + const progressbar = within(canvasElement).getByRole("progressbar", { name }); + if (progressbar.getAttribute("aria-valuenow") !== value) { + throw new Error(`Expected ${name} to reach ${value}%`); + } + }, + { timeout: 3_000 }, + ); +}; + +const waitForAlert = async (canvasElement: HTMLElement) => { + await waitFor(() => within(canvasElement).getByRole("alert"), { timeout: 3_000 }); +}; + +export const Uploading: StoryFn = (args) => ( + +); +Uploading.play = ({ canvasElement }) => waitForProgress(canvasElement, "Upload progress for graph.ttl", "45"); + +export const Completed: StoryFn = (args) => ( + +); +Completed.play = ({ canvasElement }) => waitForProgress(canvasElement, "Upload progress for graph.ttl", "100"); + +const multipleFiles = [storyFile("first.ttl"), storyFile("second.ttl"), storyFile("third.ttl")]; +const multipleUploadingState = (fileName: string): StoryUploadState => { + if (fileName === "first.ttl") return { outcome: "complete", progress: 100 }; + if (fileName === "second.ttl") return { outcome: "uploading", progress: 65 }; + return { outcome: "uploading", progress: 20 }; +}; + +export const MultipleFilesUploading: StoryFn = (args) => ( + +); +MultipleFilesUploading.args = { concurrency: 3 }; +MultipleFilesUploading.play = ({ canvasElement }) => + waitForProgress(canvasElement, "Upload progress for second.ttl", "65"); + +const mixedResultState = (fileName: string): StoryUploadState => { + if (fileName === "first.ttl") return { outcome: "complete", progress: 100 }; + if (fileName === "second.ttl") return { outcome: "uploading", progress: 65 }; + return { outcome: "error", progress: 30 }; +}; + +export const MixedResults: StoryFn = (args) => ( + +); +MixedResults.args = { concurrency: 3 }; +MixedResults.play = async ({ canvasElement }) => { + await waitFor( + () => { + const failedProgress = within(canvasElement).getByRole("progressbar", { + name: "Upload progress for third.ttl", + }); + if (failedProgress.closest("[role=listitem]")?.getAttribute("data-state") !== "error") { + throw new Error("Expected third.ttl to reach the error state"); + } + }, + { timeout: 3_000 }, + ); +}; + +export const TransportError: StoryFn = (args) => ( + +); +TransportError.play = ({ canvasElement }) => waitForAlert(canvasElement); + +export const RestrictionError: StoryFn = (args) => ( + +); +RestrictionError.play = ({ canvasElement }) => waitForAlert(canvasElement); + +const equalFiles = ["first.ttl", "second.ttl", "third.ttl"].map((name) => new File(["data"], name)); + +export const CancelledFiles: StoryFn = (args) => ( + +); +CancelledFiles.play = async ({ canvasElement }) => { + await waitForProgress(canvasElement, "Upload progress for second.ttl", "65"); + await userEvent.click(within(canvasElement).getByRole("button", { name: "Stop uploads" })); + await waitForProgress(canvasElement, "Overall upload progress", "33"); +}; + +export const RemovedBeforeContinue = CancelledFiles.bind({}); +RemovedBeforeContinue.play = async (context) => { + await CancelledFiles.play!(context); + await userEvent.click( + within(context.canvasElement).getByRole("button", { name: "Remove", description: "third.ttl" }), + ); + await waitForProgress(context.canvasElement, "Overall upload progress", "50"); +}; + +export const KeyboardRemoval = CancelledFiles.bind({}); +KeyboardRemoval.play = CancelledFiles.play; + +export const RetryAfterError: StoryFn = (args) => { + const stateForFile = React.useMemo(() => { + let attempts = 0; + // Uppy exhausts four transport attempts before exposing the manual Retry action. + return () => (++attempts <= 4 ? errorState() : completedState()); + }, []); + return ; +}; +RetryAfterError.play = ({ canvasElement }) => waitForAlert(canvasElement); diff --git a/src/components/FileUpload/FileUpload.test.tsx b/src/components/FileUpload/FileUpload.test.tsx new file mode 100644 index 00000000..534b655e --- /dev/null +++ b/src/components/FileUpload/FileUpload.test.tsx @@ -0,0 +1,329 @@ +import React from "react"; +import { fireEvent, render, screen, within } from "@testing-library/react"; + +import "@testing-library/jest-dom"; + +import { ApplicationContainer } from "../Application"; +import { SimpleDialog } from "../Dialog"; + +import FileUpload from "./FileUpload"; + +const labels = { + cancelFile: "Cancel upload", + continueUpload: "Continue uploads", + dropHereOr: "Drop a project file here or", + browse: "browse files", + uploadedFile: (file: { name: string }) => `${file.name} uploaded`, + selectedFile: (file: { name: string }) => `${file.name} selected`, + removeFile: "Remove", + removedFile: (file: { name: string }) => `${file.name} removed`, + formatError: ({ kind, error }: { kind: string; error: Error }) => + kind === "response" ? `Invalid response: ${error.message}` : `Upload failed: ${error.message}`, + uploadProgress: "Upload progress", + overallUploadProgress: "Overall upload progress", + retry: "Retry", + stopUploads: "Stop uploads", + uploadCancelled: "Upload cancelled", + completedFiles: (completed: number, total: number) => `${completed} of ${total} files completed`, + fileUploadProgress: (file: { name: string }) => `Upload progress for ${file.name}`, +}; + +const renderFileUpload = (props: Partial> = {}) => + render( + , + ); + +describe("FileUpload", () => { + it("renders a named group and a single explicitly named file-selection button", () => { + renderFileUpload({ instructions: "Turtle files up to 10 MB" }); + + const group = screen.getByRole("group", { name: "Project file upload" }); + const button = within(group).getByRole("button", { name: /browse files/i }); + const input = group.querySelector("input[type=file]") as HTMLInputElement; + + expect(button.tagName).toBe("BUTTON"); + expect(button).toHaveAttribute("role", "button"); + expect(button).toHaveAttribute("type", "button"); + expect(button).toHaveAttribute("aria-controls", input.id); + expect(button).toHaveAccessibleDescription(expect.stringContaining("Turtle files up to 10 MB")); + expect(group).toHaveAccessibleDescription(expect.stringContaining("Turtle files up to 10 MB")); + expect(input).toHaveAttribute("accept", ".ttl"); + expect(input).not.toHaveAttribute("multiple"); + expect(input).toHaveAttribute("tabindex", "-1"); + expect(group.querySelector('[data-dropzone-for="Files"]')).toBeInTheDocument(); + expect(group).not.toHaveAttribute("aria-busy"); + expect(within(group).getAllByRole("button")).toHaveLength(1); + }); + + it("creates stable, unique relationships for multiple uploaders", () => { + const { rerender } = render( + <> + + + , + ); + + const [firstButton, secondButton] = screen.getAllByRole("button", { name: /browse files/i }); + const firstInputId = firstButton.getAttribute("aria-controls"); + const secondInputId = secondButton.getAttribute("aria-controls"); + + expect(firstInputId).not.toBe(secondInputId); + expect(document.getElementById(firstInputId!)).toBeInstanceOf(HTMLInputElement); + expect(document.getElementById(secondInputId!)).toBeInstanceOf(HTMLInputElement); + + rerender( + <> + + + , + ); + + expect(screen.getAllByRole("button", { name: /browse files/i })[0]).toHaveAttribute( + "aria-controls", + firstInputId, + ); + }); + + it("adds files selected through the native picker", () => { + renderFileUpload(); + const input = document.querySelector("input[type=file]") as HTMLInputElement; + + fireEvent.change(input, { target: { files: [new File(["data"], "vocabulary.ttl", { type: "text/turtle" })] } }); + + expect(screen.getByRole("status")).toHaveTextContent("vocabulary.ttl"); + expect(screen.getByRole("status")).toHaveClass("cds--visually-hidden"); + }); + + it("reports an unrestricted multi-file selection as one batch", () => { + const onFilesAdded = jest.fn(); + renderFileUpload({ autoUpload: false, maxNumberOfFiles: null, onFilesAdded }); + const input = document.querySelector("input[type=file]") as HTMLInputElement; + const files = [new File(["one"], "one.ttl"), new File(["two"], "two.ttl")]; + + fireEvent.change(input, { target: { files } }); + + expect(input).toHaveAttribute("multiple"); + expect(onFilesAdded).toHaveBeenCalledTimes(1); + expect(onFilesAdded).toHaveBeenCalledWith([ + expect.objectContaining({ name: "one.ttl" }), + expect.objectContaining({ name: "two.ttl" }), + ]); + }); + + it("opens the native picker exactly once per button activation", () => { + renderFileUpload(); + const input = document.querySelector("input[type=file]") as HTMLInputElement; + const inputClick = jest.spyOn(input, "click"); + + fireEvent.click(screen.getByRole("button", { name: /browse files/i })); + + expect(inputClick).toHaveBeenCalledTimes(1); + }); + + it("adds dropped files with or without an ApplicationContainer", () => { + const { rerender } = renderFileUpload(); + const file = new File(["data"], "vocabulary.ttl", { type: "text/turtle" }); + + fireEvent.drop(document.querySelector('[data-dropzone-for="Files"]')!, { dataTransfer: { files: [file] } }); + expect(screen.getByRole("status")).toHaveTextContent("vocabulary.ttl"); + + rerender( + + + , + ); + + fireEvent.drop(document.querySelector('[data-dropzone-for="Files"]')!, { dataTransfer: { files: [file] } }); + expect(screen.getByRole("status")).toHaveTextContent("vocabulary.ttl"); + }); + + it("clears application drag monitoring when a drop opens a modal", () => { + const UploadWithErrorDialog = () => { + const [dialogOpen, setDialogOpen] = React.useState(false); + + return ( + + setDialogOpen(true)} + /> + setDialogOpen(false)} + title="Upload failed" + transitionDuration={0} + > + The dropped file could not be used. + + + ); + }; + render(); + fireEvent.dragOver(document.body, { dataTransfer: { types: ["Files"] } }); + expect(document.body).toHaveAttribute("data-monitor-dropzone", "Files"); + + fireEvent.drop(document.querySelector('[data-dropzone-for="Files"]')!, { + dataTransfer: { files: [new File(["data"], "invalid.txt")], types: ["Files"] }, + }); + const monitorDropzone = document.body.dataset.monitorDropzone; + delete document.body.dataset.monitorDropzone; + + expect(screen.getByText("Upload failed")).toBeInTheDocument(); + expect(monitorDropzone).toBeUndefined(); + }); + + it("keeps the drag state while the pointer moves over nested content", () => { + renderFileUpload(); + const dropzone = document.querySelector('[data-dropzone-for="Files"]')!; + const button = screen.getByRole("button", { name: /drop a project file here/i }); + + fireEvent.dragEnter(dropzone); + expect(dropzone).toHaveAttribute("data-state", "dragging"); + + fireEvent.dragEnter(button); + fireEvent.dragLeave(button); + expect(dropzone).toHaveAttribute("data-state", "dragging"); + + fireEvent.dragLeave(dropzone, { relatedTarget: document.body }); + expect(dropzone).toHaveAttribute("data-state", "idle"); + }); + + it.each(["disabled", "selectionDisabled"] as const)("clears an active drag when %s changes", (disabledProp) => { + const props = { autoUpload: false, name: "Project file upload", endpoint: "/files", labels }; + const { rerender } = render(); + const dropzone = document.querySelector('[data-dropzone-for="Files"]')!; + + fireEvent.dragEnter(dropzone); + expect(dropzone).toHaveAttribute("data-state", "dragging"); + + rerender(); + expect(dropzone).toHaveAttribute("data-state", "disabled"); + expect(dropzone.className).not.toContain("--dragging"); + rerender(); + expect(dropzone).toHaveAttribute("data-state", "idle"); + + rerender(); + fireEvent.drop(dropzone, { dataTransfer: { files: [new File(["data"], "blocked.ttl")] } }); + expect(screen.queryByRole("listitem")).not.toBeInTheDocument(); + + rerender(); + expect(dropzone).toHaveAttribute("data-state", "idle"); + fireEvent.dragEnter(dropzone); + fireEvent.dragLeave(dropzone); + expect(dropzone).toHaveAttribute("data-state", "idle"); + }); + + it("clears the drag state after dropping non-file content", () => { + renderFileUpload(); + const dropzone = document.querySelector('[data-dropzone-for="Files"]')!; + + fireEvent.dragEnter(dropzone); + fireEvent.drop(dropzone, { dataTransfer: { files: [], types: ["text/plain"] } }); + + expect(dropzone).toHaveAttribute("data-state", "idle"); + expect(dropzone.className).not.toContain("--dragging"); + expect(screen.queryByRole("listitem")).not.toBeInTheDocument(); + fireEvent.dragEnter(dropzone); + fireEvent.dragLeave(dropzone); + expect(dropzone).toHaveAttribute("data-state", "idle"); + }); + + it.each([ + ["file type", { acceptedFileTypes: [".ttl"] }, new File(["data"], "invalid.txt")], + ["file size", { maxFileSize: 3 }, new File(["too large"], "large.ttl")], + ])("shows %s restriction failures as accessible errors", (_restriction, props, file) => { + renderFileUpload({ + ...props, + labels: { + ...labels, + formatError: ({ file }) => `File rejected: ${file?.name ?? "selection"}`, + }, + }); + const input = document.querySelector("input[type=file]") as HTMLInputElement; + + fireEvent.change(input, { target: { files: [file] } }); + + expect(screen.getByRole("alert")).toHaveTextContent("File rejected:"); + expect(screen.getByRole("button", { name: /browse files/i })).toHaveAccessibleDescription( + screen.getByRole("alert").textContent!, + ); + expect(screen.getByRole("status")).toBeEmptyDOMElement(); + }); + + it("rejects a selection that exceeds the maximum file count", () => { + renderFileUpload({ maxNumberOfFiles: 1 }); + const input = document.querySelector("input[type=file]") as HTMLInputElement; + + fireEvent.change(input, { + target: { files: [new File(["one"], "one.ttl"), new File(["two"], "two.ttl")] }, + }); + + expect(screen.getByRole("alert")).not.toBeEmptyDOMElement(); + expect(screen.getByRole("status")).toBeEmptyDOMElement(); + }); + + it("uses the localized name as an aria-label when its visible label is hidden", () => { + renderFileUpload({ hideName: true }); + + const group = screen.getByRole("group", { name: "Project file upload" }); + expect(group).toHaveAttribute("aria-label", "Project file upload"); + expect(group).not.toHaveAttribute("aria-labelledby"); + expect(screen.queryByText("Project file upload")).not.toBeInTheDocument(); + }); + + it("uses the current error callback after rerender", () => { + const firstCallback = jest.fn(); + const currentCallback = jest.fn(); + const { rerender } = renderFileUpload({ onUploadError: firstCallback }); + + rerender( + , + ); + fireEvent.change(document.querySelector("input[type=file]")!, { + target: { files: [new File(["data"], "invalid.txt")] }, + }); + + expect(firstCallback).not.toHaveBeenCalled(); + expect(currentCallback).toHaveBeenCalledWith(expect.objectContaining({ kind: "restriction" })); + }); + + it("blocks picker and drop selection while disabled", () => { + renderFileUpload({ disabled: true }); + const button = screen.getByRole("button", { name: /browse files/i }); + const input = document.querySelector("input[type=file]") as HTMLInputElement; + + expect(button).toBeDisabled(); + expect(input).toBeDisabled(); + + fireEvent.change(input, { target: { files: [new File(["data"], "picker.ttl")] } }); + fireEvent.drop(document.querySelector('[data-dropzone-for="Files"]')!, { + dataTransfer: { files: [new File(["data"], "dropped.ttl")] }, + }); + + expect(screen.getByRole("status")).toBeEmptyDOMElement(); + }); +}); diff --git a/src/components/FileUpload/FileUpload.transport.test.tsx b/src/components/FileUpload/FileUpload.transport.test.tsx new file mode 100644 index 00000000..f4dd5cbe --- /dev/null +++ b/src/components/FileUpload/FileUpload.transport.test.tsx @@ -0,0 +1,742 @@ +import React from "react"; +import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react"; + +import "@testing-library/jest-dom"; + +import FileUpload from "./FileUpload"; +import { completeRequest, ControlledXMLHttpRequest, failRequest, labels } from "./testHelpers"; +import { FileUploadHandle, FileUploadResult } from "./types"; + +const select = (...files: File[]) => + fireEvent.change(document.querySelector("input[type=file]")!, { target: { files } }); +const file = (name: string, contents = "data") => new File([contents], name); +const request = async (index: number) => { + await waitFor(() => expect(ControlledXMLHttpRequest.requests[index]?.sent).toBe(true)); + return ControlledXMLHttpRequest.requests[index]; +}; +const row = (name: string) => + screen.getByRole("progressbar", { name: `Upload progress for ${name}` }).closest('[role="listitem"]')!; +const deferred = () => { + let resolve!: (value: T) => void; + let reject!: (error: Error) => void; + const promise = new Promise((yes, no) => { + resolve = yes; + reject = no; + }); + return { promise, resolve, reject }; +}; + +describe("FileUpload transport", () => { + it.each([ + [{ maxFileSize: 2 }, [file("large.ttl")], { code: "maxFileSize", maxFileSize: 2 }], + [{ acceptedFileTypes: [".ttl"] }, [file("wrong.txt")], { code: "fileType", acceptedFileTypes: [".ttl"] }], + [ + { maxNumberOfFiles: 1 }, + [file("one.ttl"), file("two.ttl")], + { code: "maxNumberOfFiles", maxNumberOfFiles: 1 }, + ], + ])("reports structured selection restrictions: %j", async (restrictions, files, detail) => { + const onUploadError = jest.fn(), + beforeUpload = jest.fn(() => true); + render( + , + ); + select(...files); + expect(onUploadError).toHaveBeenCalledWith( + expect.objectContaining({ kind: "restriction", restriction: detail }), + ); + expect(beforeUpload).not.toHaveBeenCalled(); + expect(ControlledXMLHttpRequest.requests).toHaveLength(0); + }); + + it("reports duplicates and retained-selection limits without inspecting error text", async () => { + const onUploadError = jest.fn(); + render( + , + ); + const duplicate = file("one.ttl"); + select(duplicate); + select(duplicate); + expect(onUploadError).toHaveBeenLastCalledWith( + expect.objectContaining({ + restriction: { code: "duplicate" }, + file: expect.objectContaining({ name: "one.ttl" }), + }), + ); + select(file("two.ttl"), file("three.ttl")); + expect(onUploadError).toHaveBeenLastCalledWith( + expect.objectContaining({ + restriction: { code: "maxNumberOfFiles", maxNumberOfFiles: 2 }, + }), + ); + expect(ControlledXMLHttpRequest.requests).toHaveLength(0); + }); + + it("prioritizes size details when initial files violate both size and type", () => { + const onUploadError = jest.fn(); + const formatError = jest.fn(() => "Localized restriction"); + render( + , + ); + expect(onUploadError).toHaveBeenCalledWith( + expect.objectContaining({ + kind: "restriction", + restriction: { code: "maxFileSize", maxFileSize: 2 }, + }), + ); + expect(formatError).toHaveBeenCalledWith(onUploadError.mock.calls[0][0]); + expect(screen.getByRole("alert")).toHaveTextContent("Localized restriction"); + expect(ControlledXMLHttpRequest.requests).toHaveLength(0); + }); + + it("reports updated restrictions when a queued file is started manually", async () => { + const ref = React.createRef(); + const props = { name: "Updated restrictions", endpoint: "/files", labels, autoUpload: false }; + const { rerender } = render(); + select(file("large.ttl")); + await waitFor(() => expect(row("large.ttl")).toHaveAttribute("data-state", "queued")); + rerender(); + await act(async () => { + const result = await ref.current!.upload(); + expect(result.failed).toEqual([ + expect.objectContaining({ + kind: "restriction", + restriction: { code: "maxFileSize", maxFileSize: 2 }, + }), + ]); + }); + expect(ControlledXMLHttpRequest.requests).toHaveLength(0); + }); + + it("runs synchronous approval at selection time in manual mode and catches synchronous throws", async () => { + const beforeUpload = jest.fn((selected) => { + if (selected.name === "broken.ttl") throw new Error("Check failed"); + return selected.name !== "declined.ttl"; + }); + const onUploadError = jest.fn(); + render( + , + ); + select(file("accepted.ttl"), file("declined.ttl"), file("broken.ttl")); + await waitFor(() => expect(beforeUpload).toHaveBeenCalledTimes(3)); + await waitFor(() => + expect(onUploadError).toHaveBeenCalledWith(expect.objectContaining({ kind: "validation" })), + ); + expect(screen.queryByText("declined.ttl")).not.toBeInTheDocument(); + expect(row("accepted.ttl")).toHaveAttribute("data-state", "queued"); + expect(ControlledXMLHttpRequest.requests).toHaveLength(0); + }); + it("uses short Remove labels and focuses the next or previous Remove after committing removal", async () => { + render(); + select(file("first.ttl"), file("second.ttl"), file("third.ttl")); + await request(0); + fireEvent.click(screen.getByRole("button", { name: "Stop uploads" })); + const remove = (name: string) => screen.getByRole("button", { name: "Remove", description: name }); + expect(remove("first.ttl")).toHaveTextContent(/^Remove$/); + fireEvent.click(remove("second.ttl")); + expect(remove("third.ttl")).toHaveFocus(); + expect(screen.queryByText("second.ttl")).not.toBeInTheDocument(); + fireEvent.click(remove("third.ttl")); + expect(remove("first.ttl")).toHaveFocus(); + fireEvent.click(remove("first.ttl")); + expect(screen.getByRole("button", { name: /browse files/ })).toHaveFocus(); + }); + it("preserves both successes when another selection joins an active upload", async () => { + const onUploadSuccess = jest.fn(); + render( + , + ); + const input = document.querySelector("input[type=file]")!; + fireEvent.change(input, { target: { files: [new File(["one"], "one.ttl")] } }); + await waitFor(() => expect(ControlledXMLHttpRequest.requests[0]?.sent).toBe(true)); + fireEvent.change(input, { target: { files: [new File(["two"], "two.ttl")] } }); + await waitFor(() => + expect(screen.getByRole("progressbar", { name: "Upload progress for two.ttl" })).toBeInTheDocument(), + ); + await completeRequest(ControlledXMLHttpRequest.requests[0], 200, "one"); + await waitFor(() => expect(ControlledXMLHttpRequest.requests[1]?.sent).toBe(true)); + await completeRequest(ControlledXMLHttpRequest.requests[1], 200, "two"); + await waitFor(() => expect(onUploadSuccess).toHaveBeenCalledTimes(2)); + expect(screen.getByText("2 of 2 files completed")).toBeInTheDocument(); + }); + + it("counts cancelled files until removal before continuing the remaining uploads", async () => { + render( + , + ); + fireEvent.change(document.querySelector("input[type=file]")!, { + target: { + files: [new File(["aaa"], "a.ttl"), new File(["bbb"], "b.ttl"), new File(["ccc"], "c.ttl")], + }, + }); + await waitFor(() => expect(ControlledXMLHttpRequest.requests[0]?.sent).toBe(true)); + await completeRequest(ControlledXMLHttpRequest.requests[0], 200, "a"); + await waitFor(() => expect(ControlledXMLHttpRequest.requests[1]?.sent).toBe(true)); + fireEvent.click(screen.getByRole("button", { name: "Stop uploads" })); + expect(screen.getByRole("progressbar", { name: "Overall upload progress" })).toHaveAttribute( + "aria-valuenow", + "33", + ); + fireEvent.click(screen.getByRole("button", { name: "Remove", description: "b.ttl" })); + expect(screen.getByRole("progressbar", { name: "Overall upload progress" })).toHaveAttribute( + "aria-valuenow", + "50", + ); + fireEvent.click(screen.getByRole("button", { name: "Continue uploads" })); + await waitFor(() => expect(ControlledXMLHttpRequest.requests[2]?.sent).toBe(true)); + await completeRequest(ControlledXMLHttpRequest.requests[2], 200, "c"); + expect(screen.getByText("2 of 2 files completed")).toBeInTheDocument(); + expect(ControlledXMLHttpRequest.requests.filter((request) => request.sent)).toHaveLength(3); + }); + + it("preserves a nullable parser result instead of replacing it with response text", async () => { + const onUploadSuccess = jest.fn(); + render( + null} + onUploadSuccess={onUploadSuccess} + />, + ); + fireEvent.change(document.querySelector("input[type=file]")!, { + target: { files: [new File(["a"], "a.ttl")] }, + }); + await waitFor(() => expect(ControlledXMLHttpRequest.requests[0]?.sent).toBe(true)); + await completeRequest(ControlledXMLHttpRequest.requests[0], 200, "ignored"); + expect(onUploadSuccess).toHaveBeenCalledWith(expect.objectContaining({ body: null })); + }); + + it("returns one typed result for overlapping manual calls and publishes state before completion", async () => { + const ref = React.createRef>(); + const state = jest.fn(); + const onComplete = jest.fn(() => + expect(state).toHaveBeenLastCalledWith(expect.objectContaining({ allSuccessful: true })), + ); + render( + ({ id: responseText })} + onStateChange={state} + onComplete={onComplete} + />, + ); + select(file("manual.ttl")); + expect(ControlledXMLHttpRequest.requests).toHaveLength(0); + let first!: Promise>; + act(() => { + first = ref.current!.upload(); + expect(ref.current!.upload()).toBe(first); + }); + await completeRequest(await request(0), 201, "upload-id"); + expect(await first).toEqual( + expect.objectContaining({ + reason: "settled", + successful: [expect.objectContaining({ body: { id: "upload-id" }, status: 201 })], + }), + ); + expect(onComplete).toHaveBeenCalledTimes(1); + }); + + it("lets approved files upload while another approval waits and completes only after all decisions", async () => { + const approval = deferred(); + const onComplete = jest.fn(); + render( + "/files/" + f.name} + labels={labels} + maxNumberOfFiles={null} + beforeUpload={(f) => (f.name === "existing.ttl" ? approval.promise : Promise.resolve(true))} + onComplete={onComplete} + />, + ); + select(file("existing.ttl"), file("new.ttl")); + const first = await request(0); + expect(first.url).toBe("/files/new.ttl"); + await completeRequest(first, 200, "new"); + expect(onComplete).not.toHaveBeenCalled(); + await act(async () => approval.resolve(true)); + const second = await request(1); + expect(second.url).toBe("/files/existing.ttl"); + expect(screen.getByText("1 of 2 files completed")).toBeInTheDocument(); + await completeRequest(second, 200, "existing"); + expect(onComplete).toHaveBeenCalledTimes(1); + expect(onComplete).toHaveBeenLastCalledWith( + expect.objectContaining({ + successful: expect.arrayContaining([ + expect.objectContaining({ file: expect.objectContaining({ name: "new.ttl" }) }), + expect.objectContaining({ file: expect.objectContaining({ name: "existing.ttl" }) }), + ]), + }), + ); + }); + + it("declines without a request and retries a rejected approval", async () => { + const beforeUpload = jest + .fn() + .mockRejectedValueOnce(new Error("Check unavailable")) + .mockResolvedValueOnce(true); + const onUploadError = jest.fn(); + const { rerender } = render( + , + ); + select(file("checked.ttl")); + await screen.findByRole("alert"); + expect(onUploadError).toHaveBeenCalledWith(expect.objectContaining({ kind: "validation" })); + expect(ControlledXMLHttpRequest.requests).toHaveLength(0); + fireEvent.click(screen.getByRole("button", { name: "Retry" })); + await completeRequest(await request(0), 200, "done"); + expect(beforeUpload).toHaveBeenCalledTimes(2); + rerender( false} />); + select(file("declined.ttl")); + await waitFor(() => expect(screen.queryByText("declined.ttl")).not.toBeInTheDocument()); + expect(ControlledXMLHttpRequest.requests).toHaveLength(1); + }); + + it.each(["cancel", "reset", "remove", "unmount"] as const)("ignores pending approval after %s", async (action) => { + const approval = deferred(); + const beforeUpload = jest.fn(() => approval.promise); + const ref = React.createRef(); + const onFilesAdded = jest.fn(); + const onUploadSuccess = jest.fn(); + const { unmount } = render( + , + ); + select(file("pending.ttl")); + await waitFor(() => expect(beforeUpload).toHaveBeenCalled()); + act(() => { + if (action === "unmount") unmount(); + else if (action === "remove") ref.current!.remove(onFilesAdded.mock.calls[0][0][0].id); + else ref.current![action](); + }); + await act(async () => approval.resolve(true)); + expect(beforeUpload.mock.calls[0][1].aborted).toBe(true); + expect(ControlledXMLHttpRequest.requests).toHaveLength(0); + expect(onUploadSuccess).not.toHaveBeenCalled(); + }); + + it("retains completed history and announcements while accepting the next single file", async () => { + render(); + select(file("first.ttl")); + await completeRequest(await request(0), 200, "first"); + expect(screen.getByRole("status")).toHaveTextContent("first.ttl uploaded"); + select(file("second.ttl")); + await completeRequest(await request(1), 200, "second"); + expect(screen.getByText("2 of 2 files completed")).toBeInTheDocument(); + expect(screen.getByRole("status")).toHaveTextContent("second.ttl uploaded"); + }); + + it("includes pending and cancelled files in the selection limit", async () => { + const approval = deferred(); + render( approval.promise} />); + select(file("first.ttl")); + select(file("second.ttl")); + expect(screen.getByRole("alert")).toHaveTextContent(/only upload 1/i); + fireEvent.click(screen.getByRole("button", { name: "Cancel upload" })); + select(file("third.ttl")); + expect(screen.queryByText("third.ttl")).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Remove", description: "first.ttl" })); + select(file("fourth.ttl")); + expect(screen.getByText("fourth.ttl")).toBeInTheDocument(); + }); + + it("uses updated endpoints, headers, method and callbacks for subsequent requests", async () => { + const oldSuccess = jest.fn(), + success = jest.fn(); + const { rerender } = render( + , + ); + select(file("first.ttl"), file("second.ttl")); + const first = await request(0); + rerender( + "/new/" + f.name} + method="PUT" + labels={labels} + headers={() => ({ Authorization: "current token" })} + maxNumberOfFiles={null} + onUploadSuccess={success} + />, + ); + await completeRequest(first, 200, "first"); + const second = await request(1); + expect(second.url).toBe("/new/second.ttl"); + expect(second.method.toUpperCase()).toBe("PUT"); + expect(second.requestHeaders).toEqual({ Authorization: "current token" }); + await completeRequest(second, 200, "second"); + expect(oldSuccess).not.toHaveBeenCalled(); + expect(success).toHaveBeenCalledTimes(2); + }); + + it("weights progress by bytes and shares a changing concurrency limit across requests", async () => { + const { rerender } = render( + , + ); + select(file("small.ttl", "aa"), file("large.ttl", "bbbbbbbb"), file("last.ttl", "cc")); + const first = await request(0); + act(() => first.progress(1, 2)); + expect(screen.getByRole("progressbar", { name: "Overall upload progress" })).toHaveAttribute( + "aria-valuenow", + "8", + ); + expect(ControlledXMLHttpRequest.requests).toHaveLength(1); + rerender( + , + ); + const second = await request(1); + expect(ControlledXMLHttpRequest.requests).toHaveLength(2); + await completeRequest(first, 200, "small"); + const third = await request(2); + await completeRequest(second, 200, "large"); + await completeRequest(third, 200, "last"); + expect(screen.getByText("3 of 3 files completed")).toBeInTheDocument(); + }); + + it("queues a cancelled-file retry behind the active file without another approval", async () => { + const beforeUpload = jest.fn(async () => true); + const onComplete = jest.fn(); + render( + "/files/" + f.name} + labels={labels} + maxNumberOfFiles={null} + beforeUpload={beforeUpload} + onComplete={onComplete} + />, + ); + select(file("first.ttl"), file("second.ttl")); + const first = await request(0); + fireEvent.click(within(row("first.ttl")).getByRole("button", { name: "Cancel upload" })); + const second = await request(1); + expect(first.aborted).toBe(true); + fireEvent.click(within(row("first.ttl")).getByRole("button", { name: "Retry" })); + expect(row("first.ttl")).toHaveAttribute("data-state", "queued"); + expect(ControlledXMLHttpRequest.requests).toHaveLength(2); + await completeRequest(second, 200, "second"); + const retry = await request(2); + expect(retry.url).toBe("/files/first.ttl"); + await completeRequest(retry, 200, "first"); + expect(beforeUpload).toHaveBeenCalledTimes(2); + expect(onComplete).toHaveBeenCalledTimes(1); + }); + + it("reports mixed results and permits a failed file to be retried", async () => { + const onComplete = jest.fn(), + onUploadError = jest.fn(); + render( + , + ); + select(file("good.ttl"), file("bad.ttl")); + await completeRequest(await request(0), 200, "good"); + for (let attempt = 1; attempt <= 4; attempt++) await completeRequest(await request(attempt), 400, "bad"); + expect(onUploadError).toHaveBeenCalledWith(expect.objectContaining({ kind: "transport", status: 400 })); + expect(onComplete).toHaveBeenCalledWith( + expect.objectContaining({ + successful: [expect.anything()], + failed: [expect.anything()], + }), + ); + fireEvent.click(screen.getByRole("button", { name: "Retry" })); + await completeRequest(await request(5), 200, "recovered"); + expect(screen.getByText("2 of 2 files completed")).toBeInTheDocument(); + expect(onComplete).toHaveBeenCalledTimes(2); + }); + + it("classifies parsing errors without a fallback parser", async () => { + const onUploadError = jest.fn(); + render( + { + throw new Error("Bad body"); + }} + onUploadError={onUploadError} + />, + ); + select(file("body.ttl")); + await completeRequest(await request(0), 201, "body"); + expect(onUploadError).toHaveBeenCalledWith(expect.objectContaining({ kind: "response", status: 201 })); + expect(screen.getByRole("alert")).toHaveTextContent("Invalid response: Bad body"); + }); + + it("reports an exhausted network error once", async () => { + const onUploadError = jest.fn(); + render(); + select(file("body.ttl")); + for (let attempt = 0; attempt < 4; attempt++) await failRequest(await request(attempt), "Network error"); + await waitFor(() => expect(onUploadError).toHaveBeenCalledTimes(1)); + }); + + it("separates selection disabling from retry and full disabling", async () => { + const ref = React.createRef(); + const { rerender } = render(); + select(file("retry.ttl")); + await request(0); + fireEvent.click(screen.getByRole("button", { name: "Cancel upload" })); + rerender(); + expect(screen.getByRole("button", { name: /browse files/ })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Retry" })).toBeEnabled(); + rerender(); + expect(screen.getByRole("button", { name: "Retry" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Remove", description: "retry.ttl" })).toBeEnabled(); + await expect(ref.current!.upload()).rejects.toThrow("disabled"); + }); + + it("removes the final unsuccessful row without manufacturing another success or completion", async () => { + const state = jest.fn(), + success = jest.fn(), + complete = jest.fn(); + const ref = React.createRef(); + render( + , + ); + select(file("good.ttl"), file("cancelled.ttl")); + await completeRequest(await request(0), 200, "good"); + await request(1); + fireEvent.click(screen.getByRole("button", { name: "Cancel upload" })); + await waitFor(() => expect(complete).toHaveBeenCalledTimes(1)); + fireEvent.click(screen.getByRole("button", { name: "Remove", description: "cancelled.ttl" })); + expect(screen.getByRole("status")).toHaveTextContent("cancelled.ttl removed"); + expect(screen.getByRole("button", { name: /browse files/ })).toHaveFocus(); + expect(state).toHaveBeenLastCalledWith(expect.objectContaining({ progress: 100, allSuccessful: true })); + expect(success).toHaveBeenCalledTimes(1); + expect(complete).toHaveBeenCalledTimes(1); + act(() => ref.current!.reset()); + expect(state).toHaveBeenLastCalledWith(expect.objectContaining({ progress: 0, allSuccessful: false })); + }); + + it.each([null, undefined])("preserves a parser returning %s", async (value) => { + const success = jest.fn(); + render( + value} + onUploadSuccess={success} + />, + ); + select(file("result.ttl")); + await completeRequest(await request(0), 200, "not-json"); + expect(success).toHaveBeenCalledWith(expect.objectContaining({ body: value })); + }); + + it("initializes native files once in StrictMode and never re-adds them after reset", async () => { + const ref = React.createRef(); + const initial = file("initial.ttl"); + const element = () => ( + + + + ); + const { rerender } = render(element()); + await completeRequest(await request(0), 200, "initial"); + rerender(element()); + act(() => ref.current!.reset()); + rerender(element()); + await act(async () => {}); + expect(ControlledXMLHttpRequest.requests).toHaveLength(1); + expect(screen.queryByText("initial.ttl")).not.toBeInTheDocument(); + }); + + it("keeps focus in the widget after removing its last actionable row while disabled", async () => { + const props = { name: "Disabled removal", endpoint: "/files", labels }; + const { rerender } = render(); + select(file("cancelled.ttl")); + await request(0); + fireEvent.click(screen.getByRole("button", { name: "Cancel upload" })); + rerender(); + fireEvent.click(screen.getByRole("button", { name: "Remove", description: "cancelled.ttl" })); + expect(screen.getByRole("group", { name: "Disabled removal" })).toHaveFocus(); + }); + + it("does not confuse transferred bytes with successful HTTP completion", async () => { + const state = jest.fn(), + success = jest.fn(), + complete = jest.fn(); + render( + , + ); + select(file("waiting.ttl")); + const xhr = await request(0); + act(() => xhr.progress(4, 4)); + expect(state).toHaveBeenLastCalledWith( + expect.objectContaining({ progress: 100, allSuccessful: false, uploading: 1 }), + ); + expect(success).not.toHaveBeenCalled(); + expect(complete).not.toHaveBeenCalled(); + await completeRequest(xhr, 200, "done"); + expect(state).toHaveBeenLastCalledWith(expect.objectContaining({ allSuccessful: true, uploading: 0 })); + }); + + it("pauses queued requests when disabled without aborting active work", async () => { + const props = { name: "Paused", endpoint: "/files", labels, maxNumberOfFiles: null }; + const { rerender } = render(); + select(file("first.ttl"), file("second.ttl")); + const first = await request(0); + rerender(); + await completeRequest(first, 200, "done"); + expect(ControlledXMLHttpRequest.requests).toHaveLength(1); + expect(screen.getByRole("button", { name: "Cancel upload" })).toBeEnabled(); + rerender(); + await completeRequest(await request(1), 200, "done"); + expect(screen.getByText("2 of 2 files completed")).toBeInTheDocument(); + }); + + it("applies initial-file restrictions before approval or transport", async () => { + const beforeUpload = jest.fn(async () => true); + render( + , + ); + await screen.findByRole("alert"); + expect(beforeUpload).not.toHaveBeenCalled(); + expect(ControlledXMLHttpRequest.requests).toHaveLength(0); + }); + + it("uses completed file counts for zero-byte progress and retains cancelled files", async () => { + const state = jest.fn(); + render( + , + ); + select(file("first.ttl", ""), file("second.ttl", "")); + await completeRequest(await request(0), 200, "done"); + await request(1); + fireEvent.click(screen.getByRole("button", { name: "Stop uploads" })); + expect(state).toHaveBeenLastCalledWith(expect.objectContaining({ progress: 50, allSuccessful: false })); + fireEvent.click(screen.getByRole("button", { name: "Remove", description: "second.ttl" })); + expect(state).toHaveBeenLastCalledWith(expect.objectContaining({ progress: 100, allSuccessful: true })); + }); + + it("cancels and settles a manual promise on unmount without late callbacks", async () => { + const ref = React.createRef(); + const onComplete = jest.fn(), + onUploadSuccess = jest.fn(); + const { unmount } = render( + , + ); + select(file("late.ttl")); + let promise!: Promise>; + act(() => { + promise = ref.current!.upload(); + }); + const xhr = await request(0); + unmount(); + expect(await promise).toEqual(expect.objectContaining({ reason: "cancelled" })); + expect(xhr.aborted).toBe(true); + await completeRequest(xhr, 200, "late"); + expect(onComplete).not.toHaveBeenCalled(); + expect(onUploadSuccess).not.toHaveBeenCalled(); + }); +}); diff --git a/src/components/FileUpload/FileUpload.tsx b/src/components/FileUpload/FileUpload.tsx new file mode 100644 index 00000000..25300dc6 --- /dev/null +++ b/src/components/FileUpload/FileUpload.tsx @@ -0,0 +1,318 @@ +import React from "react"; +import { UppyContextProvider, useDropzone, useFileInput } from "@uppy/react"; +import classNames from "classnames"; + +import { CLASSPREFIX as eccgui } from "../../configuration/constants"; +import Button from "../Button/Button"; +import Icon from "../Icon/Icon"; +import ProgressBar from "../ProgressBar/ProgressBar"; + +import { FileUploadHandle, FileUploadLabels, FileUploadParsedProps, FileUploadTextProps } from "./types"; +import { UploadController, UploadRow } from "./UploadController"; +import { useRemovalFocus } from "./useRemovalFocus"; +import { FileUploadControllerProps, useUploadController } from "./useUploadController"; + +// Uppy 5.2's provider declaration fixes the response body to its empty default, although +// the provider only observes status/progress. Keep the controller's response type intact. +const UploadContextProvider = UppyContextProvider as unknown as React.ComponentType< + React.PropsWithChildren<{ uppy: UploadController["uppy"] }> +>; + +interface FileSelectionProps { + buttonRef: React.Ref; + buttonDescriptionIds: string | undefined; + disabled: boolean; + labels: FileUploadLabels; +} + +const FileSelection = ({ buttonRef, buttonDescriptionIds, disabled, labels }: FileSelectionProps) => { + const [dragging, setDragging] = React.useState(false); + // Enter/leave events also fire for nested content; count them to avoid flickering between children. + const dragEntryCount = React.useRef(0); + const handleDragEnter = React.useCallback(() => { + dragEntryCount.current += 1; + setDragging(true); + }, []); + const handleDragLeave = React.useCallback(() => { + dragEntryCount.current = Math.max(0, dragEntryCount.current - 1); + if (dragEntryCount.current === 0) { + setDragging(false); + } + }, []); + const resetDragState = React.useCallback(() => { + dragEntryCount.current = 0; + setDragging(false); + }, []); + React.useEffect(() => { + // Disabled handlers no longer track drag exits, so discard any drag already in progress. + if (disabled) resetDragState(); + }, [disabled, resetDragState]); + const { getRootProps } = useDropzone({ + noClick: true, + onDragEnter: handleDragEnter, + onDragLeave: handleDragLeave, + }); + const { getButtonProps, getInputProps } = useFileInput(); + const dropzoneProps = getRootProps(); + const inputProps = getInputProps(); + const buttonProps = getButtonProps(); + const preventDisabledDrop = (event: React.DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; + return ( +
+ + +
+ ); +}; + +interface ProgressProps { + name: string; + value: number; + active: boolean; + intent?: "success" | "danger"; + valueText?: string; +} + +const UploadProgress = ({ name, value, active, intent, valueText }: ProgressProps) => ( +
+
+); + +interface FileUploadRowProps { + row: UploadRow; + widgetId: string; + labels: FileUploadLabels; + disabled: boolean; + onCancel: (fileId: string) => void; + onRetry: (fileId: string) => void; + onRemove: (fileId: string) => void; + removeButtonRef: React.Ref; +} + +const FileUploadRow = ({ + row, + widgetId, + labels, + disabled, + onCancel, + onRetry, + onRemove, + removeButtonRef, +}: FileUploadRowProps) => { + const { file, status, progress } = row; + const nameId = `${widgetId}-${file.id}-name`; + const isCancelled = status === "cancelled"; + const canCancel = status === "uploading" || status === "queued" || status === "pendingApproval"; + const canRetry = status === "error" || isCancelled; + const progressIntent = status === "complete" ? "success" : status === "error" ? "danger" : undefined; + + return ( +
+
+ {file.name} + + {isCancelled ? ( + + + ) : ( + + )} + {canCancel &&
+ +
+ ); +}; + +function FileUploadInner(props: FileUploadControllerProps, ref: React.ForwardedRef>) { + const generatedId = React.useId().replace(/[^a-zA-Z0-9_-]/g, ""); + const widgetId = props.id ?? `file-upload-${generatedId}`; + const { controller, snapshot } = useUploadController(widgetId, props); + const { files, state, error, announcement } = snapshot; + const { groupRef, browseRef, registerRemoveButton, removeRow } = useRemovalFocus(files, controller.remove); + React.useImperativeHandle(ref, () => controller, [controller]); + + const { name, hideName = false, labels, instructions, disabled = false, selectionDisabled = false } = props; + const labelId = `${widgetId}-label`; + const instructionsId = `${widgetId}-instructions`; + const errorId = `${widgetId}-error`; + const descriptionIds = + [instructions ? instructionsId : undefined, error ? errorId : undefined].filter(Boolean).join(" ") || undefined; + + const hasPendingWork = state.uploading > 0 || state.queued > 0 || state.pendingApproval > 0; + + return ( +
0 || undefined} + aria-label={hideName ? name : undefined} + aria-labelledby={hideName ? undefined : labelId} + className={`${eccgui}-fileupload`} + > + {!hideName && ( +
+ {name} +
+ )} + + + + {files.length > 0 && ( +
+ {files.length > 1 && ( +
+
+ {labels.overallUploadProgress} + + + {hasPendingWork && ( +
+ 0} + intent={state.allSuccessful ? "success" : undefined} + /> +
+ {labels.completedFiles(state.completed, files.length)} +
+
+ )} +
+ {files.map((row) => ( + registerRemoveButton(row.file.id, element)} + /> + ))} +
+
+ )} + {instructions && ( +
+ {instructions} +
+ )} + {error && ( + + )} +
+ {announcement} +
+
+ ); +} + +interface FileUploadComponent { + (props: FileUploadTextProps & React.RefAttributes>): React.JSX.Element; + (props: FileUploadParsedProps & React.RefAttributes>): React.JSX.Element; +} + +// React.forwardRef cannot preserve an overloaded generic call signature. +/** + * Select and upload files through a native picker or dropzone, with localized progress, + * errors and retry actions. Approved files upload automatically unless autoUpload is false. + * The forwarded ref exposes upload, cancel, remove and reset for application-controlled flows. + * Responses are strings by default; parseResponse determines the response type when provided. + */ +export const FileUpload = React.forwardRef(FileUploadInner) as FileUploadComponent; +export default FileUpload; diff --git a/src/components/FileUpload/README.md b/src/components/FileUpload/README.md new file mode 100644 index 00000000..4f1faf2e --- /dev/null +++ b/src/components/FileUpload/README.md @@ -0,0 +1,229 @@ +# FileUpload + +Import `FileUpload` and its types from `@eccenca/gui-elements`. The component owns selection, +approval, scheduling, transport, progress and retry state. Supply an endpoint and localized +`FileUploadLabels`; the examples below receive these labels from the application. +See `FileUpload.stories.tsx` for a complete English label set and visual states. + +Uploads start automatically, with one active request and at most one incomplete file by default. +Use `maxNumberOfFiles={null}` for unlimited selection and `concurrency` to allow parallel requests. +Successful rows release selection capacity. Cancelled rows remain in the count and overall progress +until removed: one successful and one cancelled equal-size file show 50%, not 100%. + +## Manual uploads + +```tsx +import React from "react"; +import { + FileUpload, + FileUploadHandle, + FileUploadLabels, +} from "@eccenca/gui-elements"; + +export function ManualUpload({ labels }: { labels: FileUploadLabels }) { + const upload = React.useRef(null); + const [busy, setBusy] = React.useState(false); + const [message, setMessage] = React.useState(""); + + async function start() { + if (!upload.current) return; + setBusy(true); + try { + const result = await upload.current.upload(); + setMessage( + `${result.successful.length} uploaded, ${result.failed.length} failed, ` + + `${result.cancelled.length} cancelled`, + ); + } catch (error) { + // For example, upload() rejects if the widget is disabled. + setMessage( + error instanceof Error + ? error.message + : "Unable to start uploads", + ); + } finally { + setBusy(false); + } + } + + return ( + <> + + +

{message}

+ + ); +} +``` + +Localize the surrounding button and summary in your application too. Manual mode delays automatic +request starts, not approval checks. Retry and Continue are explicit user requests and can start +uploads in manual mode. + +## Typed responses + +Without a parser, response bodies are strings. A parser determines the body type of both +`onUploadSuccess` and completion results; no generic argument or cast is needed. Validate external +JSON at the boundary: + +```tsx +import { + FileUpload, + FileUploadLabels, + FileUploadResponseMetadata, +} from "@eccenca/gui-elements"; + +function parseReceipt({ responseText }: FileUploadResponseMetadata): { + id: string; +} { + const value: unknown = JSON.parse(responseText); + if ( + typeof value !== "object" || + value === null || + !("id" in value) || + typeof value.id !== "string" + ) { + throw new Error("Expected an upload receipt with a string id"); + } + return { id: value.id }; +} + +export function ReceiptUpload({ + labels, + onReceipt, +}: { + labels: FileUploadLabels; + onReceipt: (id: string) => void; +}) { + return ( + onReceipt(body.id)} + /> + ); +} +``` + +Parser exceptions produce inline errors with `kind: "response"`, not success callbacks. +Intentional `null` and `undefined` parser results are preserved. For a manual parsed upload, use +`FileUploadHandle>` as the ref type. + +## Approval and overwrite prompts + +`beforeUpload(file, signal)` runs after local restrictions, on selection—even with +`autoUpload={false}`. Return `true` to approve or `false` to decline without an error. Throwing or +rejecting produces a retriable `validation` error. A synchronous decision needs no `async` wrapper: + +```tsx + file.name !== "protected.ttl"} +/> +``` + +For overwrite approval, keep domain checks and dialog rendering in the application: + +```tsx +import { + FileUpload, + FileUploadFile, + FileUploadLabels, +} from "@eccenca/gui-elements"; + +interface OverwriteUploadProps { + labels: FileUploadLabels; + resourceExists: (name: string, signal: AbortSignal) => Promise; + // The application dialog must close and settle its promise when the signal aborts. + confirmReplace: ( + file: FileUploadFile, + signal: AbortSignal, + ) => Promise; +} + +export function OverwriteUpload({ + labels, + resourceExists, + confirmReplace, +}: OverwriteUploadProps) { + return ( + { + const exists = await resourceExists(file.name, signal); + if (signal.aborted) return false; + return !exists || (await confirmReplace(file, signal)); + }} + /> + ); +} +``` + +Checks for different files may run concurrently: one unanswered prompt does not block another +approved file. Handle prompts independently, keyed by `file.id`. Cancellation, removal, reset and +unmount abort pending approval signals; observe them to cancel checks and dismiss dialogs. Stale +decisions cannot start an upload. Approval is not a per-request hook: transport retries do not +repeat a successful approval, whereas retrying a failed approval runs the check again. + +## Localized restriction errors + +Use `labels.formatError` for display and `onUploadError` for notification/diagnostics. Both receive +the same discriminated `FileUploadError`. For `kind === "restriction"`, switch on +`error.restriction.code` instead of parsing `error.error.message`: + +| Code | Details | +| ------------------ | ------------------------------------------------------------------------------------- | +| `maxFileSize` | `maxFileSize` in bytes | +| `fileType` | `acceptedFileTypes`, an immutable snapshot of the configured extensions/MIME patterns | +| `maxNumberOfFiles` | `maxNumberOfFiles`, the incomplete-selection limit | +| `duplicate` | A file already present in the retained selection | +| `unknown` | Fallback for an unclassified rejection | + +`error.file` is optional (for example, a count rejection can apply to a selection). Include a generic +translation when no file is available. If both size and type fail, size takes precedence in the +structured detail. Diagnostic error text is not a localization contract. Other error kinds are +`validation`, `response` and `transport`. + +## Lifecycle and completion + +- `upload()` waits for the logical batch, including pending approvals. Overlapping calls share a + result; calling while idle returns the retained result. File failures and cancellations resolve + in the result, rather than rejecting the promise. `reason: "settled"` does not mean all files succeeded. +- `onComplete` fires once per settled/cancelled logical batch. Its result is cumulative for retained + rows, not a delta; earlier successes can appear again. Use `onUploadSuccess` for per-file domain + side effects so later batches do not repeat them. +- Files rejected during selection and declined approvals do not enter the result. If restrictions + change before a queued file starts, that file's rejection appears in `result.failed`. An empty result is not + proof that a file uploaded. Use `state.allSuccessful` to enable a “continue after upload” action; + transfer progress alone does not prove HTTP success. +- The visible Stop action retains cancelled rows. Continue retries remaining incomplete rows; + remove unwanted cancelled files first. `ref.cancel()` instead cancels work and clears the selection, + retaining inline errors. `ref.reset()` also clears progress and errors. +- `ref.remove(fileId)` removes local state and pending work, never a server resource. +- `initialFiles` is consumed once per mount through the usual restriction/approval path. Rerenders + and reset do not re-add these files. +- `disabled` prevents selection and future request starts, but cancellation/removal remain available. + `selectionDisabled` blocks only picker/drop interaction. + +## Implementation + +`UploadController` owns upload state and scheduling. `useUploadController` connects its lifecycle +and snapshots to React. `FileUpload.tsx` composes selection, progress and individual file rows. +`useRemovalFocus` restores focus after a visible Remove action: the next Remove button, the closest +previous Remove button, enabled Browse, or the widget itself. It waits for React to update the DOM +before choosing among the remaining controls. diff --git a/src/components/FileUpload/UploadController.ts b/src/components/FileUpload/UploadController.ts new file mode 100644 index 00000000..055c3019 --- /dev/null +++ b/src/components/FileUpload/UploadController.ts @@ -0,0 +1,536 @@ +import Uppy, { UppyFile } from "@uppy/core"; +import XHRUpload from "@uppy/xhr-upload"; + +import { + FileUploadError, + FileUploadFile, + FileUploadHandle, + FileUploadParsedProps, + FileUploadResponse, + FileUploadRestriction, + FileUploadResult, + FileUploadState, +} from "./types"; + +type UploadBody = { value: unknown }; +type UploadUppyFile = UppyFile, UploadBody>; + +type EntryState = + | { status: "pendingApproval"; controller: AbortController } + | { status: "queued"; order: number } + | { status: "uploading"; transportId: string } + | { status: "complete"; response: FileUploadResponse } + | { status: "error"; error: FileUploadError } + | { status: "cancelled" }; + +interface Entry { + file: FileUploadFile; + data: Blob; + selectionKey: string; + approved: boolean; + bytesUploaded: number; + state: EntryState; +} + +export interface UploadRow { + file: FileUploadFile; + status: EntryState["status"]; + progress: number; +} + +interface UploadView { + files: readonly UploadRow[]; + state: Readonly; + error?: FileUploadError; + announcement: string; +} + +class ResponseParseError extends Error { + readonly cause: Error; + readonly status: number; + + constructor(cause: Error, status: number) { + super(cause.message); + this.cause = cause; + this.status = status; + this.name = "ResponseParseError"; + } +} + +const asError = (value: unknown): Error => (value instanceof Error ? value : new Error(String(value))); +const percentage = (value: number) => Math.max(0, Math.min(100, Math.round(value))); +const publicFile = (file: UploadUppyFile): FileUploadFile => ({ + id: file.id, + name: file.name ?? "", + type: file.type, + size: file.size ?? undefined, +}); + +/** Owns the selection and scheduler. Uppy holds only files currently handed to transport. */ +export class UploadController implements FileUploadHandle { + readonly uppy: Uppy, UploadBody>; + private readonly props: () => FileUploadParsedProps; + private entries = new Map(); + private listeners = new Set<() => void>(); + private sequence = 0; + private dispatchId?: string; + private scheduled = false; + private disposed = false; + private attached = true; + private enabled = false; + private batch?: { + promise: Promise>; + resolve: (result: FileUploadResult) => void; + }; + private view: UploadView = { + files: [], + announcement: "", + state: { + pendingApproval: 0, + queued: 0, + uploading: 0, + completed: 0, + failed: 0, + cancelled: 0, + progress: 0, + allSuccessful: false, + }, + }; + + constructor(id: string, props: () => FileUploadParsedProps) { + this.props = props; + this.uppy = new Uppy, UploadBody>({ + id, + autoProceed: false, + onBeforeFileAdded: (file) => (this.dispatchId ? { ...file, id: this.dispatchId } : file), + }).use(XHRUpload, { + limit: Number.POSITIVE_INFINITY, + endpoint: (file) => { + const selected = Array.isArray(file) ? undefined : this.transportEntry(file.id); + if (!selected) throw new Error("Upload was cancelled"); + const endpoint = this.props().endpoint; + return typeof endpoint === "function" ? endpoint(selected.file) : endpoint; + }, + headers: () => { + const headers = this.props().headers; + return typeof headers === "function" ? headers() : (headers ?? {}); + }, + getResponseData: (xhr) => { + try { + // The envelope prevents Uppy from substituting its parser for a null/undefined result. + return { + value: this.props().parseResponse({ status: xhr.status, responseText: xhr.responseText }), + }; + } catch (error) { + throw new ResponseParseError(asError(error), xhr.status); + } + }, + }); + this.uppy.on("files-added", this.filesAdded); + this.uppy.on("restriction-failed", (file, error) => { + if (!this.dispatchId) + this.reportError({ + kind: "restriction", + error, + file: file ? publicFile(file) : undefined, + restriction: this.restrictionDetails(error, file ? publicFile(file) : undefined), + }); + }); + this.uppy.on("upload-progress", (file, progress) => { + const entry = file && this.transportEntry(file.id); + if (!entry) return; + entry.bytesUploaded = Math.min(entry.file.size ?? progress.bytesUploaded, progress.bytesUploaded); + this.publish(); + }); + this.uppy.on("upload-success", (file, response) => { + const entry = file && this.transportEntry(file.id); + if (!entry) return; + const result = { file: entry.file, status: response.status, body: response.body?.value }; + entry.state = { status: "complete", response: result }; + entry.bytesUploaded = entry.file.size ?? 0; + this.uppy.removeFile(file.id); + this.view = { ...this.view, announcement: this.props().labels.uploadedFile(entry.file) }; + this.publish(); + if (!this.disposed && this.entries.get(entry.file.id) === entry) this.props().onUploadSuccess?.(result); + this.schedule(); + }); + this.uppy.on("upload-error", (file, error, response) => { + const entry = file && this.transportEntry(file.id); + if (!entry) return; + const parseError = error instanceof ResponseParseError; + this.fail(entry, { + kind: parseError ? "response" : "transport", + error, + file: entry.file, + status: parseError ? error.status : response?.status, + }); + }); + this.configure(); + } + + getSnapshot = (): UploadView => this.view; + attach = (): (() => void) => { + this.attached = true; + return () => { + this.attached = false; + // React StrictMode reattaches effects synchronously. Suppress callbacks immediately, + // but only destroy the transport if this is an actual unmount. + queueMicrotask(() => { + if (!this.attached) this.dispose(); + }); + }; + }; + subscribe = (listener: () => void): (() => void) => { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + }; + + configure = (): void => { + const props = this.props(); + this.uppy.setOptions({ + restrictions: { + allowedFileTypes: props.acceptedFileTypes, + maxFileSize: props.maxFileSize, + maxNumberOfFiles: props.maxNumberOfFiles === null ? undefined : (props.maxNumberOfFiles ?? 1), + }, + }); + this.uppy.getPlugin("XHRUpload")?.setOptions({ method: props.method ?? "POST" }); + this.schedule(); + }; + + addInitialFiles = (files: readonly File[]): void => { + this.uppy.addFiles(files.map((data) => ({ data, name: data.name, type: data.type }))); + }; + + private filesAdded = (files: UploadUppyFile[]): void => { + if (this.dispatchId || this.disposed || files.length === 0) return; + // Detach selection from transport immediately. Approval and manual mode never hold XHR slots. + files.forEach((file) => this.uppy.removeFile(file.id)); + const retained = [...this.entries.values()].filter((entry) => entry.state.status !== "complete"); + const maximum = this.props().maxNumberOfFiles === null ? Infinity : (this.props().maxNumberOfFiles ?? 1); + if (retained.length + files.length > maximum) { + this.reportError({ + kind: "restriction", + restriction: { code: "maxNumberOfFiles", maxNumberOfFiles: maximum }, + error: new Error(this.uppy.i18n("youCanOnlyUploadX", { smart_count: maximum })), + }); + return; + } + const accepted: Entry[] = []; + for (const file of files) { + if (file.isRemote || !file.data) continue; + if (retained.some((entry) => entry.selectionKey === file.id)) { + this.reportError({ + kind: "restriction", + restriction: { code: "duplicate" }, + file: publicFile(file), + error: new Error(this.uppy.i18n("noDuplicates", { fileName: file.name ?? "" })), + }); + continue; + } + const entry: Entry = { + file: { ...publicFile(file), id: `${file.id}-${++this.sequence}` }, + data: file.data, + selectionKey: file.id, + approved: false, + bytesUploaded: 0, + state: { status: "pendingApproval", controller: new AbortController() }, + }; + this.entries.set(entry.file.id, entry); + accepted.push(entry); + } + if (!accepted.length) return; + this.openBatch(); + this.view = { + ...this.view, + error: undefined, + announcement: accepted.map((entry) => this.props().labels.selectedFile(entry.file)).join(". "), + }; + this.publish(); + this.props().onFilesAdded?.(accepted.map((entry) => entry.file)); + accepted.forEach((entry) => this.approve(entry)); + }; + + private approve(entry: Entry): void { + if (entry.state.status !== "pendingApproval") return; + const { controller } = entry.state; + const isCurrent = () => + this.attached && !this.disposed && !controller.signal.aborted && this.entries.get(entry.file.id) === entry; + const accept = (approved: boolean) => { + if (!isCurrent()) return; + if (approved) { + entry.approved = true; + entry.state = { status: "queued", order: ++this.sequence }; + } else { + this.entries.delete(entry.file.id); + } + this.publish(); + this.schedule(); + }; + const beforeUpload = this.props().beforeUpload; + if (!beforeUpload) { + accept(true); + return; + } + Promise.resolve() + .then(() => (isCurrent() ? beforeUpload(entry.file, controller.signal) : false)) + .then(accept, (error) => { + if (isCurrent()) this.fail(entry, { kind: "validation", error: asError(error), file: entry.file }); + }); + } + + private openBatch(): void { + if (this.batch) return; + let resolve!: (result: FileUploadResult) => void; + const promise = new Promise>((done) => { + resolve = done; + }); + this.batch = { promise, resolve }; + this.enabled = this.props().autoUpload !== false; + } + + upload = (): Promise> => { + if (this.props().disabled) return Promise.reject(new Error("File upload is disabled")); + if (!this.batch) return Promise.resolve(this.result("settled")); + this.enabled = true; + this.schedule(); + return this.batch.promise; + }; + + private schedule(): void { + if (this.scheduled || this.disposed) return; + this.scheduled = true; + queueMicrotask(() => { + this.scheduled = false; + if (this.disposed || !this.attached) return; + const props = this.props(); + if (!props.disabled && (this.enabled || props.autoUpload !== false)) { + const concurrency = Math.max(1, props.concurrency ?? 1); + const ready = [...this.entries.values()] + .filter((entry) => entry.state.status === "queued") + .sort( + (a, b) => + (a.state.status === "queued" ? a.state.order : 0) - + (b.state.status === "queued" ? b.state.order : 0), + ); + for (const entry of ready) { + if (this.disposed || this.props().disabled || this.view.state.uploading >= concurrency) break; + if (this.entries.get(entry.file.id) === entry && entry.state.status === "queued") this.start(entry); + } + } + this.settle(); + }); + } + + private start(entry: Entry): void { + const transportId = `${entry.file.id}-request-${++this.sequence}`; + entry.state = { status: "uploading", transportId }; + this.dispatchId = transportId; + try { + this.uppy.addFile({ data: entry.data, name: entry.file.name, type: entry.file.type }); + } catch (error) { + const diagnostic = asError(error); + this.fail(entry, { + kind: "restriction", + error: diagnostic, + file: entry.file, + restriction: this.restrictionDetails(diagnostic, entry.file), + }); + return; + } finally { + this.dispatchId = undefined; + } + this.publish(); + if (this.transportEntry(transportId) !== entry) return; + void this.uppy.upload().catch((error) => { + if (this.transportEntry(transportId) === entry) { + this.fail(entry, { kind: "transport", error: asError(error), file: entry.file }); + } + }); + } + + private restrictionDetails(error: Error, file?: FileUploadFile): FileUploadRestriction { + if (!("isRestriction" in error) || !error.isRestriction) return { code: "unknown" }; + const { maxFileSize, acceptedFileTypes, maxNumberOfFiles = 1 } = this.props(); + // Uppy is configured with only size/type (file) and count (selection) restrictions. + // Derive a violated rule from inputs, never from Uppy message text. If both size and type + // fail, report size first; after it is fixed, type validation still applies. + if (file) { + if (maxFileSize && file.size !== undefined && file.size > maxFileSize) { + return { code: "maxFileSize", maxFileSize }; + } + if (acceptedFileTypes) return { code: "fileType", acceptedFileTypes: [...acceptedFileTypes] }; + } else if (maxNumberOfFiles) { + return { code: "maxNumberOfFiles", maxNumberOfFiles }; + } + return { code: "unknown" }; + } + + private transportEntry(id: string): Entry | undefined { + if (this.disposed || !this.attached) return undefined; + return [...this.entries.values()].find( + (entry) => entry.state.status === "uploading" && entry.state.transportId === id, + ); + } + + private interrupt(entry: Entry): void { + const previous = entry.state; + entry.state = { status: "cancelled" }; + entry.bytesUploaded = 0; + if (previous.status === "pendingApproval") previous.controller.abort(); + if (previous.status === "uploading") this.uppy.removeFile(previous.transportId); + } + + cancelFile = (id: string): void => { + const entry = this.entries.get(id); + if (!entry || entry.state.status === "complete" || entry.state.status === "cancelled") return; + this.interrupt(entry); + this.publish(); + this.schedule(); + }; + + stop = (): void => { + for (const entry of this.entries.values()) { + if (entry.state.status !== "complete" && entry.state.status !== "error") this.interrupt(entry); + } + this.publish(); + this.settle(); + }; + + retry = (id: string): void => { + const entry = this.entries.get(id); + if (this.props().disabled || !entry || !["error", "cancelled"].includes(entry.state.status)) return; + this.openBatch(); + this.enabled = true; + entry.bytesUploaded = 0; + entry.state = entry.approved + ? { status: "queued", order: ++this.sequence } + : { status: "pendingApproval", controller: new AbortController() }; + this.view = { ...this.view, error: undefined }; + this.approve(entry); + this.publish(); + this.schedule(); + }; + + continue = (): void => { + for (const entry of this.entries.values()) if (entry.state.status === "cancelled") this.retry(entry.file.id); + }; + + remove = (id: string): void => { + const entry = this.entries.get(id); + if (!entry) return; + this.entries.delete(id); + this.interrupt(entry); + this.view = { ...this.view, announcement: this.props().labels.removedFile(entry.file) }; + this.publish(); + this.schedule(); + }; + + cancel = (): void => { + for (const entry of this.entries.values()) if (entry.state.status !== "complete") this.interrupt(entry); + const result = this.result("cancelled"); + const batch = this.batch; + this.batch = undefined; + this.entries.clear(); + this.view = { ...this.view, announcement: "" }; + this.publish(); + batch?.resolve(result); + if (batch && !this.disposed) this.props().onComplete?.(result); + }; + + reset = (): void => { + this.view = { ...this.view, error: undefined }; + this.cancel(); + }; + + dispose = (): void => { + this.disposed = true; + this.cancel(); + this.listeners.clear(); + this.uppy.destroy(); + }; + + private fail(entry: Entry, error: FileUploadError): void { + this.interrupt(entry); + entry.state = { status: "error", error }; + this.reportError(error); + this.schedule(); + } + + private reportError(error: FileUploadError): void { + if (this.disposed || !this.attached) return; + this.view = { ...this.view, error }; + this.publish(); + this.props().onUploadError?.(error); + } + + private result(reason: FileUploadResult["reason"]): FileUploadResult { + const successful: FileUploadResponse[] = []; + const failed: FileUploadError[] = []; + const cancelled: FileUploadFile[] = []; + for (const entry of this.entries.values()) { + if (entry.state.status === "complete") successful.push(entry.state.response); + if (entry.state.status === "error") failed.push(entry.state.error); + if (entry.state.status === "cancelled") cancelled.push(entry.file); + } + return { reason, successful, failed, cancelled }; + } + + private settle(): void { + const { pendingApproval, queued, uploading } = this.view.state; + if (!this.batch || pendingApproval || queued || uploading) return; + const batch = this.batch; + this.batch = undefined; + const result = this.result("settled"); + batch.resolve(result); + this.props().onComplete?.(result); + } + + private publish(): void { + if (this.disposed || !this.attached) return; + const state: FileUploadState = { + pendingApproval: 0, + queued: 0, + uploading: 0, + completed: 0, + failed: 0, + cancelled: 0, + progress: 0, + allSuccessful: false, + }; + let bytesTotal = 0, + bytesUploaded = 0, + approved = 0; + const files = [...this.entries.values()].map((entry) => { + const { status } = entry.state; + if (status === "complete") state.completed++; + else if (status === "error") state.failed++; + else state[status]++; + if (entry.approved) { + approved++; + bytesTotal += entry.file.size ?? 0; + bytesUploaded += entry.bytesUploaded; + } + return { + file: entry.file, + status, + progress: + status === "complete" + ? 100 + : entry.file.size + ? percentage((entry.bytesUploaded / entry.file.size) * 100) + : 0, + }; + }); + state.progress = bytesTotal + ? percentage((bytesUploaded / bytesTotal) * 100) + : approved + ? percentage((state.completed / approved) * 100) + : 0; + state.allSuccessful = files.length > 0 && state.completed === files.length; + const changed = JSON.stringify(state) !== JSON.stringify(this.view.state); + this.view = { ...this.view, files, state }; + this.listeners.forEach((listener) => listener()); + if (changed) this.props().onStateChange?.(state); + } +} diff --git a/src/components/FileUpload/fileupload.scss b/src/components/FileUpload/fileupload.scss new file mode 100644 index 00000000..31989c76 --- /dev/null +++ b/src/components/FileUpload/fileupload.scss @@ -0,0 +1,116 @@ +.#{$eccgui}-fileupload { + display: grid; + gap: $eccgui-size-inline-whitespace; +} + +.#{$eccgui}-fileupload__label { + font-weight: $eccgui-font-weight-bold; +} + +.#{$eccgui}-fileupload__dropzone { + color: $eccgui-color-applicationheader-text; + background: $eccgui-color-workspace-background; + border: $button-border-width dashed eccgui-color-rgba($black, $pt-drop-shadow-opacity); + border-radius: $button-border-radius; + transition: + background-color 150ms, + border-color 150ms, + box-shadow 150ms; +} + +.#{$eccgui}-fileupload__dropzone--dragging { + background: eccgui-color-rgba($eccgui-color-accent, $eccgui-opacity-ghostly); + border-color: $eccgui-color-accent; + box-shadow: 0 0 $eccgui-size-block-whitespace $eccgui-color-accent inset; +} + +.#{$eccgui}-fileupload__dropzone--disabled { + opacity: $eccgui-opacity-disabled; +} + +.#{$eccgui}-fileupload__button { + display: flex; + flex-direction: column; + gap: $eccgui-size-inline-whitespace; + align-items: center; + justify-content: center; + width: 100%; + min-height: 8 * $eccgui-size-block-whitespace; + padding: $eccgui-size-block-whitespace; + font: inherit; + color: inherit; + cursor: pointer; + background: transparent; + border: 0; + border-radius: inherit; + + &:focus-visible { + @include focus-by-keyboard-static; + } + + &:disabled { + cursor: not-allowed; + } +} + +.#{$eccgui}-fileupload__instructions, +.#{$eccgui}-fileupload [role="status"] { + font-size: $eccgui-size-typo-caption; + color: eccgui-color-rgba($eccgui-color-applicationheader-text, $eccgui-opacity-muted); +} + +.#{$eccgui}-fileupload__progress { + display: grid; + gap: $eccgui-size-inline-whitespace; +} + +.#{$eccgui}-fileupload__overall-progress, +.#{$eccgui}-fileupload__file-list, +.#{$eccgui}-fileupload__file { + display: grid; + gap: 0.5 * $eccgui-size-inline-whitespace; +} + +.#{$eccgui}-fileupload__overall-progress { + padding-bottom: $eccgui-size-inline-whitespace; + border-bottom: 1px solid $pt-divider-black; +} + +.#{$eccgui}-fileupload__file + .#{$eccgui}-fileupload__file { + padding-top: $eccgui-size-inline-whitespace; + border-top: 1px solid $pt-divider-black; +} + +.#{$eccgui}-fileupload__progress-header { + display: flex; + gap: $eccgui-size-inline-whitespace; + justify-content: space-between; + font-weight: $eccgui-font-weight-bold; + + > :first-child { + overflow-wrap: anywhere; + } +} + +.#{$eccgui}-fileupload__progress-actions { + display: flex; + flex-shrink: 0; + gap: $eccgui-size-inline-whitespace; + align-items: center; +} + +.#{$eccgui}-fileupload__completed-files { + font-size: $eccgui-size-typo-caption; + color: eccgui-color-rgba($eccgui-color-applicationheader-text, $eccgui-opacity-muted); +} + +.#{$eccgui}-fileupload__cancelled-status { + display: flex; + gap: 0.5 * $eccgui-size-inline-whitespace; + align-items: center; + color: $eccgui-color-applicationheader-text; +} + +.#{$eccgui}-fileupload__error { + color: $eccgui-color-danger-text; +} diff --git a/src/components/FileUpload/index.ts b/src/components/FileUpload/index.ts new file mode 100644 index 00000000..e435f981 --- /dev/null +++ b/src/components/FileUpload/index.ts @@ -0,0 +1,2 @@ +export * from "./FileUpload"; +export * from "./types"; diff --git a/src/components/FileUpload/testHelpers.ts b/src/components/FileUpload/testHelpers.ts new file mode 100644 index 00000000..23990215 --- /dev/null +++ b/src/components/FileUpload/testHelpers.ts @@ -0,0 +1,130 @@ +import { act } from "@testing-library/react"; +import { FileUploadLabels } from "./types"; + +export const labels: FileUploadLabels = { + cancelFile: "Cancel upload", + continueUpload: "Continue uploads", + browse: "browse files", + dropHereOr: "Drop files here or", + uploadProgress: "Upload progress", + overallUploadProgress: "Overall upload progress", + fileUploadProgress: (file) => `Upload progress for ${file.name}`, + completedFiles: (completed, total) => `${completed} of ${total} files completed`, + retry: "Retry", + stopUploads: "Stop uploads", + uploadCancelled: "Upload cancelled", + removeFile: "Remove", + removedFile: (file) => `${file.name} removed`, + selectedFile: (file) => `${file.name} selected`, + uploadedFile: (file) => `${file.name} uploaded`, + formatError: ({ kind, error }) => + kind === "response" ? `Invalid response: ${error.message}` : `Upload failed: ${error.message}`, +}; + +export class ControlledXMLHttpRequest { + static requests: ControlledXMLHttpRequest[] = []; + + method = ""; + url = ""; + requestBody: Document | XMLHttpRequestBodyInit | null = null; + requestHeaders: Record = {}; + response: unknown; + responseText = ""; + responseType: XMLHttpRequestResponseType = ""; + status = 0; + statusText = ""; + withCredentials = false; + aborted = false; + sent = false; + onabort: (() => void | Promise) | null = null; + onerror: (() => void | Promise) | null = null; + onload: (() => void | Promise) | null = null; + upload = { onprogress: null as ((event: ProgressEvent) => void) | null }; + + constructor() { + ControlledXMLHttpRequest.requests.push(this); + } + + abort() { + this.aborted = true; + void this.onabort?.(); + } + + open(method: string, url: string) { + this.method = method; + this.url = url; + } + + send(body: Document | XMLHttpRequestBodyInit | null) { + this.requestBody = body; + this.sent = true; + } + + setRequestHeader(name: string, value: string) { + this.requestHeaders[name] = value; + } + + progress(loaded: number, total: number) { + this.upload.onprogress?.({ lengthComputable: true, loaded, total } as ProgressEvent); + } + + async fail(message: string) { + this.statusText = message; + await this.onerror?.(); + } + + async respond(status: number, responseText: string) { + this.status = status; + this.responseText = responseText; + await this.onload?.(); + } +} + +class ControlledFormData { + append() {} +} + +const NativeXMLHttpRequest = global.XMLHttpRequest; +const NativeFormData = global.FormData; +const nativeAbortSignalAny = AbortSignal.any; + +const combineAbortSignals = (signals: AbortSignal[]): AbortSignal => { + const controller = new AbortController(); + signals.forEach((signal) => { + if (signal.aborted) controller.abort(); + else signal.addEventListener("abort", () => controller.abort(), { once: true }); + }); + return controller.signal; +}; + +export const completeRequest = async (request: ControlledXMLHttpRequest, status: number, responseText: string) => { + await act(async () => { + await request.respond(status, responseText); + await new Promise((resolve) => setTimeout(resolve, 0)); + }); +}; + +export const failRequest = async (request: ControlledXMLHttpRequest, message: string) => { + await act(async () => { + await request.fail(message); + await new Promise((resolve) => setTimeout(resolve, 0)); + }); +}; + +beforeEach(() => { + ControlledXMLHttpRequest.requests = []; + global.XMLHttpRequest = ControlledXMLHttpRequest as unknown as typeof XMLHttpRequest; + window.XMLHttpRequest = ControlledXMLHttpRequest as unknown as typeof XMLHttpRequest; + global.FormData = ControlledFormData as unknown as typeof FormData; + window.FormData = ControlledFormData as unknown as typeof FormData; + AbortSignal.any = combineAbortSignals; +}); + +afterEach(() => { + jest.restoreAllMocks(); + global.XMLHttpRequest = NativeXMLHttpRequest; + window.XMLHttpRequest = NativeXMLHttpRequest; + global.FormData = NativeFormData; + window.FormData = NativeFormData; + AbortSignal.any = nativeAbortSignalAny; +}); diff --git a/src/components/FileUpload/types.ts b/src/components/FileUpload/types.ts new file mode 100644 index 00000000..da0ea73b --- /dev/null +++ b/src/components/FileUpload/types.ts @@ -0,0 +1,143 @@ +export interface FileUploadHandle { + /** + * Starts/waits for the current logical batch; overlapping calls share a result. + * File failures and cancellation resolve in the result, rather than rejecting this promise. + * Calling while disabled rejects. An idle call resolves with the retained selection's result. + */ + upload(): Promise>; + /** Cancels pending work and clears the selection, retaining inline errors. */ + cancel(): void; + /** Removes a local row and pending work; never deletes a server resource. */ + remove(fileId: string): void; + /** Cancels pending work and clears selection, progress and errors. */ + reset(): void; +} + +export interface FileUploadFile { + id: string; + name: string; + type?: string; + size?: number; +} + +export interface FileUploadResponse { + body: T; + status: number; + file: FileUploadFile; +} + +export type FileUploadErrorKind = "restriction" | "validation" | "response" | "transport"; + +/** Machine-readable restriction details for localized messages; sizes are in bytes. */ +export type FileUploadRestriction = + | { code: "maxFileSize"; maxFileSize: number } + | { code: "fileType"; acceptedFileTypes: readonly string[] } + | { code: "maxNumberOfFiles"; maxNumberOfFiles: number } + | { code: "duplicate" } + | { code: "unknown" }; + +interface FileUploadErrorDetails { + /** Diagnostic error. Use restriction details, not this message, to localize restrictions. */ + error: Error; + file?: FileUploadFile; + status?: number; +} + +export type FileUploadError = FileUploadErrorDetails & + ( + | { kind: "restriction"; restriction: FileUploadRestriction } + | { kind: Exclude; restriction?: never } + ); + +export interface FileUploadState { + pendingApproval: number; + queued: number; + uploading: number; + completed: number; + failed: number; + cancelled: number; + progress: number; + allSuccessful: boolean; +} + +/** A snapshot of retained files, not a delta from the previous completion. */ +export interface FileUploadResult { + reason: "settled" | "cancelled"; + successful: readonly FileUploadResponse[]; + failed: readonly FileUploadError[]; + cancelled: readonly FileUploadFile[]; +} + +export interface FileUploadLabels { + cancelFile: string; + continueUpload: string; + dropHereOr: string; + browse: string; + uploadProgress: string; + overallUploadProgress: string; + fileUploadProgress: (file: FileUploadFile) => string; + completedFiles: (completed: number, total: number) => string; + retry: string; + removeFile: string; + removedFile: (file: FileUploadFile) => string; + stopUploads: string; + uploadCancelled: string; + selectedFile: (file: FileUploadFile) => string; + uploadedFile: (file: FileUploadFile) => string; + formatError: (error: FileUploadError) => string; +} + +export interface FileUploadResponseMetadata { + status: number; + responseText: string; +} + +export type FileUploadEndpoint = string | ((file: FileUploadFile) => string); +export type FileUploadHeaders = Record | (() => Record); +/** True approves, false declines; throwing/rejecting produces a retriable validation error. */ +export type FileUploadApproval = (file: FileUploadFile, signal: AbortSignal) => boolean | Promise; + +export interface FileUploadBaseProps { + id?: string; + /** Localized widget label and accessible name. */ + name: string; + hideName?: boolean; + labels: FileUploadLabels; + instructions?: string; + endpoint: FileUploadEndpoint; + acceptedFileTypes?: string[]; + maxFileSize?: number; + /** Maximum retained incomplete files. Defaults to 1; null removes the restriction. */ + maxNumberOfFiles?: number | null; + /** Maximum active requests across selections and retries. Defaults to 1. */ + concurrency?: number; + /** Start approved files automatically. Defaults to true. */ + autoUpload?: boolean; + /** Initialized once per mount; reset/rerender does not add these files again. */ + initialFiles?: readonly File[]; + /** + * Approve each file after selection and local restrictions, including in manual mode. + * This is not a per-request hook. Observe the signal to dismiss pending approval UI on cancellation. + */ + beforeUpload?: FileUploadApproval; + method?: "POST" | "PUT"; + headers?: FileUploadHeaders; + /** Selection notification only. Use beforeUpload for approval. */ + onFilesAdded?: (files: FileUploadFile[]) => void; + /** Once per successful file; use this for domain side effects, not cumulative onComplete results. */ + onUploadSuccess?: (response: FileUploadResponse) => void; + onUploadError?: (error: FileUploadError) => void; + onStateChange?: (state: Readonly) => void; + /** Once per settled/cancelled logical batch; includes retained earlier successes, failures and cancellations. */ + onComplete?: (result: FileUploadResult) => void; + /** Blocks selection and future request starts; cancellation/removal remain available. */ + disabled?: boolean; + /** Blocks only picker/drop interaction. */ + selectionDisabled?: boolean; +} + +export type FileUploadTextProps = FileUploadBaseProps & { parseResponse?: undefined }; +export type FileUploadParsedProps = FileUploadBaseProps & { + parseResponse: (metadata: FileUploadResponseMetadata) => T; +}; +export type FileUploadProps = FileUploadParsedProps | (string extends T ? FileUploadTextProps : never); diff --git a/src/components/FileUpload/useRemovalFocus.ts b/src/components/FileUpload/useRemovalFocus.ts new file mode 100644 index 00000000..07b86cc7 --- /dev/null +++ b/src/components/FileUpload/useRemovalFocus.ts @@ -0,0 +1,47 @@ +import React from "react"; + +import { UploadRow } from "./UploadController"; + +/** + * Keep focus within the uploader when a visible Remove action deletes its own button. + * After React updates the DOM, prefer the next Remove button, then the closest previous one, + * then enabled Browse, and finally the widget itself. + * + * @param files Current rows in display order; only cancelled rows have Remove buttons. + * @param removeFile Removes the selected row from the upload controller. + * @returns Refs for the group and Browse button, a Remove-button ref registry, and removeRow + * for visible Remove actions. Attach groupRef to an element with tabIndex={-1} for the final fallback. + */ +export function useRemovalFocus(files: readonly UploadRow[], removeFile: (fileId: string) => void) { + const groupRef = React.useRef(null); + const browseRef = React.useRef(null); + const removeRefs = React.useRef(new Map()); + const pendingFocusIds = React.useRef(); + + React.useEffect(() => { + if (!pendingFocusIds.current) return; + // Wait for the updated DOM refs: focus the next Remove, then the closest previous Remove, + // then Browse. The group remains focusable when selection is disabled. + const nextRemoveButton = pendingFocusIds.current.map((id) => removeRefs.current.get(id)).find(Boolean); + pendingFocusIds.current = undefined; + const browseButton = browseRef.current; + const fallbackFocusTarget = browseButton && !browseButton.disabled ? browseButton : groupRef.current; + (nextRemoveButton ?? fallbackFocusTarget)?.focus(); + }, [files]); + + const registerRemoveButton = (fileId: string, element: HTMLButtonElement | HTMLAnchorElement | null) => { + if (element) removeRefs.current.set(fileId, element); + else removeRefs.current.delete(fileId); + }; + + const removeRow = (fileId: string) => { + const removableIds = files.filter((row) => row.status === "cancelled").map((row) => row.file.id); + const index = removableIds.indexOf(fileId); + const followingRemovableIds = removableIds.slice(index + 1); + const precedingRemovableIds = removableIds.slice(0, index).reverse(); + pendingFocusIds.current = [...followingRemovableIds, ...precedingRemovableIds]; + removeFile(fileId); + }; + + return { groupRef, browseRef, registerRemoveButton, removeRow }; +} diff --git a/src/components/FileUpload/useUploadController.ts b/src/components/FileUpload/useUploadController.ts new file mode 100644 index 00000000..5b86d68f --- /dev/null +++ b/src/components/FileUpload/useUploadController.ts @@ -0,0 +1,60 @@ +import React from "react"; + +import { FileUploadBaseProps, FileUploadResponseMetadata } from "./types"; +import { UploadController } from "./UploadController"; + +/** Internal props shared by uploads with plain-text responses and uploads with a custom parser. */ +export type FileUploadControllerProps = FileUploadBaseProps & { + parseResponse?: (metadata: FileUploadResponseMetadata) => unknown; +}; + +const readResponseText = (metadata: FileUploadResponseMetadata): string => metadata.responseText; + +/** + * Connect one upload controller to the component's lifetime and subscribe to its view snapshot. + * The controller keeps its identity while reading current props, applies changed configuration, + * and releases its resources on unmount. Initial files are consumed only once per mount. + * + * @param widgetId Identifier used when creating the controller; keep it stable for this mount. + * @param props Current upload configuration and callbacks; an omitted parser returns response text. + * @returns The stable controller for actions and the current snapshot for rendering. + */ +export function useUploadController( + widgetId: string, + props: FileUploadControllerProps, +): { controller: UploadController; snapshot: ReturnType } { + // Keep the controller across renders while letting ongoing uploads read current callbacks and options. + const latestProps = React.useRef(props); + latestProps.current = props; + const [controller] = React.useState( + () => + new UploadController(widgetId, () => ({ + ...latestProps.current, + parseResponse: latestProps.current.parseResponse ?? readResponseText, + })), + ); + const snapshot = React.useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot); + const initialFilesAdded = React.useRef(false); + + React.useEffect(() => controller.attach(), [controller]); + React.useEffect(() => { + controller.configure(); + }, [ + controller, + props.acceptedFileTypes, + props.maxFileSize, + props.maxNumberOfFiles, + props.method, + props.concurrency, + props.disabled, + props.autoUpload, + ]); + React.useEffect(() => { + // Initial files belong to this mount; neither StrictMode effect replay nor reset should add them again. + if (initialFilesAdded.current) return; + initialFilesAdded.current = true; + if (props.initialFiles?.length) controller.addInitialFiles(props.initialFiles); + }, [controller, props.initialFiles]); + + return { controller, snapshot }; +} diff --git a/src/components/index.scss b/src/components/index.scss index b3852c48..cdf9be9e 100644 --- a/src/components/index.scss +++ b/src/components/index.scss @@ -10,6 +10,7 @@ @import "./Dialog/dialog"; @import "./FlexibleLayout/flexiblelayout"; @import "./Form/form"; +@import "./FileUpload/fileupload"; @import "./Grid/grid"; @import "./HoverToggler/hovertoggler"; @import "./Icon/icon"; diff --git a/src/components/index.ts b/src/components/index.ts index e4cd3125..6206cb8e 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -14,6 +14,7 @@ export * from "./DecoupledOverlay/DecoupledOverlay"; export * from "./Depiction/Depiction"; export * from "./Dialog"; export * from "./FlexibleLayout"; +export * from "./FileUpload"; export * from "./Form"; export * from "./Grid"; export * from "./HoverToggler/HoverToggler"; diff --git a/src/extensions/codemirror/debouncedLinter.ts b/src/extensions/codemirror/debouncedLinter.ts index b0af1076..8d31704d 100644 --- a/src/extensions/codemirror/debouncedLinter.ts +++ b/src/extensions/codemirror/debouncedLinter.ts @@ -1,9 +1,10 @@ import { Diagnostic } from "@codemirror/lint"; import { EditorView } from "@codemirror/view"; -import { debounce } from "lodash"; +import lodash from "lodash"; import { Linter } from "./types"; +const { debounce } = lodash; const DEBOUNCE_TIME = 500; export const debouncedLinter = (lintFunction: Linter, time = DEBOUNCE_TIME) => { diff --git a/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.test.ts b/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.test.ts new file mode 100644 index 00000000..d961e65f --- /dev/null +++ b/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.test.ts @@ -0,0 +1,15 @@ +import { EditorState } from "@codemirror/state"; +import { renderHook } from "@testing-library/react"; + +import { supportedCodeEditorModes, useCodeMirrorModeExtension } from "./useCodemirrorModeExtension.hooks"; + +it.each(supportedCodeEditorModes)("constructs a usable CodeMirror extension for %s", (mode) => { + const { result } = renderHook(() => useCodeMirrorModeExtension(mode)); + const state = EditorState.create({ doc: "example", extensions: [result.current] }); + expect(state.doc.toString()).toBe("example"); +}); + +it("supports the default highlighting without a language", () => { + const { result } = renderHook(() => useCodeMirrorModeExtension()); + expect(() => EditorState.create({ extensions: [result.current] })).not.toThrow(); +}); diff --git a/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.ts b/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.ts index f1fc518a..c052d366 100644 --- a/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.ts +++ b/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.ts @@ -6,7 +6,7 @@ import { markdown } from "@codemirror/lang-markdown"; import { sql } from "@codemirror/lang-sql"; import { xml } from "@codemirror/lang-xml"; import { yaml } from "@codemirror/lang-yaml"; -import { defaultHighlightStyle, LanguageSupport, StreamLanguage, StreamParser } from "@codemirror/language"; +import { defaultHighlightStyle, StreamLanguage, StreamParser } from "@codemirror/language"; //legacy mode imports import { jinja2 } from "@codemirror/legacy-modes/mode/jinja2"; import { mathematica } from "@codemirror/legacy-modes/mode/mathematica"; @@ -37,24 +37,12 @@ const supportedModes = { export const supportedCodeEditorModes = Object.keys(supportedModes) as Array; export type SupportedCodeEditorModes = (typeof supportedCodeEditorModes)[number]; -const v6AdaptedModes: ReadonlyMap = new Map([ - ["json", true], - ["markdown", true], - ["xml", true], - ["sql", true], - ["yaml", true], - ["javascript", true], - ["html", true], -]); - export const useCodeMirrorModeExtension = (mode?: SupportedCodeEditorModes) => { - return !mode - ? adaptedSyntaxHighlighting(defaultHighlightStyle) - : v6AdaptedModes.has(mode) - ? ( - (typeof supportedModes[mode] === "function" - ? supportedModes[mode] - : () => null) as () => LanguageSupport - )() - : StreamLanguage?.define(supportedModes[mode] as StreamParser); + if (!mode) return adaptedSyntaxHighlighting(defaultHighlightStyle); + const language = supportedModes[mode]; + // Legacy-mode declarations reference ESM CodeMirror types even for their CommonJS entry. + // Both entries implement the same parser contract, but StringStream has private members. + return typeof language === "function" + ? language() + : StreamLanguage.define(language as unknown as StreamParser); }; diff --git a/tests/package-consumer/commonjs.cts b/tests/package-consumer/commonjs.cts new file mode 100644 index 00000000..d3ee7baa --- /dev/null +++ b/tests/package-consumer/commonjs.cts @@ -0,0 +1,7 @@ +import guiElements = require("@eccenca/gui-elements"); + +export const button: typeof guiElements.Button = guiElements.Button; +export const upload: typeof guiElements.FileUpload = guiElements.FileUpload; + +// @ts-expect-error CommonJS imports must retain the declared component types. +export const invalidUpload: typeof guiElements.FileUpload = 42; diff --git a/tests/package-consumer/consumer.tsx b/tests/package-consumer/consumer.tsx new file mode 100644 index 00000000..f1448b7e --- /dev/null +++ b/tests/package-consumer/consumer.tsx @@ -0,0 +1,33 @@ +import { Button, FileUpload, type FileUploadLabels, type FileUploadProps } from "@eccenca/gui-elements"; + +declare const labels: FileUploadLabels; +const uploadProps = { name: "Upload", endpoint: "/upload", labels }; + +export const button = ; +export const textUpload = ( + file.name.endsWith(".ttl")} + onUploadSuccess={({ body }) => { + body.toUpperCase(); + // @ts-expect-error The default response body is a string, not any. + body.missingProperty; + }} + /> +); +export const parsedUpload = ( + ({ id: 42 })} + onUploadSuccess={({ body }) => { + body.id.toFixed(); + // @ts-expect-error The parser determines the response body's type. + body.toUpperCase(); + }} + /> +); + +// @ts-expect-error Approval must return a boolean, not a message. +export const invalidApproval = "approved"} />; +// @ts-expect-error Structured response props require a parser. +export const invalidProps: FileUploadProps<{ id: number }> = uploadProps; diff --git a/tests/package-consumer/esm.mts b/tests/package-consumer/esm.mts new file mode 100644 index 00000000..044674c9 --- /dev/null +++ b/tests/package-consumer/esm.mts @@ -0,0 +1,7 @@ +import { Button, FileUpload } from "@eccenca/gui-elements"; + +export const button: typeof Button = Button; +export const upload: typeof FileUpload = FileUpload; + +// @ts-expect-error ESM imports must retain the declared component types. +export const invalidUpload: typeof FileUpload = 42; diff --git a/tests/package-consumer/package.json b/tests/package-consumer/package.json new file mode 100644 index 00000000..5a7c88e8 --- /dev/null +++ b/tests/package-consumer/package.json @@ -0,0 +1,4 @@ +{ + "name": "gui-elements-package-consumer", + "private": true +} diff --git a/tests/package-consumer/tsconfig.bundler.json b/tests/package-consumer/tsconfig.bundler.json new file mode 100644 index 00000000..8c6d70ef --- /dev/null +++ b/tests/package-consumer/tsconfig.bundler.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler" + }, + "include": ["consumer.tsx", "esm.mts"] +} diff --git a/tests/package-consumer/tsconfig.json b/tests/package-consumer/tsconfig.json new file mode 100644 index 00000000..074896e5 --- /dev/null +++ b/tests/package-consumer/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "esModuleInterop": true, + "types": ["react", "react-dom"] + }, + "include": ["consumer.tsx", "commonjs.cts", "esm.mts"] +} diff --git a/tests/package-consumer/tsconfig.node16.json b/tests/package-consumer/tsconfig.node16.json new file mode 100644 index 00000000..fc25630f --- /dev/null +++ b/tests/package-consumer/tsconfig.node16.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "Node16", + "moduleResolution": "Node16" + } +} diff --git a/tests/package-consumer/tsconfig.nodenext.json b/tests/package-consumer/tsconfig.nodenext.json new file mode 100644 index 00000000..3ae81326 --- /dev/null +++ b/tests/package-consumer/tsconfig.nodenext.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +} diff --git a/tsconfig.json b/tsconfig.json index 12941d13..2262e29d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,7 @@ "lib": ["dom", "dom.iterable", "es5", "esnext", "es2015.collection", "es2015.iterable"], "jsx": "react", "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "allowJs": true, "resolveJsonModule": true, diff --git a/yarn.lock b/yarn.lock index d00ef687..ee9d456c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -82,6 +82,27 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629" integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg== +"@babel/core@^7.22.5", "@babel/core@^7.29.7", "@babel/core@^7.7.5": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7" + integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA== + dependencies: + "@babel/code-frame" "^7.29.7" + "@babel/generator" "^7.29.7" + "@babel/helper-compilation-targets" "^7.29.7" + "@babel/helper-module-transforms" "^7.29.7" + "@babel/helpers" "^7.29.7" + "@babel/parser" "^7.29.7" + "@babel/template" "^7.29.7" + "@babel/traverse" "^7.29.7" + "@babel/types" "^7.29.7" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/core@^7.23.0", "@babel/core@^7.23.9", "@babel/core@^7.24.4", "@babel/core@^7.26.0": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" @@ -145,26 +166,16 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/core@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7" - integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA== +"@babel/generator@^7.22.5", "@babel/generator@^7.29.7", "@babel/generator@^7.29.8": + version "7.29.8" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.8.tgz#4b0b887885422643339e09022148a4c4ebaa4979" + integrity sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg== dependencies: - "@babel/code-frame" "^7.29.7" - "@babel/generator" "^7.29.7" - "@babel/helper-compilation-targets" "^7.29.7" - "@babel/helper-module-transforms" "^7.29.7" - "@babel/helpers" "^7.29.7" - "@babel/parser" "^7.29.7" - "@babel/template" "^7.29.7" - "@babel/traverse" "^7.29.7" - "@babel/types" "^7.29.7" - "@jridgewell/remapping" "^2.3.5" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" + "@babel/parser" "^7.29.8" + "@babel/types" "^7.29.8" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" "@babel/generator@^7.26.10", "@babel/generator@^7.27.0": version "7.27.0" @@ -199,17 +210,6 @@ "@jridgewell/trace-mapping" "^0.3.28" jsesc "^3.0.2" -"@babel/generator@^7.29.7", "@babel/generator@^7.29.8": - version "7.29.8" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.8.tgz#4b0b887885422643339e09022148a4c4ebaa4979" - integrity sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg== - dependencies: - "@babel/parser" "^7.29.8" - "@babel/types" "^7.29.8" - "@jridgewell/gen-mapping" "^0.3.12" - "@jridgewell/trace-mapping" "^0.3.28" - jsesc "^3.0.2" - "@babel/helper-annotate-as-pure@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" @@ -1509,6 +1509,15 @@ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-8.0.0.tgz#d7bd513e6843662346552c2798ab895716cf97f2" integrity sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw== +"@babel/template@^7.22.5", "@babel/template@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700" + integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg== + dependencies: + "@babel/code-frame" "^7.29.7" + "@babel/parser" "^7.29.7" + "@babel/types" "^7.29.7" + "@babel/template@^7.26.9", "@babel/template@^7.27.0": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4" @@ -1536,15 +1545,6 @@ "@babel/parser" "^7.28.6" "@babel/types" "^7.28.6" -"@babel/template@^7.29.7": - version "7.29.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700" - integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg== - dependencies: - "@babel/code-frame" "^7.29.7" - "@babel/parser" "^7.29.7" - "@babel/types" "^7.29.7" - "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10", "@babel/traverse@^7.26.5", "@babel/traverse@^7.27.0": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.0.tgz#11d7e644779e166c0442f9a07274d02cd91d4a70" @@ -1605,6 +1605,14 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" +"@babel/types@^7.22.5", "@babel/types@^7.29.7", "@babel/types@^7.29.8": + version "7.29.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.8.tgz#1229eef31d85156d70fa3f4cd859376d0eaf6863" + integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg== + dependencies: + "@babel/helper-string-parser" "^7.29.7" + "@babel/helper-validator-identifier" "^7.29.7" + "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.0", "@babel/types@^7.28.2": version "7.28.2" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" @@ -1629,14 +1637,6 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" -"@babel/types@^7.29.7", "@babel/types@^7.29.8": - version "7.29.8" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.8.tgz#1229eef31d85156d70fa3f4cd859376d0eaf6863" - integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg== - dependencies: - "@babel/helper-string-parser" "^7.29.7" - "@babel/helper-validator-identifier" "^7.29.7" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -2107,6 +2107,14 @@ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz#ef28e27c1ded1d8e5c54879a9399e7055aed1920" integrity sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA== +"@emnapi/core@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.10.0.tgz#380ccc8f2412ea22d1d972df7f8ee23a3b9c7467" + integrity sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw== + dependencies: + "@emnapi/wasi-threads" "1.2.1" + tslib "^2.4.0" + "@emnapi/core@1.11.0": version "1.11.0" resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.11.0.tgz#8a655042dbbb10d0266670c9903c34a7001c705b" @@ -2131,6 +2139,13 @@ "@emnapi/wasi-threads" "1.0.4" tslib "^2.4.0" +"@emnapi/runtime@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c" + integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA== + dependencies: + tslib "^2.4.0" + "@emnapi/runtime@1.11.0": version "1.11.0" resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.11.0.tgz#ce16b3674ff7266bbf50f9668bde8a04f3014d4e" @@ -2457,6 +2472,18 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429" integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" @@ -2610,6 +2637,18 @@ jest-util "30.4.1" slash "^3.0.0" +"@jest/console@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-30.5.1.tgz#54a25bf4fee09a4e4c52b5a903ca7473d89297f6" + integrity sha512-u5Ncuc+gXVUwjNMFQOnphHo2Qx2DxyC8Dvpmf4HCx4y0kvSkRRNiQYRixrvOP4V7y52JcSuNxqochCt0PpdHVg== + dependencies: + "@jest/types" "30.5.1" + "@types/node" "*" + chalk "^4.1.2" + jest-message-util "30.5.1" + jest-util "30.5.1" + slash "^3.0.0" + "@jest/core@30.4.2": version "30.4.2" resolved "https://registry.yarnpkg.com/@jest/core/-/core-30.4.2.tgz#3d4081f894b7e2ff57d04a31842416bd07b76c32" @@ -2644,6 +2683,47 @@ pretty-format "30.4.1" slash "^3.0.0" +"@jest/core@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-30.5.1.tgz#4e2eccc3367105d7b4bacc2fb4ccdbfa34a590f1" + integrity sha512-BL9g6CJUUhIbdoAflz/Va658erSSXUIvU8XUYiWNTbZljJjZ5yaC9EJ9/dQ19rpbYV3ZOK4sBACqhPaTyhoUIA== + dependencies: + "@jest/console" "30.5.1" + "@jest/pattern" "30.5.0" + "@jest/reporters" "30.5.1" + "@jest/test-result" "30.5.1" + "@jest/transform" "30.5.1" + "@jest/types" "30.5.1" + "@types/node" "*" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + ci-info "^4.2.0" + exit-x "^0.2.2" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.11" + jest-changed-files "30.5.1" + jest-config "30.5.1" + jest-haste-map "30.5.1" + jest-message-util "30.5.1" + jest-regex-util "30.5.0" + jest-resolve "30.5.1" + jest-resolve-dependencies "30.5.1" + jest-runner "30.5.1" + jest-runtime "30.5.1" + jest-snapshot "30.5.1" + jest-util "30.5.1" + jest-validate "30.5.1" + jest-watcher "30.5.1" + pretty-format "30.5.1" + slash "^3.0.0" + +"@jest/create-cache-key-function@^30.0.0": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-30.5.1.tgz#dc7c728bb1b4a8b5cb774bdf363e4a26d89d2b0e" + integrity sha512-5qkif//qhlbt54255O/M2JtUZNlnefOzE12bi7FlApOIYO4pqrZcAwhNCfP4jOGFubez8uoLgY9czWFjYY57ow== + dependencies: + "@jest/types" "30.5.1" + "@jest/diff-sequences@30.0.1": version "30.0.1" resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" @@ -2654,6 +2734,11 @@ resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz#8be2d260e6241d6cddddd102c304fe13b4fc8e3e" integrity sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g== +"@jest/diff-sequences@30.5.0": + version "30.5.0" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz#b896d470df751cc0c7d1a0c5078f80a67ce108d4" + integrity sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg== + "@jest/environment-jsdom-abstract@30.4.1": version "30.4.1" resolved "https://registry.yarnpkg.com/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.4.1.tgz#03cf1400aea958733f3a5d20cdc983ffcedfe2b1" @@ -2677,6 +2762,16 @@ "@types/node" "*" jest-mock "30.4.1" +"@jest/environment@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.5.1.tgz#a28a93864515da3ad90d3a841dc32a95be462c52" + integrity sha512-eYJAkOsrpwDXPcoLNEG6lN9Zo3Cy35pxnVQD74vyIfsi/Q8wB/lZFsEjU4wrecOgT4ZviQDZaX/cFIJyMdMxTw== + dependencies: + "@jest/fake-timers" "30.5.1" + "@jest/types" "30.5.1" + "@types/node" "*" + jest-mock "30.5.1" + "@jest/expect-utils@30.2.0": version "30.2.0" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.2.0.tgz#4f95413d4748454fdb17404bf1141827d15e6011" @@ -2691,6 +2786,13 @@ dependencies: "@jest/get-type" "30.1.0" +"@jest/expect-utils@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.5.1.tgz#ae10e1698eff0800de971168aa783ead6a9403df" + integrity sha512-WcRWhHQdTMRDpyWKZ/6MINBmovI7zeD+bL8wFjCncRV3NQOwKy1X45IfyblfHR4k/XciIlNEdFL9QjFO+HNKOg== + dependencies: + "@jest/get-type" "30.5.0" + "@jest/expect@30.4.1": version "30.4.1" resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.4.1.tgz#7fefc67f86c2cb2af3c86d9d41fe4a1d74862b8c" @@ -2699,6 +2801,14 @@ expect "30.4.1" jest-snapshot "30.4.1" +"@jest/expect@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.5.1.tgz#b42ce55ba35cc0cb2ee8aa823baec6b6fbb95d1f" + integrity sha512-uOGd40P/COyUp9xHf5jeGiGJC2/ANg+2+Tk9/xN5/LxmlY/r/gxsPrx3DTEtaRZsLAX4wgNSLEDELZ5bmVs2bA== + dependencies: + expect "30.5.1" + jest-snapshot "30.5.1" + "@jest/fake-timers@30.4.1": version "30.4.1" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.4.1.tgz#ad2d3412d5d005a3e45740bd4c8ee1ccae2f89e1" @@ -2711,11 +2821,28 @@ jest-mock "30.4.1" jest-util "30.4.1" +"@jest/fake-timers@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.5.1.tgz#6e25f439f113216590a56e6423e716a2259a1255" + integrity sha512-rEkV6YzpBXo/L9cnj2ibyuYZuyGiNWaPUsyCu3HYJbqCV4jnEiKmf7hId20z8eKjZ0JQ9jtIYKxRSmYB/OYSsA== + dependencies: + "@jest/types" "30.5.1" + "@sinonjs/fake-timers" "^15.4.0" + "@types/node" "*" + jest-message-util "30.5.1" + jest-mock "30.5.1" + jest-util "30.5.1" + "@jest/get-type@30.1.0": version "30.1.0" resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== +"@jest/get-type@30.5.0": + version "30.5.0" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.5.0.tgz#0fc76dd792523bf05d7715a18041c185f9128cc4" + integrity sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q== + "@jest/globals@30.4.1": version "30.4.1" resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.4.1.tgz#6376975e137ef87926349b5e75ccf230f491e843" @@ -2726,6 +2853,16 @@ "@jest/types" "30.4.1" jest-mock "30.4.1" +"@jest/globals@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.5.1.tgz#2794ea50e7ef7dea2675d04467b9eb7c42f17713" + integrity sha512-VhqvQ251XIC7pk46YymI3HCeBLOdvriDw9zibekodAHEwoVvbVVgpU5H13GvmyOAzxtZCfsm1uvzqkaqJf2I+g== + dependencies: + "@jest/environment" "30.5.1" + "@jest/expect" "30.5.1" + "@jest/types" "30.5.1" + jest-mock "30.5.1" + "@jest/pattern@30.0.1": version "30.0.1" resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" @@ -2742,6 +2879,24 @@ "@types/node" "*" jest-regex-util "30.4.0" +"@jest/pattern@30.5.0": + version "30.5.0" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.5.0.tgz#9f5dd0596a684b81eba2d7c1dfdca8d09978d326" + integrity sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w== + dependencies: + "@types/node" "*" + jest-regex-util "30.5.0" + +"@jest/react-is-18@npm:react-is@^18.3.1": + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +"@jest/react-is-19@npm:react-is@^19.2.5": + version "19.3.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.3.0.tgz#716028d67e9993d23a2d353c6d08a3c1ef2bbeb4" + integrity sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA== + "@jest/reporters@30.4.1": version "30.4.1" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-30.4.1.tgz#41d42533f199e737ae352a0a0b32ff300826efe2" @@ -2771,6 +2926,35 @@ string-length "^4.0.2" v8-to-istanbul "^9.0.1" +"@jest/reporters@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-30.5.1.tgz#5e7dd01fa614fee81ff140bf48d32057bfec23b7" + integrity sha512-RbUXIfv85KxitJn4l3MpAoMilkvXe2QCO5lXxHWftywo/VdZ5vkEoHRDgphcxP6qmoIkUaN8/UZj6NhdkIFdJg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "30.5.1" + "@jest/test-result" "30.5.1" + "@jest/transform" "30.5.1" + "@jest/types" "30.5.1" + "@jridgewell/trace-mapping" "^0.3.31" + "@types/node" "*" + chalk "^4.1.2" + collect-v8-coverage "^1.0.2" + exit-x "^0.2.2" + glob "^13.0.6" + graceful-fs "^4.2.11" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^5.0.0" + istanbul-reports "^3.1.3" + jest-message-util "30.5.1" + jest-util "30.5.1" + jest-worker "30.5.1" + slash "^3.0.0" + string-length "^4.0.2" + v8-to-istanbul "^9.0.1" + "@jest/schemas@30.0.5": version "30.0.5" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" @@ -2785,6 +2969,13 @@ dependencies: "@sinclair/typebox" "^0.34.0" +"@jest/schemas@30.5.0": + version "30.5.0" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.5.0.tgz#781f142de46345b903f43140b15865732abfd356" + integrity sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/snapshot-utils@30.4.1": version "30.4.1" resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz#0f829488b9d46b118854a16a56d509a3c6d9e064" @@ -2795,6 +2986,16 @@ graceful-fs "^4.2.11" natural-compare "^1.4.0" +"@jest/snapshot-utils@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.5.1.tgz#172720d0546ab05d5a7dc7fa3feb74c166c96310" + integrity sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw== + dependencies: + "@jest/types" "30.5.1" + chalk "^4.1.2" + graceful-fs "^4.2.11" + natural-compare "^1.4.0" + "@jest/source-map@30.0.1": version "30.0.1" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-30.0.1.tgz#305ebec50468f13e658b3d5c26f85107a5620aaa" @@ -2804,6 +3005,16 @@ callsites "^3.1.0" graceful-fs "^4.2.11" +"@jest/source-map@30.5.0": + version "30.5.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-30.5.0.tgz#7c3da7fafcffcc92c3605c15a7fcabba120e6f3d" + integrity sha512-xWpTJP9D0bDFGbPGT8XuWSwwha/iHADyyKzUnMx4UbdgnHugxrDaQFO4RZ8x4ZsFzRP6pNii8uvlgKCDxCIuDg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.31" + callsites "^3.1.0" + convert-source-map "^2.0.0" + graceful-fs "^4.2.11" + "@jest/test-result@30.4.1": version "30.4.1" resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-30.4.1.tgz#e21146ebbb3e1f7f76c3c49805d9f39ae45f8de1" @@ -2814,6 +3025,16 @@ "@types/istanbul-lib-coverage" "^2.0.6" collect-v8-coverage "^1.0.2" +"@jest/test-result@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-30.5.1.tgz#084d9221f157bdbe9f16c9ba9b1a83da7831a540" + integrity sha512-A/1S6ZBdpic50E0pxLgvaB9XNPL4k7AksmG69OO2oiotxciWZwHkbOec+qbIi+uUtIIByOVlXJUl97oZUsz+Jw== + dependencies: + "@jest/console" "30.5.1" + "@jest/types" "30.5.1" + "@types/istanbul-lib-coverage" "^2.0.6" + collect-v8-coverage "^1.0.2" + "@jest/test-sequencer@30.4.1": version "30.4.1" resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz#caf9a5e0924ed3b04957441edf9e8cef6a804391" @@ -2824,6 +3045,16 @@ jest-haste-map "30.4.1" slash "^3.0.0" +"@jest/test-sequencer@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-30.5.1.tgz#6c8d48bb957988ed38c82683e051089cc4929751" + integrity sha512-SHcPnrjdVRYJv6y6l4JUTy4Jccu7zmO6BmiOfOA34UiVBto22s19WiDC0A/2qfM7MWB/qdcoqfSIRMitpjTT4A== + dependencies: + "@jest/test-result" "30.5.1" + graceful-fs "^4.2.11" + jest-haste-map "30.5.1" + slash "^3.0.0" + "@jest/transform@30.4.1": version "30.4.1" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.4.1.tgz#1646cddb800d38d9c4e30fecfd4a6eba0fa8acfa" @@ -2844,6 +3075,26 @@ slash "^3.0.0" write-file-atomic "^5.0.1" +"@jest/transform@30.5.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.5.1.tgz#563895f9cb60bc490c3addde758e78f3dd9dc321" + integrity sha512-EDnDhn0jleU9ZhpVoA4gvqw+Ev0iw/r5upNT2b79RiwiaTiYAMMhvNJP3lmocjIe5J2ZkoNr0B3K/J6O5GIm4Q== + dependencies: + "@babel/core" "^7.27.4" + "@jest/types" "30.5.1" + "@jridgewell/trace-mapping" "^0.3.31" + babel-plugin-istanbul "^8.0.0" + chalk "^4.1.2" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.11" + jest-haste-map "30.5.1" + jest-regex-util "30.5.0" + jest-util "30.5.1" + pirates "^4.0.7" + slash "^3.0.0" + write-file-atomic "^5.0.1" + "@jest/types@30.2.0": version "30.2.0" resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.2.0.tgz#1c678a7924b8f59eafd4c77d56b6d0ba976d62b8" @@ -2870,6 +3121,19 @@ "@types/yargs" "^17.0.33" chalk "^4.1.2" +"@jest/types@30.5.1", "@jest/types@^30.0.1": + version "30.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.5.1.tgz#dc08c773401c18ea0d9ca670fb4a10a22c8a4fb5" + integrity sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g== + dependencies: + "@jest/pattern" "30.5.0" + "@jest/schemas" "30.5.0" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jridgewell/gen-mapping@^0.3.12": version "0.3.12" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz#2234ce26c62889f03db3d7fea43c1932ab3e927b" @@ -2942,6 +3206,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.31": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@keyv/bigmap@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@keyv/bigmap/-/bigmap-1.3.1.tgz#fc82fa83947e7ff68c6798d08907db842771ef2c" @@ -3323,56 +3595,111 @@ resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz#5f32e0dba356f4ac9a11068d2a5c134ca3ba6564" integrity sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A== +"@parcel/watcher-android-arm64@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz#99aaa3223d43807c9340af439cad7e9b6d26ada6" + integrity sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA== + "@parcel/watcher-darwin-arm64@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz#88d3e720b59b1eceffce98dac46d7c40e8be5e8e" integrity sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA== +"@parcel/watcher-darwin-arm64@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz#024496e586b4744f09ce532bbe89fe38ef02a64e" + integrity sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw== + "@parcel/watcher-darwin-x64@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz#bf05d76a78bc15974f15ec3671848698b0838063" integrity sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg== +"@parcel/watcher-darwin-x64@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz#a4621df1359a93d39a332d9bab5ff09016a0608f" + integrity sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw== + "@parcel/watcher-freebsd-x64@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz#8bc26e9848e7303ac82922a5ae1b1ef1bdb48a53" integrity sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng== +"@parcel/watcher-freebsd-x64@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz#7f565ed1a5b3a5e604e6a4799121518265d62a3d" + integrity sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ== + "@parcel/watcher-linux-arm-glibc@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz#1328fee1deb0c2d7865079ef53a2ba4cc2f8b40a" integrity sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ== +"@parcel/watcher-linux-arm-glibc@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz#ad7d3825e67b81999165da42593022045abc0889" + integrity sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg== + "@parcel/watcher-linux-arm-musl@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz#bad0f45cb3e2157746db8b9d22db6a125711f152" integrity sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg== +"@parcel/watcher-linux-arm-musl@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz#fe7d1cccb2c483215c090e938cf5cf404d2f9a8c" + integrity sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw== + "@parcel/watcher-linux-arm64-glibc@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz#b75913fbd501d9523c5f35d420957bf7d0204809" integrity sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA== +"@parcel/watcher-linux-arm64-glibc@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz#7e239dcb4646c4c79f006a7131a48238249530da" + integrity sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g== + "@parcel/watcher-linux-arm64-musl@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz#da5621a6a576070c8c0de60dea8b46dc9c3827d4" integrity sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA== +"@parcel/watcher-linux-arm64-musl@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz#c58b8d9c6d8d81594be00dd83aab741c1aaf7e0e" + integrity sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA== + "@parcel/watcher-linux-x64-glibc@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz#ce437accdc4b30f93a090b4a221fd95cd9b89639" integrity sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ== +"@parcel/watcher-linux-x64-glibc@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz#5184fa9a770478d86e56875f4ee163a0abdc8791" + integrity sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A== + "@parcel/watcher-linux-x64-musl@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz#02400c54b4a67efcc7e2327b249711920ac969e2" integrity sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg== +"@parcel/watcher-linux-x64-musl@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz#2d1c55aa7246cbc7670e2612058a8a542c9cf246" + integrity sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw== + "@parcel/watcher-win32-arm64@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz#caae3d3c7583ca0a7171e6bd142c34d20ea1691e" integrity sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q== +"@parcel/watcher-win32-arm64@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz#15e09432040fee9e2213aa9c10ed589012526def" + integrity sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ== + "@parcel/watcher-win32-ia32@2.5.6": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz#9ac922550896dfe47bfc5ae3be4f1bcaf8155d6d" @@ -3383,6 +3710,11 @@ resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz#73fdafba2e21c448f0e456bbe13178d8fe11739d" integrity sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw== +"@parcel/watcher-win32-x64@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz#9bee199a2a4accd557b451ac2c1c793f305ae012" + integrity sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A== + "@parcel/watcher@^2.4.1": version "2.5.6" resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.6.tgz#3f932828c894f06d0ad9cfefade1756ecc6ef1f1" @@ -3407,6 +3739,29 @@ "@parcel/watcher-win32-ia32" "2.5.6" "@parcel/watcher-win32-x64" "2.5.6" +"@parcel/watcher@^2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.6.0.tgz#99661f6220070b76a766aba6b7e313a087a1be4f" + integrity sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w== + dependencies: + detect-libc "^2.0.3" + is-glob "^4.0.3" + node-addon-api "^7.0.0" + picomatch "^4.0.4" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.6.0" + "@parcel/watcher-darwin-arm64" "2.6.0" + "@parcel/watcher-darwin-x64" "2.6.0" + "@parcel/watcher-freebsd-x64" "2.6.0" + "@parcel/watcher-linux-arm-glibc" "2.6.0" + "@parcel/watcher-linux-arm-musl" "2.6.0" + "@parcel/watcher-linux-arm64-glibc" "2.6.0" + "@parcel/watcher-linux-arm64-musl" "2.6.0" + "@parcel/watcher-linux-x64-glibc" "2.6.0" + "@parcel/watcher-linux-x64-musl" "2.6.0" + "@parcel/watcher-win32-arm64" "2.6.0" + "@parcel/watcher-win32-x64" "2.6.0" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -3429,6 +3784,23 @@ dependencies: "@types/node" "*" +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sinclair/typebox@^0.34.0": version "0.34.38" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.38.tgz#2365df7c23406a4d79413a766567bfbca708b49d" @@ -3614,6 +3986,119 @@ react-docgen "^8.0.2" react-docgen-typescript "^2.2.2" +"@storybook/test-runner@0.24.5": + version "0.24.5" + resolved "https://registry.yarnpkg.com/@storybook/test-runner/-/test-runner-0.24.5.tgz#92baf0e18bd8ba888d2cbed8d39a09a86a42dd55" + integrity sha512-U8ib6parBD+wA4z5ZW/JnsR3KkEXoP+sHP8WJdDZeh0GwfTlcWjhbeR6Ij/gxNFEA1bdpBa/P8qYTOs+LKIN1g== + dependencies: + "@babel/core" "^7.22.5" + "@babel/generator" "^7.22.5" + "@babel/template" "^7.22.5" + "@babel/types" "^7.22.5" + "@jest/types" "^30.0.1" + "@swc/core" "^1.5.22" + "@swc/jest" "^0.2.38" + expect-playwright "^0.8.0" + jest "^30.0.4" + jest-circus "^30.0.4" + jest-environment-node "^30.0.4" + jest-junit "^16.0.0" + jest-process-manager "^0.4.0" + jest-runner "^30.0.4" + jest-serializer-html "^7.1.0" + jest-watch-typeahead "^3.0.1" + nyc "^15.1.0" + playwright "^1.14.0" + playwright-core ">=1.2.0" + rimraf "^3.0.2" + uuid "^8.3.2" + +"@swc/core-darwin-arm64@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.16.2.tgz#b180d8647923c75e5aabbf4ea6e543d113aa3c79" + integrity sha512-i/j0HNbnn79qnTVPicvay92Nark8fW8NQqn1e2mGERjUXNpBV0+SwQxlRpk2zBhn6laJ8PDI6Kn1nHZhnz3LCA== + +"@swc/core-darwin-x64@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.16.2.tgz#ae8286ddd5a6f1b0f1c540e3338682fed7ceb2ab" + integrity sha512-HrwqHyEyHVXO3qTk8EkNK7/b6sOZSEoNh+pot6RdE5x0LbNqfo8LtJUvi3UTXr+5ja/o5HbJdW80eCXo+NjbiA== + +"@swc/core-linux-arm-gnueabihf@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.16.2.tgz#9879d6eb91d3663dfcae557cde866642b21b7590" + integrity sha512-MdXi83Z/gGp1LIrg+h7HKxiul/z/Bty/ZJSvYAFqDl9zteC1XLSAZdScquKtXPp50rdyXqritTDCqQBhwVfZKA== + +"@swc/core-linux-arm64-gnu@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.16.2.tgz#9684df59917e4b0de8f528f04cf7f6e7526ca7b1" + integrity sha512-/jcTmK6Ktz3owM3YtiKvjofV6p3VpHnYzTIrOGwDIOsDigRAAVuZ8east33wYO/7UTdKYFlyHNnJNT0WJqOA3Q== + +"@swc/core-linux-arm64-musl@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.16.2.tgz#36b59ed1a46795755e6d083e6693753c5ea1e7d0" + integrity sha512-4gFarKaFnlJTSlJYKmMhV4u+3YE4uYfiydpBoYjmgQhCf9lAieOq+WilZaK9vVSHeqLuQpTEiGULZqAdsRX5Dw== + +"@swc/core-linux-ppc64-gnu@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.16.2.tgz#b0f648e760031e4674e0d07eb4991dfb468d21ee" + integrity sha512-syqSLGd6KlZ1PciNzs6bIUlhOuFztZufebOHaERjc4N4SqNZxyqYd4I+jj/EfOYnpe0kNjccn9HJLN1p5dz3+w== + +"@swc/core-linux-s390x-gnu@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.16.2.tgz#7757e4999f4e236a8bb05bd04367574ce926d4cc" + integrity sha512-ZBBLK+ewGyXLzWeMS7wbKtWBdnif6etn7xvPY/iOfbdsjX/+bgkp1pQt2lWF2wlu2hXYZuhJ/tHZE/QR8/apzg== + +"@swc/core-linux-x64-gnu@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.16.2.tgz#a25271e8199c46e54f1042c9d98fbf38fdb4736c" + integrity sha512-LyHJgxCA4Tje0ysBMbEb0tt/ie8kgUKoFE3JAKFhpevmTmhYEoC0H9s47WuDsqiFckF1ITUguZIXJG6K5e0dvg== + +"@swc/core-linux-x64-musl@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.16.2.tgz#afc4e5f5e519e4e2ca75a560496fbc6b5164718d" + integrity sha512-PghXJlVM1cgtLfNUR1vxFo1z+PDRAe8cWAJlZZ7spmeiN7BospGXg/MHUg7oNSgwSX7Zo//YKv9P5yD9apsFJQ== + +"@swc/core-win32-arm64-msvc@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.16.2.tgz#98b936a9cf2faad87c92c979161f17f22c55b742" + integrity sha512-StTOSefYBxemvNYYUI3UmO1a8y+hSPjjfHogC2TEHL+Z1PlEBim/XtLas5rS04jAzT9RrNmbtX911SZ42H9jSQ== + +"@swc/core-win32-ia32-msvc@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.16.2.tgz#2e30f6a8f78b0c79268e6d39f0ae3b378919449c" + integrity sha512-fycER209DYIzsibpTMC+chND05OfOjgztWL9U8OE6/uUlsOUZH3eh98isBLEnOymYUhlJLEt5++W1+KL/FOh5Q== + +"@swc/core-win32-x64-msvc@1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.16.2.tgz#a25e080ed09770e68c1dc5a2a7e973a01dc40b2f" + integrity sha512-cSd1z6ivSrJPVr+moVwOHWjeKy6TpO4/Shwcv5KCrKYXCccxwh4pRy1C3fDioNx2PF1jPZWHKZjtXt+Be9VbaQ== + +"@swc/core@^1.5.22": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.16.2.tgz#6cc0c7f27938bea7208467a18ab518fa23b78367" + integrity sha512-95I4kiSMeveI/Mhi+tE4fiWcWLUMfzfKrk0jtr8LRMqHgOgq+xHS+zExkDqoO4b5OeeuXHMWVdD5MeP3X6sULw== + dependencies: + "@swc/counter" "^0.1.3" + "@swc/types" "^0.1.28" + optionalDependencies: + "@swc/core-darwin-arm64" "1.16.2" + "@swc/core-darwin-x64" "1.16.2" + "@swc/core-linux-arm-gnueabihf" "1.16.2" + "@swc/core-linux-arm64-gnu" "1.16.2" + "@swc/core-linux-arm64-musl" "1.16.2" + "@swc/core-linux-ppc64-gnu" "1.16.2" + "@swc/core-linux-s390x-gnu" "1.16.2" + "@swc/core-linux-x64-gnu" "1.16.2" + "@swc/core-linux-x64-musl" "1.16.2" + "@swc/core-win32-arm64-msvc" "1.16.2" + "@swc/core-win32-ia32-msvc" "1.16.2" + "@swc/core-win32-x64-msvc" "1.16.2" + +"@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + "@swc/helpers@^0.5.0": version "0.5.17" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971" @@ -3621,6 +4106,22 @@ dependencies: tslib "^2.8.0" +"@swc/jest@^0.2.38": + version "0.2.39" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.39.tgz#482bee0adb0726fab1487a4f902a278ec563a6b7" + integrity sha512-eyokjOwYd0Q8RnMHri+8/FS1HIrIUKK/sRrFp8c1dThUOfNeCWbLmBP1P5VsKdvmkd25JaH+OKYwEYiAYg9YAA== + dependencies: + "@jest/create-cache-key-function" "^30.0.0" + "@swc/counter" "^0.1.3" + jsonc-parser "^3.2.0" + +"@swc/types@^0.1.28": + version "0.1.28" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.28.tgz#e3cd892383fba3b8904c40518bbe1265a50753f2" + integrity sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw== + dependencies: + "@swc/counter" "^0.1.3" + "@testing-library/dom@^10.4.1": version "10.4.1" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.1.tgz#d444f8a889e9a46e9a3b4f3b88e0fcb3efb6cf95" @@ -3676,6 +4177,11 @@ resolved "https://registry.yarnpkg.com/@topoconfig/extends/-/extends-0.16.2.tgz#0741dbe5198a28f306a116498f7ded1089d0fadc" integrity sha512-sTF+qpWakr5jf1Hn/kkFSi833xPW15s/loMAiKSYSSVv4vDonxf6hwCGzMXjLq+7HZoaK6BgaV72wXr1eY7FcQ== +"@transloadit/prettier-bytes@^0.3.4": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@transloadit/prettier-bytes/-/prettier-bytes-0.3.5.tgz#0cca83975293e3f4990229914942c69714122ede" + integrity sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA== + "@tsconfig/node10@^1.0.7": version "1.0.11" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" @@ -4036,6 +4542,11 @@ resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.6.tgz#e6e60dad29c2c8c206c026e6dd8d6d1bdda850b8" integrity sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ== +"@types/retry@0.12.2": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" + integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== + "@types/semver@^7.7.1": version "7.7.1" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" @@ -4061,6 +4572,13 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== +"@types/wait-on@^5.2.0": + version "5.3.4" + resolved "https://registry.yarnpkg.com/@types/wait-on/-/wait-on-5.3.4.tgz#5ee270b3e073fb01073f9f044922c6893de8c4d2" + integrity sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -4179,76 +4697,166 @@ resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81" integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== +"@unrs/resolver-binding-android-arm-eabi@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz#98a9fee62c01f209747a4ab5855f1ced38a6d03a" + integrity sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w== + "@unrs/resolver-binding-android-arm64@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f" integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== +"@unrs/resolver-binding-android-arm64@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz#46b7e8a1393f907462324f1576e8883529acf066" + integrity sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ== + "@unrs/resolver-binding-darwin-arm64@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf" integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== +"@unrs/resolver-binding-darwin-arm64@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz#0ea07b00e2583ab004b853d4c02ec5f0745d490c" + integrity sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w== + "@unrs/resolver-binding-darwin-x64@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc" integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== +"@unrs/resolver-binding-darwin-x64@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz#a2a6901ed58449b91b4438e582f6890cba956049" + integrity sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA== + "@unrs/resolver-binding-freebsd-x64@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b" integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== +"@unrs/resolver-binding-freebsd-x64@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz#ebe6fe7f6706b7378ea4a48a024602e9c2f48f89" + integrity sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg== + "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a" integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== +"@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz#e6040fedaa240124419d35b25b69c5fa15ddb499" + integrity sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A== + "@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3" integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== +"@unrs/resolver-binding-linux-arm-musleabihf@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz#d217a8fb59f659c131539326c140e7b62e3e3c6a" + integrity sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g== + "@unrs/resolver-binding-linux-arm64-gnu@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d" integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== +"@unrs/resolver-binding-linux-arm64-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz#edab13c46a45783a7e01351e113825c04f352e24" + integrity sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg== + "@unrs/resolver-binding-linux-arm64-musl@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0" integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== +"@unrs/resolver-binding-linux-arm64-musl@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz#e5e195db1130f7d3b6aa2fd67b3c9fe1ea4859a0" + integrity sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA== + +"@unrs/resolver-binding-linux-loong64-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz#f01d22e091bae13016f4636698d9dcbbda775c3e" + integrity sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q== + +"@unrs/resolver-binding-linux-loong64-musl@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz#7d23efcb98adf076bfbcecc27b4212c36aa6697d" + integrity sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew== + "@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44" integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== +"@unrs/resolver-binding-linux-ppc64-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz#1f35f1eaa322f33cf2d96dac27f0626a93ffe2f6" + integrity sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg== + "@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9" integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== +"@unrs/resolver-binding-linux-riscv64-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz#674faa696f5ce96f214873946a1e2d6ca96723dd" + integrity sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A== + "@unrs/resolver-binding-linux-riscv64-musl@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165" integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== +"@unrs/resolver-binding-linux-riscv64-musl@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz#37835fdd0b472ecdcffccd4288f19018454b138c" + integrity sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w== + "@unrs/resolver-binding-linux-s390x-gnu@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94" integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== +"@unrs/resolver-binding-linux-s390x-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz#b6edf13db4bb0accdcd1ad482a4eea0301de9224" + integrity sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw== + "@unrs/resolver-binding-linux-x64-gnu@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935" integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== +"@unrs/resolver-binding-linux-x64-gnu@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz#daddad00bf65a405202284da1eb1db8eb83b218f" + integrity sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ== + "@unrs/resolver-binding-linux-x64-musl@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6" integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== +"@unrs/resolver-binding-linux-x64-musl@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz#dfdff1e0c2bad25420b41c76a746011c3983b9bb" + integrity sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A== + +"@unrs/resolver-binding-openharmony-arm64@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz#ce07c4f5e7b42f7bfce45e7629b8659063aefefe" + integrity sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ== + "@unrs/resolver-binding-wasm32-wasi@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d" @@ -4256,21 +4864,108 @@ dependencies: "@napi-rs/wasm-runtime" "^0.2.11" +"@unrs/resolver-binding-wasm32-wasi@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz#82514f0506cfaf65f17fe16095f92d450e487183" + integrity sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A== + dependencies: + "@emnapi/core" "1.10.0" + "@emnapi/runtime" "1.10.0" + "@napi-rs/wasm-runtime" "^1.1.4" + "@unrs/resolver-binding-win32-arm64-msvc@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35" integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== +"@unrs/resolver-binding-win32-arm64-msvc@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz#521427dd59a8f4740ddd1dc7c3bc6af1aa1d260d" + integrity sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g== + "@unrs/resolver-binding-win32-ia32-msvc@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6" integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== +"@unrs/resolver-binding-win32-ia32-msvc@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz#05b63286ff2da37e0ce3083b8390884385efff62" + integrity sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g== + "@unrs/resolver-binding-win32-x64-msvc@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== +"@unrs/resolver-binding-win32-x64-msvc@1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz#72da0da48d72b1e87831b9c0308931d3f4669027" + integrity sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA== + +"@uppy/companion-client@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@uppy/companion-client/-/companion-client-5.1.1.tgz#5bfeda3312ba2b70e94d4fd73a31d0f7757884c0" + integrity sha512-DzrOWTbIZHvtgAFXBMYHk2wD27NjpBSVhY2tEiEIUhPd2CxbFRZjHM/N3HOt3VwZEAP471QWFLlJRWPcIY3A2Q== + dependencies: + "@uppy/utils" "^7.1.1" + namespace-emitter "^2.0.1" + p-retry "^6.1.0" + +"@uppy/components@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@uppy/components/-/components-1.2.0.tgz#20d815ca7920f8d875cb40b942a7e2878fc19ea8" + integrity sha512-rtIr+77Rw/q5Vw++xazF1dCg2d4A4zT9CV+ZyN8Rsx8xiIr2CxCR4TaHHBy+WeC0b7Mk6yNuJ0wUa34tFJ6pKg== + dependencies: + clsx "^2.1.1" + dequal "^2.0.3" + preact "^10.26.10" + pretty-bytes "^6.1.1" + +"@uppy/core@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@uppy/core/-/core-5.2.0.tgz#baeb0f9f1ef5bec1882eac912f5a41094af69f84" + integrity sha512-uvfNyz4cnaplt7LYJmEZHuqOuav0tKp4a9WKJIaH6iIj7XiqYvS2J5SEByexAlUFlzefOAyjzj4Ja2dd/8aMrw== + dependencies: + "@transloadit/prettier-bytes" "^0.3.4" + "@uppy/store-default" "^5.0.0" + "@uppy/utils" "^7.1.4" + lodash "^4.17.21" + mime-match "^1.0.2" + namespace-emitter "^2.0.1" + nanoid "^5.0.9" + preact "^10.5.13" + +"@uppy/react@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@uppy/react/-/react-5.2.0.tgz#ddee35205344efb80781bb86d13103a277ef79c0" + integrity sha512-6lzPutg2XGavs7P6ALmqOBPitd/Jqi3r1jCJQD5nx8xtNlBRwvlBR6hrZgo8XOI9cR+OaNDrJ0vEFxXDWb04Ag== + dependencies: + "@uppy/components" "^1.2.0" + preact "^10.26.10" + use-sync-external-store "^1.3.0" + +"@uppy/store-default@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@uppy/store-default/-/store-default-5.0.0.tgz#2466162857a999b8c99051426e5f412e2f34cb26" + integrity sha512-hQtCSQ1yGiaval/wVYUWquYGDJ+bpQ7e4FhUUAsRQz1x1K+o7NBtjfp63O9I4Ks1WRoKunpkarZ+as09l02cPw== + +"@uppy/utils@^7.1.1", "@uppy/utils@^7.1.4", "@uppy/utils@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@uppy/utils/-/utils-7.2.0.tgz#10212e92d8b57ff9d854276d056e98fdcd4643c9" + integrity sha512-6lC246qszMv6bTyl/+QyHwrudgeguWkA94ME1wHn+a6uRAvmtAEaUManIfGqTJfoKvWAiCJqdJPl5xRJjhAloQ== + dependencies: + lodash "^4.17.23" + preact "^10.26.10" + +"@uppy/xhr-upload@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@uppy/xhr-upload/-/xhr-upload-5.2.0.tgz#f8a6b98bad3ec7678433dc6bfa0b4b5c6855652c" + integrity sha512-3LV/X5Of6BINnKplP+CwUJ0a4/7cRFfzxwGyXnW+uCrNQHoo09dttcz3begWHejGvzenQHuUnMO3Fxyc71Pryg== + dependencies: + "@uppy/companion-client" "^5.1.1" + "@uppy/utils" "^7.2.0" + "@vitest/expect@3.2.4": version "3.2.4" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433" @@ -4494,11 +5189,26 @@ acorn@^8.15.0, acorn@^8.16.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + agent-base@^7.1.0, agent-base@^7.1.2: version "7.1.4" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -4582,6 +5292,13 @@ ansi-regex@^6.2.2: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -4612,6 +5329,18 @@ anymatch@^3.1.3: normalize-path "^3.0.0" picomatch "^2.0.4" +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== + dependencies: + default-require-extensions "^3.0.0" + +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -4755,6 +5484,11 @@ async-function@^1.0.0: resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -4767,6 +5501,16 @@ axe-core@^4.2.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.11.4.tgz#5b535e381ff1e61ffdd615e5483d16186d3b46a5" integrity sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA== +axios@^1.6.1: + version "1.20.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.20.0.tgz#515513445aa60e71d04b6521ca6210829ccb4786" + integrity sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg== + dependencies: + follow-redirects "^1.16.0" + form-data "^4.0.6" + https-proxy-agent "^5.0.1" + proxy-from-env "^2.1.0" + babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" @@ -4785,6 +5529,19 @@ babel-jest@30.4.1, babel-jest@^30.4.1: graceful-fs "^4.2.11" slash "^3.0.0" +babel-jest@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-30.5.1.tgz#b20da241e3d525e17a1cda61eadbc2e13230baeb" + integrity sha512-ge1xUVZS91ml09YRMgRGgeKJ4YJpcOiuwteAxFBYLugQyp7cRw+hHej6Ho0vPjvLrjq60bb7JPHH9LAMA1/krA== + dependencies: + "@jest/transform" "30.5.1" + "@types/babel__core" "^7.20.5" + babel-plugin-istanbul "^8.0.0" + babel-preset-jest "30.5.0" + chalk "^4.1.2" + graceful-fs "^4.2.11" + slash "^3.0.0" + babel-loader@^10.0.0: version "10.1.1" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-10.1.1.tgz#ce9748e85b7071eb88006e3cfa9e6cf14eeb97c5" @@ -4803,6 +5560,17 @@ babel-plugin-istanbul@^7.0.1: istanbul-lib-instrument "^6.0.2" test-exclude "^6.0.0" +babel-plugin-istanbul@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz#8762f542153a52b77e626dd2c033b467de2f2fa2" + integrity sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-instrument "^6.0.2" + test-exclude "^7.0.1" + babel-plugin-jest-hoist@30.4.0: version "30.4.0" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz#f7d6a6d8f435808b56b45a81dc4b61a39e36794a" @@ -4810,6 +5578,13 @@ babel-plugin-jest-hoist@30.4.0: dependencies: "@types/babel__core" "^7.20.5" +babel-plugin-jest-hoist@30.5.0: + version "30.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.5.0.tgz#425bf7ee24cffe47bfdcfeeca2885b87b1cf6644" + integrity sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A== + dependencies: + "@types/babel__core" "^7.20.5" + babel-plugin-polyfill-corejs2@^0.4.15: version "0.4.17" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz#198f970f1c99a856b466d1187e88ce30bd199d91" @@ -4863,6 +5638,14 @@ babel-preset-jest@30.4.0: babel-plugin-jest-hoist "30.4.0" babel-preset-current-node-syntax "^1.2.0" +babel-preset-jest@30.5.0: + version "30.5.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-30.5.0.tgz#33162eee375c0066f2b2059cd97a7840ef84c259" + integrity sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w== + dependencies: + babel-plugin-jest-hoist "30.5.0" + babel-preset-current-node-syntax "^1.2.0" + bail@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" @@ -4962,6 +5745,16 @@ cacheable@^2.5.0: keyv "^5.6.0" qified "^0.10.1" +caching-transform@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== + dependencies: + hasha "^5.0.0" + make-dir "^3.0.0" + package-hash "^4.0.0" + write-file-atomic "^3.0.0" + call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" @@ -5019,7 +5812,7 @@ camel-case@^4.1.2: pascal-case "^3.1.2" tslib "^2.0.3" -camelcase@^5.3.1: +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== @@ -5069,7 +5862,16 @@ chai@^5.2.0: loupe "^3.1.0" pathval "^2.0.0" -chalk@^4.0.0, chalk@^4.1.2: +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2, chalk@~4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -5077,6 +5879,11 @@ chalk@^4.0.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.2.0: + version "5.6.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" + integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== + chalk@^5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" @@ -5200,6 +6007,11 @@ cjs-module-lexer@^2.1.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz#586e87d4341cb2661850ece5190232ccdebcff8b" integrity sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA== +cjs-module-lexer@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.2.1.tgz#ab35b03c56ade05fe170c70e67ae89f60666847c" + integrity sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q== + classcat@^5.0.3: version "5.0.5" resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.5.tgz#8c209f359a93ac302404a10161b501eba9c09c77" @@ -5217,6 +6029,11 @@ clean-css@^5.2.2: dependencies: source-map "~0.6.0" +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + cli-cursor@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38" @@ -5240,6 +6057,15 @@ cli@~1.0.0: exit "0.1.2" glob "^7.1.1" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -5272,6 +6098,11 @@ clsx@^1.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -5295,6 +6126,13 @@ collect-v8-coverage@^1.0.2: resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -5309,6 +6147,11 @@ color-convert@^3.1.3: dependencies: color-name "^2.0.0" +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" @@ -5360,11 +6203,23 @@ colorette@^2.0.10, colorette@^2.0.20: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + comma-separated-tokens@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + commander@^13.1.0: version "13.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46" @@ -5380,6 +6235,11 @@ commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" + integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== + commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -5429,6 +6289,11 @@ constant-case@^3.0.4: tslib "^2.0.3" upper-case "^2.0.2" +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" @@ -5505,7 +6370,7 @@ crelt@^1.0.5: resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== -cross-spawn@^7.0.3, cross-spawn@^7.0.6: +cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -5608,6 +6473,14 @@ csstype@^3.2.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== +cwd@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/cwd/-/cwd-0.10.0.tgz#172400694057c22a13b0cf16162c7e4b7a7fe567" + integrity sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA== + dependencies: + find-pkg "^0.1.2" + fs-exists-sync "^0.1.0" + "d3-color@1 - 3": version "3.1.0" resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" @@ -5731,6 +6604,11 @@ debug@^4.4.3: dependencies: ms "^2.1.3" +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + decimal.js@^10.5.0: version "10.6.0" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" @@ -5781,6 +6659,13 @@ default-browser@^5.2.1: bundle-name "^4.1.0" default-browser-id "^5.0.0" +default-require-extensions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.1.tgz#bfae00feeaeada68c2ae256c62540f60b80625bd" + integrity sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw== + dependencies: + strip-bom "^4.0.0" + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -5804,6 +6689,11 @@ define-properties@^1.1.3, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depseek@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/depseek/-/depseek-0.4.1.tgz#f495cff2742cc119753164689595f08c2a2c484e" @@ -5841,6 +6731,13 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diffable-html@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/diffable-html/-/diffable-html-4.1.0.tgz#e7a2d1de187c4e23a59751b4e4c17483a058c696" + integrity sha512-++kyNek+YBLH8cLXS+iTj/Hiy2s5qkRJEJ8kgu/WHbFrVY2vz9xPFUT+fii2zGF0m1CaojDlQJjkfrCt7YWM1g== + dependencies: + htmlparser2 "^3.9.2" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -5897,7 +6794,7 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" -domelementtype@1: +domelementtype@1, domelementtype@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== @@ -5914,6 +6811,13 @@ domhandler@2.3: dependencies: domelementtype "1" +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" @@ -5929,6 +6833,14 @@ domutils@1.5: dom-serializer "0" domelementtype "1" +domutils@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" @@ -6038,6 +6950,11 @@ entities@1.0: resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" integrity sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ== +entities@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -6207,6 +7124,11 @@ es-toolkit@^1.43.0: resolved "https://registry.yarnpkg.com/es-toolkit/-/es-toolkit-1.46.1.tgz#38ca27191a98a867fc544b81cf1477a68947fb06" integrity sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ== +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + "esbuild@^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0": version "0.28.2" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.28.2.tgz#0f43bd1bad955b72d24e2261e3abe5957ccf0816" @@ -6244,6 +7166,11 @@ escalade@^3.1.1, escalade@^3.2.0: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + escape-string-regexp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" @@ -6475,11 +7402,18 @@ exit-x@^0.2.2: resolved "https://registry.yarnpkg.com/exit-x/-/exit-x-0.2.2.tgz#1f9052de3b8d99a696b10dad5bced9bdd5c3aa64" integrity sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== -exit@0.1.2, exit@0.1.x: +exit@0.1.2, exit@0.1.x, exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== +expand-tilde@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" + integrity sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q== + dependencies: + os-homedir "^1.0.1" + expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" @@ -6487,6 +7421,11 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" +expect-playwright@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/expect-playwright/-/expect-playwright-0.8.0.tgz#6d4ebe0bdbdd3c1693d880d97153b96a129ae4e8" + integrity sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg== + expect@30.4.1: version "30.4.1" resolved "https://registry.yarnpkg.com/expect/-/expect-30.4.1.tgz#897e0390a0b6c333dbcf3a24dee3ad49553577e0" @@ -6499,6 +7438,18 @@ expect@30.4.1: jest-mock "30.4.1" jest-util "30.4.1" +expect@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.5.1.tgz#0fdbaf8a9be4c660f394d745fc17174d08b4fba6" + integrity sha512-m8YrYgvKe9+9gEnWEuKz+qCGfHqkrff7PPfyDnOFkjsfYRKqiYyOxDFMFieCGAuhtk/VNm63tvNgKZVzVy+Hvg== + dependencies: + "@jest/expect-utils" "30.5.1" + "@jest/get-type" "30.5.0" + jest-matcher-utils "30.5.1" + jest-message-util "30.5.1" + jest-mock "30.5.1" + jest-util "30.5.1" + expect@^30.0.0: version "30.2.0" resolved "https://registry.yarnpkg.com/expect/-/expect-30.2.0.tgz#d4013bed267013c14bc1199cec8aa57cee9b5869" @@ -6613,7 +7564,7 @@ find-cache-dir@^2.0.0: make-dir "^2.0.0" pkg-dir "^3.0.0" -find-cache-dir@^3.3.1: +find-cache-dir@^3.2.0, find-cache-dir@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== @@ -6622,6 +7573,30 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" +find-file-up@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/find-file-up/-/find-file-up-0.1.3.tgz#cf68091bcf9f300a40da411b37da5cce5a2fbea0" + integrity sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A== + dependencies: + fs-exists-sync "^0.1.0" + resolve-dir "^0.1.0" + +find-pkg@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/find-pkg/-/find-pkg-0.1.2.tgz#1bdc22c06e36365532e2a248046854b9788da557" + integrity sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw== + dependencies: + find-file-up "^0.1.2" + +find-process@^1.4.4: + version "1.4.11" + resolved "https://registry.yarnpkg.com/find-process/-/find-process-1.4.11.tgz#f7246251d396b35b9ae41fff7b87137673567fcc" + integrity sha512-mAOh9gGk9WZ4ip5UjV0o6Vb4SrfnAmtsFNzkMRH9HQiFXVQnDyQFrSHTK5UoG6E+KV+s+cIznbtwpfN41l2nFA== + dependencies: + chalk "~4.1.2" + commander "^12.1.0" + loglevel "^1.9.2" + find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -6703,6 +7678,11 @@ flow-parser@0.*: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.269.1.tgz#92067f8100d89a84433c656eb59c5b92e4036eb9" integrity sha512-2Yr0kqvT7RwaGL192nT78O5AWJeECQjl0NEzBkMsx8OJt63BvNl5yvSIbE4qZ1VDSjEkhbUgaWYdwX354bVNjw== +follow-redirects@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" + integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== + for-each@^0.3.3, for-each@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" @@ -6710,6 +7690,14 @@ for-each@^0.3.3, for-each@^0.3.5: dependencies: is-callable "^1.2.7" +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" + foreground-child@^3.1.0: version "3.3.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" @@ -6736,6 +7724,17 @@ fork-ts-checker-webpack-plugin@^9.1.0: semver "^7.3.5" tapable "^2.2.1" +form-data@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.6.tgz#28e864e1b786dbebb68db1f452f9635278665827" + integrity sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.4" + mime-types "^2.1.35" + format@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" @@ -6750,6 +7749,16 @@ framer-motion@^13.1.1: motion-utils "^13.0.0" tslib "^2.4.0" +fromentries@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + +fs-exists-sync@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" + integrity sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg== + fs-extra@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -6815,7 +7824,7 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.5: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -6897,7 +7906,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^10.5.0: +glob@^10.4.1, glob@^10.5.0: version "10.5.0" resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== @@ -6909,7 +7918,7 @@ glob@^10.5.0: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^13.0.3: +glob@^13.0.3, glob@^13.0.6: version "13.0.6" resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d" integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw== @@ -6930,6 +7939,14 @@ glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +global-modules@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" + integrity sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA== + dependencies: + global-prefix "^0.1.4" + is-windows "^0.2.0" + global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -6946,6 +7963,16 @@ global-modules@^2.0.0: dependencies: global-prefix "^3.0.0" +global-prefix@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" + integrity sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw== + dependencies: + homedir-polyfill "^1.0.0" + ini "^1.3.4" + is-windows "^0.2.0" + which "^1.2.12" + global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" @@ -7007,7 +8034,7 @@ gopd@^1.0.1, gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -7022,6 +8049,11 @@ has-bigints@^1.0.2: resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -7058,6 +8090,14 @@ has-tostringtag@^1.0.2: dependencies: has-symbols "^1.0.3" +hasha@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + hashery@^1.4.0, hashery@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/hashery/-/hashery-1.5.1.tgz#4ba82ad54911ac617467870845d57a9fe508a400" @@ -7072,6 +8112,13 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hasown@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" + integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== + dependencies: + function-bind "^1.1.2" + hast-util-definition-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hast-util-definition-list/-/hast-util-definition-list-2.1.0.tgz#9bdf16835c91f47d1c8ff53d732c138e128b9506" @@ -7335,7 +8382,7 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: dependencies: react-is "^16.7.0" -homedir-polyfill@^1.0.1: +homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== @@ -7419,6 +8466,18 @@ htmlparser2@3.8.x: entities "1.0" readable-stream "1.1" +htmlparser2@^3.9.2: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + htmlparser2@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" @@ -7437,6 +8496,14 @@ http-proxy-agent@^7.0.2: agent-base "^7.1.0" debug "^4.3.4" +https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + https-proxy-agent@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" @@ -7549,7 +8616,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3, inherits@~2.0.1: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -7789,6 +8856,11 @@ is-nan@^1.3.2: call-bind "^1.0.0" define-properties "^1.1.3" +is-network-error@^1.0.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.3.2.tgz#9460bc30f8419a4bca77114f4de88a3ee5e0c519" + integrity sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA== + is-number-object@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" @@ -7885,6 +8957,11 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15, is-typed dependencies: which-typed-array "^1.1.16" +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + is-upper-case@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-1.1.2.tgz#8d0b1fa7e7933a1e58483600ec7d9661cbaf756f" @@ -7912,7 +8989,12 @@ is-weakset@^2.0.3: call-bound "^1.0.3" get-intrinsic "^1.2.6" -is-windows@^1.0.1: +is-windows@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" + integrity sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q== + +is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== @@ -7949,6 +9031,23 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" @@ -7960,6 +9059,18 @@ istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.2: istanbul-lib-coverage "^3.2.0" semver "^7.5.4" +istanbul-lib-processinfo@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz#366d454cd0dcb7eb6e0e419378e60072c8626169" + integrity sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg== + dependencies: + archy "^1.0.0" + cross-spawn "^7.0.3" + istanbul-lib-coverage "^3.2.0" + p-map "^3.0.0" + rimraf "^3.0.0" + uuid "^8.3.2" + istanbul-lib-report@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" @@ -7969,6 +9080,15 @@ istanbul-lib-report@^3.0.0: make-dir "^4.0.0" supports-color "^7.1.0" +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + istanbul-lib-source-maps@^5.0.0: version "5.0.6" resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441" @@ -7978,6 +9098,14 @@ istanbul-lib-source-maps@^5.0.0: debug "^4.1.1" istanbul-lib-coverage "^3.0.0" +istanbul-reports@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + istanbul-reports@^3.1.3: version "3.1.7" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" @@ -8016,6 +9144,15 @@ jest-changed-files@30.4.1: jest-util "30.4.1" p-limit "^3.1.0" +jest-changed-files@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-30.5.1.tgz#0f91cc77e6f834fb2e85bd277d9b313e555acd43" + integrity sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg== + dependencies: + execa "^5.1.1" + jest-util "30.5.1" + p-limit "^3.1.0" + jest-circus@30.4.2: version "30.4.2" resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-30.4.2.tgz#9a5b9b9c57bf51871f112ccf7a673d486c28f8e7" @@ -8042,6 +9179,32 @@ jest-circus@30.4.2: slash "^3.0.0" stack-utils "^2.0.6" +jest-circus@30.5.1, jest-circus@^30.0.4: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-30.5.1.tgz#323ff2a51a656958acbb2fe983702261eee71981" + integrity sha512-NgliezXQ6yznqR4W5Gqw++0cZSbBZlF0NknNIAE5VmSJKWOtmWJmWnBq3TSkUOVBTPrT7FGGhVdA8DLWnxo2Sw== + dependencies: + "@jest/environment" "30.5.1" + "@jest/expect" "30.5.1" + "@jest/test-result" "30.5.1" + "@jest/types" "30.5.1" + "@types/node" "*" + chalk "^4.1.2" + co "^4.6.0" + dedent "^1.6.0" + is-generator-fn "^2.1.0" + jest-each "30.5.1" + jest-matcher-utils "30.5.1" + jest-message-util "30.5.1" + jest-runtime "30.5.1" + jest-snapshot "30.5.1" + jest-util "30.5.1" + p-limit "^3.1.0" + pretty-format "30.5.1" + pure-rand "^7.0.0" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-cli@30.4.2: version "30.4.2" resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-30.4.2.tgz#e353ef54035c5ac97f200807c97b3d857f52bddc" @@ -8058,6 +9221,22 @@ jest-cli@30.4.2: jest-validate "30.4.1" yargs "^17.7.2" +jest-cli@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-30.5.1.tgz#5462c51d01b41e8f339faa88fd9139eeb0a9b34d" + integrity sha512-uwNYepWgaNBCplm42fCIUZTf/tIEHIy5AYvx7eL1BrwIgyjPt+2poouR23UO2yopIP+kV8oZFHfv+l4pg/8prQ== + dependencies: + "@jest/core" "30.5.1" + "@jest/test-result" "30.5.1" + "@jest/types" "30.5.1" + chalk "^4.1.2" + exit-x "^0.2.2" + import-local "^3.2.0" + jest-config "30.5.1" + jest-util "30.5.1" + jest-validate "30.5.1" + yargs "^17.7.2" + jest-config@30.4.2: version "30.4.2" resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-30.4.2.tgz#78f589b5410d2805518b8bdce517217fb96b5e61" @@ -8087,6 +9266,35 @@ jest-config@30.4.2: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-config@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-30.5.1.tgz#fff038a74c4750c478efe3a66bde8c80051edb48" + integrity sha512-L8PKM2X/ngG8PxfLMqglKGZjylPgw84bVPUlMY9W/o76TnLqPWrmQgsaT0HrheGdRtpGE5FbmREA0Kvb+Qx5gQ== + dependencies: + "@babel/core" "^7.27.4" + "@jest/get-type" "30.5.0" + "@jest/pattern" "30.5.0" + "@jest/test-sequencer" "30.5.1" + "@jest/types" "30.5.1" + babel-jest "30.5.1" + chalk "^4.1.2" + ci-info "^4.2.0" + deepmerge "^4.3.1" + glob "^13.0.6" + graceful-fs "^4.2.11" + jest-circus "30.5.1" + jest-docblock "30.5.0" + jest-environment-node "30.5.1" + jest-regex-util "30.5.0" + jest-resolve "30.5.1" + jest-runner "30.5.1" + jest-util "30.5.1" + jest-validate "30.5.1" + parse-json "^5.2.0" + pretty-format "30.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" + jest-diff@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.2.0.tgz#e3ec3a6ea5c5747f605c9e874f83d756cba36825" @@ -8107,6 +9315,16 @@ jest-diff@30.4.1: chalk "^4.1.2" pretty-format "30.4.1" +jest-diff@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.5.1.tgz#e03474eca7e5dc42924b15c72069b3203253768d" + integrity sha512-e3cNNMpv8Kh20MjjphTXs+3Vz7DQyLM1nft7KJhnh46atFhjVJRa+0Hq0beywuwsACtMQUBihQkFl8zxb7gt1Q== + dependencies: + "@jest/diff-sequences" "30.5.0" + "@jest/get-type" "30.5.0" + chalk "^4.1.2" + pretty-format "30.5.1" + jest-docblock@30.4.0: version "30.4.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-30.4.0.tgz#3ab779a027d1495ae21550accd4266bbe99af7a3" @@ -8114,6 +9332,13 @@ jest-docblock@30.4.0: dependencies: detect-newline "^3.1.0" +jest-docblock@30.5.0: + version "30.5.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-30.5.0.tgz#7cf9d08ba714fde0b68c9cb4bfc0be867a7dd11d" + integrity sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw== + dependencies: + detect-newline "^3.1.0" + jest-each@30.4.1: version "30.4.1" resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-30.4.1.tgz#b69e66da8e2b578c6140d357f6574044c2a40537" @@ -8125,6 +9350,17 @@ jest-each@30.4.1: jest-util "30.4.1" pretty-format "30.4.1" +jest-each@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-30.5.1.tgz#6647c948db58351d4081851eae329a410288bacd" + integrity sha512-S1af0TU4v1EZ/AUlkFs/sxf/5KGsbAT9kRgdyoMX/x72y7C8ZEgETE5o1TPnbKRnrbprc5FR/moYLfdRmqEjsQ== + dependencies: + "@jest/get-type" "30.5.0" + "@jest/types" "30.5.1" + chalk "^4.1.2" + jest-util "30.5.1" + pretty-format "30.5.1" + jest-environment-jsdom@^30.4.1: version "30.4.1" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-30.4.1.tgz#928c81b3ea630b409fc6483cd16553b90b220bfc" @@ -8147,6 +9383,19 @@ jest-environment-node@30.4.1: jest-util "30.4.1" jest-validate "30.4.1" +jest-environment-node@30.5.1, jest-environment-node@^30.0.4: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.5.1.tgz#c4581f0a0b656d2b17be809066c051cee99ea8ed" + integrity sha512-LrPj3sPMjsQoOB3jrb8p/sa+XkSFNKo60TTsyc+EB2kxQJHgbwElpXnx1yX25fdFn5958FIObRtcLSHyV8VIAw== + dependencies: + "@jest/environment" "30.5.1" + "@jest/fake-timers" "30.5.1" + "@jest/types" "30.5.1" + "@types/node" "*" + jest-mock "30.5.1" + jest-util "30.5.1" + jest-validate "30.5.1" + jest-fixed-jsdom@^0.0.11: version "0.0.11" resolved "https://registry.yarnpkg.com/jest-fixed-jsdom/-/jest-fixed-jsdom-0.0.11.tgz#67b5d5c4e9821bfb1e09a43139bfb0b9f4ec4f18" @@ -8170,6 +9419,33 @@ jest-haste-map@30.4.1: optionalDependencies: fsevents "^2.3.3" +jest-haste-map@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.5.1.tgz#a1dbba63564492f53783d754623cdd41bb0328b0" + integrity sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ== + dependencies: + "@jest/types" "30.5.1" + "@parcel/watcher" "^2.6.0" + "@types/node" "*" + anymatch "^3.1.3" + fb-watchman "^2.0.2" + fdir "^6.5.0" + graceful-fs "^4.2.11" + jest-regex-util "30.5.0" + jest-util "30.5.1" + jest-worker "30.5.1" + picomatch "^4.0.3" + +jest-junit@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-16.0.0.tgz#d838e8c561cf9fdd7eb54f63020777eee4136785" + integrity sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ== + dependencies: + mkdirp "^1.0.4" + strip-ansi "^6.0.1" + uuid "^8.3.2" + xml "^1.0.1" + jest-leak-detector@30.4.1: version "30.4.1" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz#96077059a68e5871fc8f53aa90647a6a33f916cd" @@ -8178,6 +9454,14 @@ jest-leak-detector@30.4.1: "@jest/get-type" "30.1.0" pretty-format "30.4.1" +jest-leak-detector@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-30.5.1.tgz#bca4b6b08d5e3a0b46560e268d28c89b15e7b72e" + integrity sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ== + dependencies: + "@jest/get-type" "30.5.0" + pretty-format "30.5.1" + jest-matcher-utils@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz#69a0d4c271066559ec8b0d8174829adc3f23a783" @@ -8198,6 +9482,16 @@ jest-matcher-utils@30.4.1: jest-diff "30.4.1" pretty-format "30.4.1" +jest-matcher-utils@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.5.1.tgz#93e64fa4362c44d68cdc7a5590a2ccf6972083f4" + integrity sha512-aroZVqwOz/wC2y6pC+obgFWKV9viaQWQTTSB6W5H55+egtUczIfXR/rxExTv92xD/ADYwpqaYfVWx1aqwKg7FA== + dependencies: + "@jest/get-type" "30.5.0" + chalk "^4.1.2" + jest-diff "30.5.1" + pretty-format "30.5.1" + jest-message-util@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.2.0.tgz#fc97bf90d11f118b31e6131e2b67fc4f39f92152" @@ -8229,6 +9523,22 @@ jest-message-util@30.4.1: slash "^3.0.0" stack-utils "^2.0.6" +jest-message-util@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.5.1.tgz#e8d04d7b6d123f5dbfb1497432cd9c2b3d510f90" + integrity sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.5.1" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + jest-util "30.5.1" + picomatch "^4.0.3" + pretty-format "30.5.1" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-mock@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.2.0.tgz#69f991614eeb4060189459d3584f710845bff45e" @@ -8247,11 +9557,37 @@ jest-mock@30.4.1: "@types/node" "*" jest-util "30.4.1" +jest-mock@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.5.1.tgz#a52a7286d4bbf049bf9dabaa94616a9cc28c4b84" + integrity sha512-9fVjc3leUpGID2/by/LU4Dvdcp7PFh9LlxS3QRWK3ABm+KtvEVsG/AEGeLY3gKOZsjkBxyfwGltoAVlW7dygHg== + dependencies: + "@jest/expect-utils" "30.5.1" + "@jest/types" "30.5.1" + "@types/node" "*" + jest-util "30.5.1" + jest-pnp-resolver@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== +jest-process-manager@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/jest-process-manager/-/jest-process-manager-0.4.0.tgz#fb05c8e09ad400fd038436004815653bb98f4e8b" + integrity sha512-80Y6snDyb0p8GG83pDxGI/kQzwVTkCxc7ep5FPe/F6JYdvRDhwr6RzRmPSP7SEwuLhxo80lBS/NqOdUIbHIfhw== + dependencies: + "@types/wait-on" "^5.2.0" + chalk "^4.1.0" + cwd "^0.10.0" + exit "^0.1.2" + find-process "^1.4.4" + prompts "^2.4.1" + signal-exit "^3.0.3" + spawnd "^5.0.0" + tree-kill "^1.2.2" + wait-on "^7.0.0" + jest-regex-util@30.0.1: version "30.0.1" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" @@ -8262,6 +9598,11 @@ jest-regex-util@30.4.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.4.0.tgz#f75ccc43857633df2563a03588b5cb45c7c2941b" integrity sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg== +jest-regex-util@30.5.0, jest-regex-util@^30.0.0: + version "30.5.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.5.0.tgz#aedb1932d361d4e701ecacda6ac83acf37299505" + integrity sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A== + jest-resolve-dependencies@30.4.2: version "30.4.2" resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz#152f8a4cb2dd351cedeb5ada53c89f9683a3ad92" @@ -8270,6 +9611,14 @@ jest-resolve-dependencies@30.4.2: jest-regex-util "30.4.0" jest-snapshot "30.4.1" +jest-resolve-dependencies@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.1.tgz#2ac3052a773e70277607609ba27351025cb4bb11" + integrity sha512-JKpXGONDcTaunrNVn8KCl6qAnwl06jIkvv70lhq0Ze47lsPx9sH5HoeEUiXX6iFGnliHN/qpIbQ0l38wrVmXaw== + dependencies: + jest-regex-util "30.5.0" + jest-snapshot "30.5.1" + jest-resolve@30.4.1: version "30.4.1" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-30.4.1.tgz#b9e432892dc0e2a470eb4826ef5f120a50b3205e" @@ -8284,6 +9633,19 @@ jest-resolve@30.4.1: slash "^3.0.0" unrs-resolver "^1.7.11" +jest-resolve@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-30.5.1.tgz#929372ea827696ceb710eed4168999d0dd2152ed" + integrity sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ== + dependencies: + chalk "^4.1.2" + graceful-fs "^4.2.11" + jest-haste-map "30.5.1" + jest-util "30.5.1" + jest-validate "30.5.1" + slash "^3.0.0" + unrs-resolver "^1.12.1" + jest-runner@30.4.2: version "30.4.2" resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-30.4.2.tgz#15debf3cb6d817538aa97427d5a79277cdff65fe" @@ -8312,6 +9674,34 @@ jest-runner@30.4.2: p-limit "^3.1.0" source-map-support "0.5.13" +jest-runner@30.5.1, jest-runner@^30.0.4: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-30.5.1.tgz#2c2bc32ae71d7be4090bcd03b5018fc1b9733cc6" + integrity sha512-FPlQE4+mwnFxXmpPrSi836KV2ZzvK1g6/nPCT8o5BcoDUUNJQeHo1/Qdkoe/QeoL4M7OeJpbnGUhYKhC1VMdaQ== + dependencies: + "@jest/console" "30.5.1" + "@jest/environment" "30.5.1" + "@jest/source-map" "30.5.0" + "@jest/test-result" "30.5.1" + "@jest/transform" "30.5.1" + "@jest/types" "30.5.1" + "@types/node" "*" + chalk "^4.1.2" + emittery "^0.13.1" + exit-x "^0.2.2" + graceful-fs "^4.2.11" + jest-docblock "30.5.0" + jest-environment-node "30.5.1" + jest-haste-map "30.5.1" + jest-leak-detector "30.5.1" + jest-message-util "30.5.1" + jest-resolve "30.5.1" + jest-runtime "30.5.1" + jest-util "30.5.1" + jest-watcher "30.5.1" + jest-worker "30.5.1" + p-limit "^3.1.0" + jest-runtime@30.4.2: version "30.4.2" resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-30.4.2.tgz#03b5955003440975b12e76518ec85d091c25b84a" @@ -8340,6 +9730,42 @@ jest-runtime@30.4.2: slash "^3.0.0" strip-bom "^4.0.0" +jest-runtime@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-30.5.1.tgz#76372008c88d124ad832e2f759ad280a92cca634" + integrity sha512-UB88+NRkK2Tw/OqV7dofYcyiUGrVZtD41k/N0xQOs9fG//57XHK7JLWG52HD1UYpUAhjK4xm6DBvFX3yWudsMA== + dependencies: + "@jest/environment" "30.5.1" + "@jest/fake-timers" "30.5.1" + "@jest/globals" "30.5.1" + "@jest/source-map" "30.5.0" + "@jest/test-result" "30.5.1" + "@jest/transform" "30.5.1" + "@jest/types" "30.5.1" + "@types/node" "*" + chalk "^4.1.2" + cjs-module-lexer "^2.2.0" + collect-v8-coverage "^1.0.2" + es-module-lexer "^2.1.0" + glob "^13.0.6" + graceful-fs "^4.2.11" + jest-haste-map "30.5.1" + jest-message-util "30.5.1" + jest-mock "30.5.1" + jest-regex-util "30.5.0" + jest-resolve "30.5.1" + jest-snapshot "30.5.1" + jest-util "30.5.1" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-serializer-html@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/jest-serializer-html/-/jest-serializer-html-7.1.0.tgz#0cfea8a03b9b82bc420fd2cb969bd76713a87c08" + integrity sha512-xYL2qC7kmoYHJo8MYqJkzrl/Fdlx+fat4U1AqYg+kafqwcKPiMkOcjWHPKhueuNEgr+uemhGc+jqXYiwCyRyLA== + dependencies: + diffable-html "^4.1.0" + jest-snapshot@30.4.1: version "30.4.1" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.4.1.tgz#0380cbbaa9d53d32cf7e61af98459ac10a339842" @@ -8367,6 +9793,33 @@ jest-snapshot@30.4.1: semver "^7.7.2" synckit "^0.11.8" +jest-snapshot@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.5.1.tgz#37fe8f798710bc1b9f598d5f51313c0c38438eae" + integrity sha512-cNWFdSb5xuDGl8hKkAZJ3YtI/PzHpAPFV+HUXWIOG8rMhpDTLVbvAc2d2wRicoYw2wGsJGLaurJT6BLC97bLXQ== + dependencies: + "@babel/core" "^7.27.4" + "@babel/generator" "^7.27.5" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.27.1" + "@babel/types" "^7.27.3" + "@jest/expect-utils" "30.5.1" + "@jest/get-type" "30.5.0" + "@jest/snapshot-utils" "30.5.1" + "@jest/transform" "30.5.1" + "@jest/types" "30.5.1" + babel-preset-current-node-syntax "^1.2.0" + chalk "^4.1.2" + expect "30.5.1" + graceful-fs "^4.2.11" + jest-diff "30.5.1" + jest-matcher-utils "30.5.1" + jest-message-util "30.5.1" + jest-util "30.5.1" + pretty-format "30.5.1" + semver "^7.7.2" + synckit "^0.11.8" + jest-util@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.2.0.tgz#5142adbcad6f4e53c2776c067a4db3c14f913705" @@ -8391,6 +9844,18 @@ jest-util@30.4.1: graceful-fs "^4.2.11" picomatch "^4.0.3" +jest-util@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.5.1.tgz#1e71a1ee24f365c34001c1f8aab6d6f52ceadcb7" + integrity sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg== + dependencies: + "@jest/types" "30.5.1" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.3" + jest-validate@30.4.1: version "30.4.1" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.4.1.tgz#dcc4784547bf644dca0226d3266fb1bde392c5a4" @@ -8403,6 +9868,31 @@ jest-validate@30.4.1: leven "^3.1.0" pretty-format "30.4.1" +jest-validate@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.5.1.tgz#0922700fae123c9e1d8403bdf62924b7e3c668de" + integrity sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg== + dependencies: + "@jest/get-type" "30.5.0" + "@jest/types" "30.5.1" + camelcase "^6.3.0" + chalk "^4.1.2" + leven "^3.1.0" + pretty-format "30.5.1" + +jest-watch-typeahead@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-3.0.1.tgz#50cb9653190228b8ebd489b37645067ee0bb7584" + integrity sha512-SFmHcvdueTswZlVhPCWfLXMazvwZlA2UZTrcE7MC3NwEVeWvEcOx6HUe+igMbnmA6qowuBSW4in8iC6J2EYsgQ== + dependencies: + ansi-escapes "^7.0.0" + chalk "^5.2.0" + jest-regex-util "^30.0.0" + jest-watcher "^30.0.0" + slash "^5.0.0" + string-length "^6.0.0" + strip-ansi "^7.0.1" + jest-watcher@30.4.1: version "30.4.1" resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-30.4.1.tgz#d2a78fd27553db9206947eeda6068d76bacfd276" @@ -8417,6 +9907,20 @@ jest-watcher@30.4.1: jest-util "30.4.1" string-length "^4.0.2" +jest-watcher@30.5.1, jest-watcher@^30.0.0: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-30.5.1.tgz#70fc6bbcc26282b1bae5d53d7e46259e266b238d" + integrity sha512-+FHJ7C+S7b3ySfhA1aFmoa6TztsnwZVv84ycPRn0tVN93np2EU4b8C/KadqA3l6igdjgLBTnUotab9ER0HLG8A== + dependencies: + "@jest/test-result" "30.5.1" + "@jest/types" "30.5.1" + "@types/node" "*" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + emittery "^0.13.1" + jest-util "30.5.1" + string-length "^4.0.2" + jest-worker@30.4.1: version "30.4.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.4.1.tgz#ac010eb6c512425748a39e2d6bf05b2c4866ca4f" @@ -8428,6 +9932,17 @@ jest-worker@30.4.1: merge-stream "^2.0.0" supports-color "^8.1.1" +jest-worker@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.5.1.tgz#32a4c17502addef713411ca3bf951796de83e2ba" + integrity sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA== + dependencies: + "@types/node" "*" + "@ungap/structured-clone" "^1.3.0" + jest-util "30.5.1" + merge-stream "^2.0.0" + supports-color "^8.1.1" + jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -8437,6 +9952,16 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" +jest@^30.0.4: + version "30.5.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-30.5.1.tgz#db781144fcff8b4859d8dd5a0108d4c3b2173cfd" + integrity sha512-3qrR8+ZXFnn7y0H2yjWQNkGGBLBY4zTRoTMAq9zJcgwLLtlyonfsCLviIXK9xuE2KgIyM+M36AFcoK2DgFR36w== + dependencies: + "@jest/core" "30.5.1" + "@jest/types" "30.5.1" + import-local "^3.2.0" + jest-cli "30.5.1" + jest@^30.4.2: version "30.4.2" resolved "https://registry.yarnpkg.com/jest/-/jest-30.4.2.tgz#e9bdb00f4bf1126d781b0d98e23130db096bbd9a" @@ -8447,6 +9972,17 @@ jest@^30.4.2: import-local "^3.2.0" jest-cli "30.4.2" +joi@^17.11.0: + version "17.13.8" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.8.tgz#92069d521eea09fd709d8abbb86b0ef02862224f" + integrity sha512-iPKOGmiRw1jxf/JOPwxmCcUQAOdF359mdzYiP2DJ+TMX0YK2zjK3D+zYOaGjpumWxOFF/l2xVWjRVK5bGSLdEw== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -8572,7 +10108,7 @@ json5@^2.2.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonc-parser@^3.3.1: +jsonc-parser@^3.2.0, jsonc-parser@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== @@ -8615,6 +10151,11 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + known-css-properties@^0.37.0: version "0.37.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.37.0.tgz#10ebe49b9dbb6638860ff8a002fb65a053f4aec5" @@ -8708,12 +10249,17 @@ lodash.escaperegexp@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw== +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== + lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== -lodash@^4.17.20, lodash@^4.17.21, lodash@^4.18.1, lodash@~4.17.21: +lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.23, lodash@^4.18.1, lodash@~4.17.21: version "4.18.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== @@ -8729,6 +10275,11 @@ log-update@^6.1.0: strip-ansi "^7.1.0" wrap-ansi "^9.0.0" +loglevel@^1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08" + integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg== + longest-streak@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" @@ -8815,7 +10366,7 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.2: +make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -9385,7 +10936,14 @@ mime-db@^1.54.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@^2.1.27, mime-types@^2.1.31: +mime-match@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/mime-match/-/mime-match-1.0.2.tgz#3f87c31e9af1a5fd485fb9db134428b23bbb7ba8" + integrity sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg== + dependencies: + wildcard "^1.1.0" + +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@^2.1.35: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -9419,7 +10977,7 @@ minimatch@^10.2.2, minimatch@^10.2.4, minimatch@^10.2.5, minimatch@^3.0.2, minim dependencies: brace-expansion "^1.1.7" -minimist@^1.2.6: +minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -9444,6 +11002,11 @@ minipass@^7.1.3: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + motion-dom@^13.1.1: version "13.1.1" resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-13.1.1.tgz#bbcb7c2e7236583f20759330d1275876131dc2eb" @@ -9482,16 +11045,31 @@ n3@^1.26.0: buffer "^6.0.3" readable-stream "^4.0.0" +namespace-emitter@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz#978d51361c61313b4e6b8cf6f3853d08dfa2b17c" + integrity sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g== + nanoid@^3.3.17: version "3.3.18" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.18.tgz#f66a2de1199ffde0fcf21c8a5f13106b1c081913" integrity sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w== +nanoid@^5.0.9: + version "5.1.16" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.1.16.tgz#fe345c0a1f9007c32fbb5c139e1208bfd3f41ef7" + integrity sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ== + napi-postinstall@^0.3.0: version "0.3.2" resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.2.tgz#03c62080e88b311c4d7423b0f15f0c920bbcc626" integrity sha512-tWVJxJHmBWLy69PvO96TZMZDrzmw5KeiZBz3RHmiM2XZ9grBJ2WgMAFVVg25nqp3ZjTFUs2Ftw1JhscL3Teliw== +napi-postinstall@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.4.tgz#7af256d6588b5f8e952b9190965d6b019653bbb9" + integrity sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -9539,6 +11117,13 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== +node-preload@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== + dependencies: + process-on-spawn "^1.0.0" + node-releases@^2.0.19: version "2.0.19" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" @@ -9605,6 +11190,39 @@ nwsapi@^2.2.16: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.21.tgz#8df7797079350adda208910d8c33fc4c2d7520c3" integrity sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA== +nyc@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" + integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + caching-transform "^4.0.0" + convert-source-map "^1.7.0" + decamelize "^1.2.0" + find-cache-dir "^3.2.0" + find-up "^4.1.0" + foreground-child "^2.0.0" + get-package-type "^0.1.0" + glob "^7.1.6" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-processinfo "^2.0.2" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + make-dir "^3.0.0" + node-preload "^0.2.1" + p-map "^3.0.0" + process-on-spawn "^1.0.0" + resolve-from "^5.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + spawn-wrap "^2.0.0" + test-exclude "^6.0.0" + yargs "^15.0.2" + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -9735,6 +11353,11 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +os-homedir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== + own-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" @@ -9832,11 +11455,37 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.1.tgz#81828f8dc61c6ef5a800585491572cc9892703af" + integrity sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ== + dependencies: + "@types/retry" "0.12.2" + is-network-error "^1.0.0" + retry "^0.13.1" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + package-json-from-dist@^1.0.0, package-json-from-dist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" @@ -10050,6 +11699,18 @@ pkg-dir@^5.0.0: dependencies: find-up "^5.0.0" +playwright-core@1.63.0, playwright-core@>=1.2.0: + version "1.63.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.63.0.tgz#e57665bc32846c213ac39a1e4d5bc6228e76b376" + integrity sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg== + +playwright@1.63.0, playwright@^1.14.0: + version "1.63.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.63.0.tgz#99b56f9f69b1b70c44f00bf84b2fe52348ae2511" + integrity sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg== + dependencies: + playwright-core "1.63.0" + please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -10160,6 +11821,11 @@ postcss@^6.0.14, postcss@^8.2.7, postcss@^8.4.40, postcss@^8.5.16, postcss@^8.5. picocolors "^1.1.1" source-map-js "^1.2.1" +preact@^10.26.10, preact@^10.5.13: + version "10.29.8" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.29.8.tgz#fc85b82dc2474e245b1430b51781d417361f5fc3" + integrity sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -10175,6 +11841,11 @@ prettier@^3.9.6: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.6.tgz#b3ea5146515d40fc53f18aa63f74dfab1e10dbf6" integrity sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g== +pretty-bytes@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.1.1.tgz#38cd6bb46f47afbf667c202cfc754bffd2016a3b" + integrity sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ== + pretty-error@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" @@ -10202,6 +11873,16 @@ pretty-format@30.4.1: react-is-18 "npm:react-is@^18.3.1" react-is-19 "npm:react-is@^19.2.5" +pretty-format@30.5.1: + version "30.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.5.1.tgz#0dda910a75d12346b771977b1f328517b9e846d4" + integrity sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg== + dependencies: + "@jest/react-is-18" "npm:react-is@^18.3.1" + "@jest/react-is-19" "npm:react-is@^19.2.5" + "@jest/schemas" "30.5.0" + ansi-styles "^5.2.0" + pretty-format@^27.0.2: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" @@ -10216,6 +11897,13 @@ prismjs@^1.30.0: resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9" integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw== +process-on-spawn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.1.0.tgz#9d5999ba87b3bf0a8acb05322d69f2f5aa4fb763" + integrity sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q== + dependencies: + fromentries "^1.2.0" + process@^0.11.1, process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -10228,6 +11916,14 @@ promise@^8.1.0: dependencies: asap "~2.0.6" +prompts@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -10247,6 +11943,11 @@ property-information@^7.0.0: resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.0.0.tgz#3508a6d6b0b8eb3ca6eb2c6623b164d2ed2ab112" integrity sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg== +proxy-from-env@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz#a7487568adad577cfaaa7e88c49cab3ab3081aba" + integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA== + punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -10484,6 +12185,15 @@ readable-stream@1.1: isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@^3.1.1: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@^4.0.0: version "4.7.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91" @@ -10673,6 +12383,13 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA== + dependencies: + es6-error "^4.0.1" + remark-definition-list@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/remark-definition-list/-/remark-definition-list-2.0.0.tgz#0047c727416ffe64ee92f961d91100fda20db87b" @@ -10745,6 +12462,11 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + reset-css@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/reset-css/-/reset-css-5.0.2.tgz#67432ee97f1dc133b87b7e1475cdf0bd19556ed8" @@ -10757,6 +12479,14 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" +resolve-dir@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" + integrity sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA== + dependencies: + expand-tilde "^1.2.2" + global-modules "^0.2.3" + resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" @@ -10811,6 +12541,11 @@ restore-cursor@^5.0.0: onetime "^7.0.0" signal-exit "^4.1.0" +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + reusify@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" @@ -10821,7 +12556,7 @@ rfdc@^1.4.1: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== -rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -10860,6 +12595,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rxjs@^7.8.1: + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + safe-array-concat@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" @@ -10977,7 +12719,7 @@ semver@^5.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.0.0, semver@^6.3.1: +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -11019,6 +12761,11 @@ serialize-javascript@^6.0.2, serialize-javascript@^7.0.5: resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.1.0.tgz#9e462c5c6dec5dbc8b55d90c52a4ad6aff985b9f" integrity sha512-RNEqWOyhhUQYN9V1GfHwu9AR/g+NTciH6Z5u3/no6X3/w+04J2lVDL+svFQVXgXrEGBMG2puMVN3gq2SNGuTGw== +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -11126,12 +12873,17 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slash@^5.1.0: +slash@^5.0.0, slash@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce" integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg== @@ -11197,7 +12949,7 @@ source-map-support@^0.5.16, source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0, source-map@~0.6.0, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -11207,6 +12959,28 @@ space-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== +spawn-wrap@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" + integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== + dependencies: + foreground-child "^2.0.0" + is-windows "^1.0.2" + make-dir "^3.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + which "^2.0.1" + +spawnd@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/spawnd/-/spawnd-5.0.0.tgz#ea72200bdc468998e84e1c3e7b914ce85fc1c32c" + integrity sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA== + dependencies: + exit "^0.1.2" + signal-exit "^3.0.3" + tree-kill "^1.2.2" + wait-port "^0.2.9" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -11255,6 +13029,13 @@ string-length@^4.0.2: char-regex "^1.0.2" strip-ansi "^6.0.0" +string-length@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-6.0.0.tgz#1c7342bbf032129b2f80003e69f889c70231d791" + integrity sha512-1U361pxZHEQ+FeSjzqRpV+cu2vTzYeWeafXFLykiFlv4Vc0n3njgU8HrMbyik5uwm77naWMuVG8fhEF+Ovb1Kg== + dependencies: + strip-ansi "^7.1.0" + "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -11358,7 +13139,7 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.3.0: +string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -11576,6 +13357,13 @@ supports-color@^10.2.2: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-10.2.2.tgz#466c2978cc5cd0052d542a0b576461c2b802ebb4" integrity sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g== +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -11719,6 +13507,15 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +test-exclude@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.2.tgz#482392077630bc57d5630c13abe908bb910dfc65" + integrity sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^10.4.1" + minimatch "^10.2.2" + tiny-invariant@^1.3.1, tiny-invariant@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" @@ -11788,6 +13585,11 @@ tr46@^5.1.0: dependencies: punycode "^2.3.1" +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + trim-lines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" @@ -11853,7 +13655,7 @@ tsconfig-paths@^4.2.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.4.0, tslib@^2.8.0, tslib@^2.8.1: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.8.0, tslib@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -11880,6 +13682,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + type-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/type-flag/-/type-flag-3.0.0.tgz#2caef2f20f2c71e960fe1d3b6f57bae8c8246459" @@ -11930,6 +13737,13 @@ typed-array-length@^1.0.7: possible-typed-array-names "^1.0.0" reflect.getprototypeof "^1.0.6" +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typescript@^5.9.3: version "5.9.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" @@ -12091,6 +13905,36 @@ unplugin@^2.3.5: picomatch "^4.0.3" webpack-virtual-modules "^0.6.2" +unrs-resolver@^1.12.1: + version "1.12.2" + resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.12.2.tgz#a6c6888396abba5adaac4cab6587df866f1d7afd" + integrity sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ== + dependencies: + napi-postinstall "^0.3.4" + optionalDependencies: + "@unrs/resolver-binding-android-arm-eabi" "1.12.2" + "@unrs/resolver-binding-android-arm64" "1.12.2" + "@unrs/resolver-binding-darwin-arm64" "1.12.2" + "@unrs/resolver-binding-darwin-x64" "1.12.2" + "@unrs/resolver-binding-freebsd-x64" "1.12.2" + "@unrs/resolver-binding-linux-arm-gnueabihf" "1.12.2" + "@unrs/resolver-binding-linux-arm-musleabihf" "1.12.2" + "@unrs/resolver-binding-linux-arm64-gnu" "1.12.2" + "@unrs/resolver-binding-linux-arm64-musl" "1.12.2" + "@unrs/resolver-binding-linux-loong64-gnu" "1.12.2" + "@unrs/resolver-binding-linux-loong64-musl" "1.12.2" + "@unrs/resolver-binding-linux-ppc64-gnu" "1.12.2" + "@unrs/resolver-binding-linux-riscv64-gnu" "1.12.2" + "@unrs/resolver-binding-linux-riscv64-musl" "1.12.2" + "@unrs/resolver-binding-linux-s390x-gnu" "1.12.2" + "@unrs/resolver-binding-linux-x64-gnu" "1.12.2" + "@unrs/resolver-binding-linux-x64-musl" "1.12.2" + "@unrs/resolver-binding-openharmony-arm64" "1.12.2" + "@unrs/resolver-binding-wasm32-wasi" "1.12.2" + "@unrs/resolver-binding-win32-arm64-msvc" "1.12.2" + "@unrs/resolver-binding-win32-ia32-msvc" "1.12.2" + "@unrs/resolver-binding-win32-x64-msvc" "1.12.2" + unrs-resolver@^1.7.11: version "1.11.1" resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" @@ -12177,7 +14021,12 @@ use-sync-external-store@^1.2.2: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== -util-deprecate@^1.0.2: +use-sync-external-store@^1.3.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz#6dcb66ef569e02f186af6b3d575f414ce746e18f" + integrity sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A== + +util-deprecate@^1.0.1, util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -12205,6 +14054,11 @@ utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -12255,6 +14109,26 @@ w3c-xmlserializer@^5.0.0: dependencies: xml-name-validator "^5.0.0" +wait-on@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.2.0.tgz#d76b20ed3fc1e2bebc051fae5c1ff93be7892928" + integrity sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ== + dependencies: + axios "^1.6.1" + joi "^17.11.0" + lodash "^4.17.21" + minimist "^1.2.8" + rxjs "^7.8.1" + +wait-port@^0.2.9: + version "0.2.14" + resolved "https://registry.yarnpkg.com/wait-port/-/wait-port-0.2.14.tgz#6df40629be2c95aa4073ceb895abef7d872b28c6" + integrity sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ== + dependencies: + chalk "^2.4.2" + commander "^3.0.2" + debug "^4.1.1" + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -12450,6 +14324,11 @@ which-collection@^1.0.2: is-weakmap "^2.0.2" is-weakset "^2.0.3" +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + which-pm-runs@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.1.0.tgz#35ccf7b1a0fce87bd8b92a478c9d045785d3bf35" @@ -12481,7 +14360,7 @@ which-typed-array@^1.1.2: gopd "^1.2.0" has-tostringtag "^1.0.2" -which@^1.2.14, which@^1.3.1: +which@^1.2.12, which@^1.2.14, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -12500,6 +14379,11 @@ wicg-inert@^3.1.3: resolved "https://registry.yarnpkg.com/wicg-inert/-/wicg-inert-3.1.3.tgz#e53dbc9ac1e0d7f8c60f25e707614a835986272a" integrity sha512-5L0PKK7iP+0Q/jv2ccgmkz/pfXbumZtlEyWS/xnX+L+Og3f7WjL4+iEs18k4IuldOX3PgGpza3qGndL9xUBjCQ== +wildcard@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-1.1.2.tgz#a7020453084d8cd2efe70ba9d3696263de1710a5" + integrity sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng== + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" @@ -12514,6 +14398,15 @@ word-wrap@^1.2.5: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -12555,6 +14448,16 @@ write-file-atomic@^2.3.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + write-file-atomic@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" @@ -12592,11 +14495,21 @@ xml-name-validator@^5.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== +xml@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== + xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -12617,6 +14530,14 @@ yaml@^2.7.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.1.tgz#44a247d1b88523855679ac7fa7cda6ed7e135cf6" integrity sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" @@ -12627,6 +14548,23 @@ yargs-parser@^22.0.0: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-22.0.0.tgz#87b82094051b0567717346ecd00fd14804b357c8" integrity sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw== +yargs@^15.0.2: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"