diff --git a/.circleci/config.yml b/.circleci/config.yml index 993e54db3f..040d6c9724 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1125,6 +1125,29 @@ jobs: - store_test_results: path: libs/test-utils/reports/ + # @votingworks/test-utils-vitest + test-libs-test-utils-vitest: + executor: nodejs + resource_class: xlarge + steps: + - checkout-and-install + - run: + name: Build + command: | + pnpm --dir libs/test-utils-vitest build + - run: + name: Lint + command: | + pnpm --dir libs/test-utils-vitest lint + - run: + name: Test + command: | + pnpm --dir libs/test-utils-vitest test + environment: + JEST_JUNIT_OUTPUT_DIR: ./reports/ + - store_test_results: + path: libs/test-utils-vitest/reports/ + # @votingworks/types test-libs-types: executor: nodejs @@ -1447,6 +1470,7 @@ workflows: - test-libs-pdi-scanner - test-libs-printing - test-libs-test-utils + - test-libs-test-utils-vitest - test-libs-types - test-libs-types-rs - test-libs-ui diff --git a/libs/test-utils-vitest/.eslintignore b/libs/test-utils-vitest/.eslintignore new file mode 100644 index 0000000000..ca254321c0 --- /dev/null +++ b/libs/test-utils-vitest/.eslintignore @@ -0,0 +1,3 @@ +build +coverage +vitest.config.ts \ No newline at end of file diff --git a/libs/test-utils-vitest/.eslintrc.json b/libs/test-utils-vitest/.eslintrc.json new file mode 100644 index 0000000000..291c40eeef --- /dev/null +++ b/libs/test-utils-vitest/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": ["plugin:vx/recommended"], + "rules": { + // Disable JSDOC rule as code is self-documenting. + "vx/gts-jsdoc": "off" + } +} diff --git a/libs/test-utils-vitest/.gitignore b/libs/test-utils-vitest/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/test-utils-vitest/package.json b/libs/test-utils-vitest/package.json new file mode 100644 index 0000000000..8f29f808f5 --- /dev/null +++ b/libs/test-utils-vitest/package.json @@ -0,0 +1,46 @@ +{ + "name": "@votingworks/test-utils-vitest", + "version": "1.0.0", + "private": true, + "description": "Test utilities for the monorepo using vitest instead of jest", + "license": "GPL-3.0-only", + "main": "build/index.js", + "types": "build/index.d.ts", + "files": [ + "build" + ], + "scripts": { + "build": "pnpm --filter $npm_package_name... build:self", + "build:self": "tsc --build tsconfig.build.json", + "clean": "pnpm --filter $npm_package_name... clean:self", + "clean:self": "rm -rf build && tsc --build --clean tsconfig.build.json", + "lint": "pnpm type-check && eslint .", + "lint:fix": "pnpm type-check && eslint . --fix", + "pre-commit": "lint-staged", + "test": "is-ci test:ci test:watch", + "test:ci": "vitest --coverage --reporter junit --outputFile reports/junit.xml", + "test:coverage": "vitest run --coverage", + "test:watch": "vitest", + "type-check": "tsc --build" + }, + "dependencies": { + "@testing-library/react": "^15.0.7", + "@votingworks/basics": "workspace:*", + "@votingworks/types": "workspace:*", + "chalk": "4.1.2", + "jest-diff": "^29.6.2", + "react": "18.3.1", + "vitest": "^2.1.8" + }, + "devDependencies": { + "@types/kiosk-browser": "workspace:*", + "@types/node": "20.16.0", + "@types/react": "18.3.3", + "@vitest/coverage-istanbul": "^2.1.8", + "eslint-plugin-vx": "workspace:*", + "is-ci-cli": "2.2.0", + "lint-staged": "11.0.0", + "sort-package-json": "^1.50.0" + }, + "packageManager": "pnpm@8.15.5" +} diff --git a/libs/test-utils-vitest/src/advance_timers.ts b/libs/test-utils-vitest/src/advance_timers.ts new file mode 100644 index 0000000000..67a8449e5a --- /dev/null +++ b/libs/test-utils-vitest/src/advance_timers.ts @@ -0,0 +1,25 @@ +import { vi } from 'vitest'; +import { act, waitFor } from '@testing-library/react'; + +export const IDLE_TIMEOUT_SECONDS = 5 * 60; // 5 minute + +export function advanceTimers(seconds = 0): void { + const maxSeconds = IDLE_TIMEOUT_SECONDS; + if (seconds > maxSeconds) { + throw new Error(`Seconds value should not be greater than ${maxSeconds}`); + } + act(() => { + vi.advanceTimersByTime(seconds * 1000); + }); +} + +export async function advancePromises(): Promise { + await waitFor(() => { + // Wait for promises. + }); +} + +export async function advanceTimersAndPromises(seconds = 0): Promise { + advanceTimers(seconds); + await advancePromises(); +} diff --git a/libs/test-utils-vitest/src/child_process.test.ts b/libs/test-utils-vitest/src/child_process.test.ts new file mode 100644 index 0000000000..3acb9c6daf --- /dev/null +++ b/libs/test-utils-vitest/src/child_process.test.ts @@ -0,0 +1,109 @@ +import { expect, test, vi } from 'vitest'; +import { Buffer } from 'node:buffer'; +import { mockChildProcess, mockReadable, mockWritable } from './child_process'; + +test('mockReadable', () => { + const onReadable = vi.fn(); + const onData = vi.fn(); + const onEnd = vi.fn(); + const readable = mockReadable() + .on('readable', onReadable) + .on('data', onData) + .on('end', onEnd); + + expect(onReadable).not.toHaveBeenCalled(); + expect(onData).not.toHaveBeenCalled(); + expect(onEnd).not.toHaveBeenCalled(); + + readable.append('abcd'); + expect(onReadable).toHaveBeenCalledTimes(1); + expect(onData).toHaveBeenNthCalledWith(1, 'abcd'); + expect(onEnd).not.toHaveBeenCalled(); + expect(readable.read(1)).toEqual('a'); + expect(readable.read(1)).toEqual('b'); + expect(readable.read()).toEqual('cd'); + expect(readable.read()).toBeUndefined(); + + expect(readable.isPaused()).toEqual(false); + readable.pause(); + expect(readable.isPaused()).toEqual(true); + + readable.append('efgh'); + expect(onReadable).toHaveBeenCalledTimes(1); + expect(onData).toHaveBeenCalledTimes(1); + expect(onEnd).not.toHaveBeenCalled(); + readable.resume(); + expect(onReadable).toHaveBeenCalledTimes(2); + expect(onData).toHaveBeenNthCalledWith(2, 'efgh'); + expect(onEnd).not.toHaveBeenCalled(); + readable.append('ijkl'); + expect(onData).toHaveBeenNthCalledWith(3, 'ijkl'); + expect(readable.read(5)).toEqual('efghi'); + expect(readable.read(100)).toEqual('jkl'); + + readable.end(); + expect(onEnd).toHaveBeenCalledTimes(1); +}); + +test('mockWritable', async () => { + const writable = mockWritable(); + expect(writable.toBuffer()).toEqual(Buffer.of()); + expect(writable.toString()).toEqual(''); + + writable.write(Buffer.of(1, 2, 3)); + expect(writable.toBuffer()).toEqual(Buffer.of(1, 2, 3)); + expect(writable.toString()).toEqual('\x01\x02\x03'); // mirrors `Buffer.of(1, 2, 3)` + + writable.write('hi!', 'ascii'); + expect(writable.toBuffer()).toEqual(Buffer.of(1, 2, 3, 104, 105, 33)); + expect(writable.toString()).toEqual('\x01\x02\x03hi!'); // mirrors `Buffer.of(1, 2, 3, 104, 105, 33)` + + { + const writeCallback = vi.fn(); + writable.write('', 'utf-8', writeCallback); + await new Promise((resolve) => { + process.nextTick(resolve); + }); + expect(writeCallback).toHaveBeenCalledWith(); + } + + { + const writeCallback = vi.fn(); + writable.write('', writeCallback); + await new Promise((resolve) => { + process.nextTick(resolve); + }); + expect(writeCallback).toHaveBeenCalledWith(); + } + + // @ts-expect-error - testing invalid argument type + expect(() => writable.write('', 88)).toThrowError( + 'encoding expected to be a string' + ); + + const endCallback = vi.fn(); + writable.end(endCallback); + await new Promise((resolve) => { + process.nextTick(resolve); + }); + expect(endCallback).toHaveBeenCalledWith(); + + expect(writable.writes).toEqual([ + { chunk: Buffer.of(1, 2, 3), encoding: undefined }, + { chunk: 'hi!', encoding: 'ascii' }, + { chunk: '', encoding: 'utf-8' }, + { chunk: '', encoding: undefined }, + ]); +}); + +test('mockChildProcess', () => { + const child = mockChildProcess(); + + expect(typeof child.pid).toEqual('number'); + child.stdin.write('hello child!\n'); + + const onExit = vi.fn(); + child.on('exit', onExit); + child.emit('exit'); + expect(onExit).toHaveBeenCalled(); +}); diff --git a/libs/test-utils-vitest/src/child_process.ts b/libs/test-utils-vitest/src/child_process.ts new file mode 100644 index 0000000000..3e65594a6f --- /dev/null +++ b/libs/test-utils-vitest/src/child_process.ts @@ -0,0 +1,180 @@ +import { vi } from 'vitest'; +import { Buffer } from 'node:buffer'; +import { ChildProcess } from 'node:child_process'; +import { EventEmitter } from 'node:events'; +import { Readable, Writable } from 'node:stream'; + +export interface MockReadable extends Readable { + append(chunk: string): void; + end(): void; +} + +export interface MockWritable extends Writable { + toBuffer(): Buffer; + toString(): string; + writes: ReadonlyArray<{ chunk: unknown; encoding?: string }>; +} + +/** + * Makes a mock readable stream. + */ +export function mockReadable(): MockReadable { + const readable = new EventEmitter() as MockReadable; + let buffer: string | undefined; + let isPaused = false; + let pendingChunks: unknown[] = []; + + function flush(): void { + for (const chunk of pendingChunks) { + buffer = (buffer ?? '') + chunk; + readable.emit('readable'); + readable.emit('data', chunk); + } + pendingChunks = []; + } + + readable.resume = vi.fn(() => { + isPaused = false; + flush(); + return readable; + }); + readable.pause = vi.fn(() => { + isPaused = true; + return readable; + }); + readable.isPaused = vi.fn().mockImplementation(() => isPaused); + readable.setEncoding = vi.fn(); + readable.append = vi.fn((chunk): void => { + pendingChunks.push(chunk); + if (!isPaused) { + flush(); + } + }); + readable.read = vi.fn((size): unknown => { + if (typeof buffer === 'string') { + const readSize = size ?? buffer.length; + const result = buffer.slice(0, readSize); + buffer = buffer.length <= readSize ? undefined : buffer.slice(readSize); + return result; + } + + return undefined; + }); + readable.end = vi.fn(() => { + readable.emit('end'); + }); + return readable; +} + +/** + * Makes a mock writable stream. + */ +export function mockWritable(): MockWritable { + const writable = new EventEmitter() as MockWritable; + const writes: Array<{ chunk: unknown; encoding?: string }> = []; + + writable.writes = writes; + writable.write = vi.fn((...args: unknown[]): boolean => { + let chunk: unknown; + let encoding: unknown; + let callback: unknown; + + if (args.length === 3) { + [chunk, encoding, callback] = args; + } else if (args.length === 2 && typeof args[1] === 'function') { + [chunk, callback] = args; + } else if (args.length === 2) { + [chunk, encoding] = args; + } else { + [chunk] = args; + } + + if (typeof encoding !== 'undefined' && typeof encoding !== 'string') { + throw new TypeError('encoding expected to be a string'); + } + + if (typeof chunk !== 'undefined') { + writes.push({ chunk, encoding }); + } + + process.nextTick(() => { + if (typeof callback === 'function') { + callback(); + } + }); + + return true; + }); + + writable.end = vi.fn((...args: unknown[]): MockWritable => { + let chunk: unknown; + let encoding: unknown; + let callback: unknown; + + if (args.length === 3) { + [chunk, encoding, callback] = args; + } else if (args.length === 2 && typeof args[1] === 'function') { + [chunk, callback] = args; + } else if (args.length === 2) { + [chunk, encoding] = args; + } else { + [callback] = args; + } + + if (typeof encoding !== 'undefined' && typeof encoding !== 'string') { + throw new TypeError('encoding expected to be a string'); + } + + if (typeof chunk !== 'undefined') { + writes.push({ chunk, encoding }); + } + + process.nextTick(() => { + if (typeof callback === 'function') { + callback(); + } + }); + + return writable; + }); + + writable.toBuffer = () => + writes.reduce( + (result, { chunk }) => + Buffer.concat([result, Buffer.from(chunk as Buffer | string)]), + Buffer.of() + ); + + writable.toString = () => + writes.reduce( + (result, { chunk, encoding }) => + result + + (typeof chunk === 'string' + ? chunk + : (chunk as Buffer).toString(encoding as BufferEncoding | undefined)), + '' + ); + + return writable; +} + +export interface MockChildProcess extends ChildProcess { + stdin: MockWritable; + stdout: MockReadable; + stderr: MockReadable; +} + +/** + * Creates a mock child process with mock streams. + */ +export function mockChildProcess(): MockChildProcess { + const result: Partial = { + pid: Math.floor(Math.random() * 10_000), + stdin: mockWritable(), + stdout: mockReadable(), + stderr: mockReadable(), + kill: vi.fn(), + }; + + return Object.assign(new EventEmitter(), result) as MockChildProcess; +} diff --git a/libs/test-utils-vitest/src/console.test.ts b/libs/test-utils-vitest/src/console.test.ts new file mode 100644 index 0000000000..32240f138b --- /dev/null +++ b/libs/test-utils-vitest/src/console.test.ts @@ -0,0 +1,39 @@ +/* eslint-disable no-console */ +import { expect, test } from 'vitest'; +import { suppressingConsoleOutput } from './console'; + +test.each(['log', 'warn', 'error'] as const)( + 'suppressingConsoleOutput replaces console.%s with a mock', + (method) => { + const original = console[method]; + suppressingConsoleOutput(() => { + console[method]('test'); + expect(console[method]).toHaveBeenNthCalledWith(1, 'test'); + }); + expect(console[method]).toStrictEqual(original); + } +); + +test('suppressingConsoleOutput returns the value returned by the callback', () => { + expect(suppressingConsoleOutput(() => 'test')).toEqual('test'); +}); + +test('suppressingConsoleOutput resolves to the value returned by the callback', async () => { + await expect( + suppressingConsoleOutput(async () => await Promise.resolve('test')) + ).resolves.toEqual('test'); +}); + +test('suppressingConsoleOutput throws the error thrown by the callback', () => { + expect(() => + suppressingConsoleOutput(() => { + throw new Error('test'); + }) + ).toThrowError('test'); +}); + +test('suppressingConsoleOutput rejects with the error thrown by the callback', async () => { + await expect( + suppressingConsoleOutput(() => Promise.reject(new Error('test'))) + ).rejects.toThrowError('test'); +}); diff --git a/libs/test-utils-vitest/src/console.ts b/libs/test-utils-vitest/src/console.ts new file mode 100644 index 0000000000..666d832036 --- /dev/null +++ b/libs/test-utils-vitest/src/console.ts @@ -0,0 +1,182 @@ +import { afterAll, beforeAll, expect, vi } from 'vitest'; +import chalk from 'chalk'; +import { MaybePromise, Optional, assert, iter } from '@votingworks/basics'; + +const capturedCallCountsByTest = new Map< + string, + Map<'log' | 'warn' | 'error', { count: number }> +>(); + +/** + * Strips ANSI escape codes from a string. When printing a box around a string + * with ANSI escape codes, the box will be the wrong size. This function removes + * the ANSI escape codes so that the box is the correct size. + */ +function stripAnsi(str: string): string { + // eslint-disable-next-line no-control-regex + return str.replace(/\x1b\[[0-9;]*m/g, ''); +} + +function printLinesInBox(lines: string[], out: NodeJS.WritableStream) { + const boxTopLeft = '┌'; + const boxTopRight = '┐'; + const boxTopAndBottom = '─'; + const boxLeft = '│'; + const boxRight = '│'; + const boxBottomLeft = '└'; + const boxBottomRight = '┘'; + + const longestLine = + iter(lines) + .map((line) => stripAnsi(line).length) + .max() ?? 0; + + const boxTop = `${boxTopLeft}${boxTopAndBottom.repeat( + longestLine + 2 + )}${boxTopRight}`; + const boxBottom = `${boxBottomLeft}${boxTopAndBottom.repeat( + longestLine + 2 + )}${boxBottomRight}`; + + out.write(chalk.dim(`${boxTop}\n`)); + for (const line of lines) { + out.write( + chalk.dim( + `${boxLeft} ${line}${' '.repeat( + longestLine - stripAnsi(line).length + )} ${boxRight}\n` + ) + ); + } + out.write(chalk.dim(`${boxBottom}\n`)); +} + +if (typeof beforeAll === 'function' && typeof afterAll === 'function') { + beforeAll(() => { + capturedCallCountsByTest.clear(); + }); + + afterAll(() => { + const shouldPrintSummary = process.env['CI'] !== 'true'; + + if (!shouldPrintSummary) { + return; + } + + const allSummaries = Array.from(capturedCallCountsByTest).flatMap( + ([testName, capturedCallCounts]) => { + const summaries = Array.from(capturedCallCounts.entries()) + .filter(([, { count }]) => count > 0) + .map( + ([name, { count }]) => `${count} ${name}${count > 1 ? 's' : ''}` + ); + + return summaries.length > 0 + ? [`${testName} (${summaries.join(', ')})`] + : []; + } + ); + + if (allSummaries.length > 0) { + printLinesInBox( + [ + '🤫 Some tests suppressed console output:', + '', + ...allSummaries, + '', + `Run with ${chalk.italic( + 'SUPPRESS_CONSOLE_OUTPUT=false' + )} to see the output.`, + ], + process.stderr + ); + process.stderr.write('\n'); + } + }); +} + +/** + * Suppresses console output during the execution of a function. Resolves to the + * return value of the function. + */ +export function suppressingConsoleOutput(fn: () => Promise): Promise; + +/** + * Suppresses console output during the execution of a function. Returns the + * return value of the function. + */ +export function suppressingConsoleOutput(fn: () => T): T; + +export function suppressingConsoleOutput( + fn: () => MaybePromise +): MaybePromise { + if (process.env['SUPPRESS_CONSOLE_OUTPUT'] === 'false') { + return fn(); + } + + const { currentTestName } = expect.getState(); + assert(currentTestName !== undefined); + const capturedCallCounts = + capturedCallCountsByTest.get(currentTestName) ?? + new Map([ + ['log', { count: 0 }], + ['warn', { count: 0 }], + ['error', { count: 0 }], + ]); + capturedCallCountsByTest.set(currentTestName, capturedCallCounts); + const logStats = capturedCallCounts.get('log') as { count: number }; + const warnStats = capturedCallCounts.get('warn') as { count: number }; + const errorStats = capturedCallCounts.get('error') as { count: number }; + + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { + logStats.count += 1; + }); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { + warnStats.count += 1; + }); + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { + errorStats.count += 1; + }); + + function cleanup() { + logSpy.mockRestore(); + warnSpy.mockRestore(); + errorSpy.mockRestore(); + } + + let value: Optional>; + let error: unknown; + let success = false; + + try { + value = fn(); + success = true; + } catch (e) { + error = e; + } + + if (!success) { + cleanup(); + throw error; + } + + if ( + typeof value === 'object' && + value !== null && + typeof (value as Promise).then === 'function' + ) { + return (value as Promise).then( + (v) => { + cleanup(); + return v; + }, + (e) => { + cleanup(); + throw e; + } + ); + } + + cleanup(); + return value as T; +} diff --git a/libs/test-utils-vitest/src/index.ts b/libs/test-utils-vitest/src/index.ts new file mode 100644 index 0000000000..1439c75a85 --- /dev/null +++ b/libs/test-utils-vitest/src/index.ts @@ -0,0 +1,6 @@ +export * from './advance_timers'; +export * from './child_process'; +export * from './console'; +export * from './mock_kiosk'; +export * from './mock_of'; +export * from './mock_use_audio_controls'; diff --git a/libs/test-utils-vitest/src/mock_function.test.ts b/libs/test-utils-vitest/src/mock_function.test.ts new file mode 100644 index 0000000000..6b5ac0a742 --- /dev/null +++ b/libs/test-utils-vitest/src/mock_function.test.ts @@ -0,0 +1,344 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { describe, expect, it } from 'vitest'; +import { mockFunction } from './mock_function'; + +describe('mockFunction', () => { + function add(num1: number, num2: number): number { + throw new Error('Not implemented'); + } + + it('creates a mock function that returns a value', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + addMock.assertComplete(); + }); + + it('ensures the actual input matches the expected input', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + const expectedMessage = + 'Mismatched call to mock function:\nExpected: add(1, 2)\nActual: add(1, 3)'; + expect(() => addMock(1, 3)).toThrow(expectedMessage); + }); + + it('errors on unexpected calls', () => { + const addMock = mockFunction('add'); + const expectedMessage = 'Unexpected call to mock function: add(1, 2)'; + expect(() => addMock(1, 2)).toThrow(expectedMessage); + }); + + it('handles multiple calls', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + addMock.expectCallWith(1, 3).returns(4); + expect(addMock(1, 2)).toEqual(3); + expect(addMock(1, 3)).toEqual(4); + addMock.assertComplete(); + }); + + it('only allows calls to be used once', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(() => addMock(1, 2)).toThrowErrorMatchingInlineSnapshot( + `[Error: Unexpected call to mock function: add(1, 2)]` + ); + }); + + it('enforces the order of calls', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + addMock.expectCallWith(1, 3).returns(4); + expect(() => addMock(1, 3)).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatched call to mock function: + Expected: add(1, 2) + Actual: add(1, 3) + Input diff: - Expected + + Received + + Array [ + 1, + - 2, + + 3, + ]] + `); + }); + + it('supports all different types of arguments', () => { + function funcWithManyTypes( + a: string, + b: number, + c: boolean, + d: null, + e: undefined, + f: { foo: string }, + g: number[] + ): string { + throw new Error('Not implemented'); + } + const funcMock = + mockFunction('funcWithManyTypes'); + funcMock + .expectCallWith('a', 1, true, null, undefined, { foo: 'bar' }, [1, 2]) + .returns('success'); + expect( + funcMock('a', 1, true, null, undefined, { foo: 'bar' }, [1, 2]) + ).toEqual('success'); + expect(() => + funcMock('a', 1, true, null, undefined, { foo: 'bar' }, [1, 2]) + ).toThrowErrorMatchingInlineSnapshot( + `[Error: Unexpected call to mock function: funcWithManyTypes('a', 1, true, null, undefined, { foo: 'bar' }, [ 1, 2 ])]` + ); + funcMock.reset(); + funcMock + .expectCallWith('a', 1, true, null, undefined, { foo: 'bar' }, [1, 2]) + .returns('success'); + expect(() => + funcMock('a', 1, true, null, undefined, { foo: 'wrong' }, [1, 2]) + ).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatched call to mock function: + Expected: funcWithManyTypes('a', 1, true, null, undefined, { foo: 'bar' }, [ 1, 2 ]) + Actual: funcWithManyTypes('a', 1, true, null, undefined, { foo: 'wrong' }, [ 1, 2 ]) + Input diff: - Expected + + Received + + Array [ + "a", + 1, + true, + null, + undefined, + Object { + - "foo": "bar", + + "foo": "wrong", + }, + Array [ + 1, + 2, + ], + ]] + `); + }); + + it('enforces correct types', () => { + const addMock = mockFunction('add'); + // @ts-expect-error - wrong argument type + addMock.expectCallWith('1', 2).returns(3); + // @ts-expect-error - wrong return type + addMock.expectCallWith(1, 2).returns('3'); + }); + + it('supports async functions', async () => { + // eslint-disable-next-line @typescript-eslint/require-await + async function addAsync(num1: number, num2: number): Promise { + throw new Error('Not implemented'); + } + const addMock = mockFunction('addAsync'); + addMock.expectCallWith(1, 2).returns(Promise.resolve(3)); + addMock.expectCallWith(1, 2).resolves(3); + addMock.expectRepeatedCallsWith(1, 2).resolves(3); + expect(await addMock(1, 2)).toEqual(3); + expect(await addMock(1, 2)).toEqual(3); + expect(await addMock(1, 2)).toEqual(3); + expect(await addMock(1, 2)).toEqual(3); + }); + + it('supports mocking an exception', () => { + const addMock = mockFunction('add'); + const error = new Error('Mock error'); + addMock.expectCallWith(1, 2).throws(error); + expect(() => addMock(1, 2)).toThrow(error); + }); + + it('supports repeated calls', () => { + const addMock = mockFunction('add'); + addMock.expectRepeatedCallsWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(addMock(1, 2)).toEqual(3); + expect(addMock(1, 2)).toEqual(3); + addMock.expectCallWith(1, 2).returns(4); + expect(addMock(1, 2)).toEqual(4); + addMock.expectRepeatedCallsWith(1, 2).returns(5); + expect(addMock(1, 2)).toEqual(5); + expect(addMock(1, 2)).toEqual(5); + addMock.assertComplete(); + }); + + it('errors if actual input doesnt match expected input of repeated call', () => { + const addMock = mockFunction('add'); + addMock.expectRepeatedCallsWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(() => addMock(1, 3)).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatched call to mock function: + Expected: add(1, 2) (repeated) + Actual: add(1, 3) + Input diff: - Expected + + Received + + Array [ + 1, + - 2, + + 3, + ]] + `); + }); + + it('supports optional repeated calls', () => { + const addMock = mockFunction('add'); + addMock.expectOptionalRepeatedCallsWith(1, 2).returns(3); + addMock.assertComplete(); + expect(addMock(1, 2)).toEqual(3); + expect(addMock(1, 2)).toEqual(3); + addMock.assertComplete(); + }); + + it('assertComplete errors if not all expected calls are used', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + addMock.expectCallWith(2, 2).returns(4); + addMock(1, 2); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 2) + Actual: add(1, 2) + + Call #1 + Expected: add(2, 2) + Actual: ] + `); + }); + + it('assertComplete errors if not all repeated expected calls are used', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + addMock.expectRepeatedCallsWith(2, 2).returns(4); + addMock(1, 2); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 2) + Actual: add(1, 2) + + Call #1 + Expected: add(2, 2) (repeated) + Actual: ] + `); + }); + + it('assertComplete errors if there are unexpected calls', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(() => addMock(1, 3)).toThrow(); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 2) + Actual: add(1, 2) + + Call #1 + Expected: + Actual: add(1, 3)] + `); + }); + + it('assertComplete errors if there are unexpected calls with repeated calls', () => { + const addMock = mockFunction('add'); + addMock.expectRepeatedCallsWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(() => addMock(1, 3)).toThrow(); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 2) (repeated) + Actual: add(1, 2) + + Call #1 + Expected: add(1, 2) (repeated) + Actual: add(1, 3) + Input diff: - Expected + + Received + + Array [ + 1, + - 2, + + 3, + ]] + `); + }); + + it('assertComplete errors if there are mismatched calls', () => { + const addMock = mockFunction('add'); + addMock.expectCallWith(1, 2).returns(3); + addMock.expectCallWith(1, 3).returns(4); + expect(() => addMock(1, 3)).toThrow(); + expect(() => addMock(1, 2)).toThrow(); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 2) + Actual: add(1, 3) + Input diff: - Expected + + Received + + Array [ + 1, + - 2, + + 3, + ]] + `); + }); + + it('assertComplete errors if there are mismatched calls with repeated calls', () => { + const addMock = mockFunction('add'); + addMock.expectRepeatedCallsWith(1, 3).returns(4); + addMock.expectCallWith(1, 2).returns(3); + expect(() => addMock(1, 2)).toThrow(); + expect(() => addMock(1, 3)).toThrow(); + expect(() => addMock.assertComplete()).toThrowErrorMatchingInlineSnapshot(` + [Error: Mismatch between expected mock function calls and actual mock function calls: + + Call #0 + Expected: add(1, 3) (repeated) + Actual: add(1, 2) + Input diff: - Expected + + Received + + Array [ + 1, + - 3, + + 2, + ]] + `); + }); + + it('reset clears the mock state', () => { + const addMock = mockFunction('add'); + + addMock.expectCallWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + addMock.assertComplete(); + addMock.reset(); + + addMock.expectCallWith(1, 2).returns(3); + addMock.reset(); + + expect(() => addMock(1, 2)).toThrow(); + addMock.reset(); + + addMock.expectRepeatedCallsWith(1, 2).returns(3); + expect(addMock(1, 2)).toEqual(3); + expect(addMock(1, 2)).toEqual(3); + addMock.assertComplete(); + addMock.reset(); + + addMock.assertComplete(); + }); +}); diff --git a/libs/test-utils-vitest/src/mock_function.ts b/libs/test-utils-vitest/src/mock_function.ts new file mode 100644 index 0000000000..427f02b87a --- /dev/null +++ b/libs/test-utils-vitest/src/mock_function.ts @@ -0,0 +1,345 @@ +import { inspect } from 'node:util'; +import { diff } from 'jest-diff'; +import { assertDefined, deepEqual } from '@votingworks/basics'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyFunc = (...args: any[]) => any; + +interface Call { + input: Parameters; +} + +type ActualCall = Call & { + matchingExpectedCallIndex?: number; +}; + +type ExpectedCall = + | ({ + output: ReturnType; + repeated: boolean; + optional?: boolean; + } & Call) + | ({ + error: unknown; + repeated: false; + optional?: false; + } & Call); + +interface ReturnHelpers { + returns(output: ReturnType): void; + throws(error: unknown): void; +} + +interface AsyncReturnHelpers extends ReturnHelpers { + resolves(output: Awaited>): void; +} + +export class MockFunctionError extends Error {} + +export interface MockFunction { + (...input: Parameters): ReturnType; + expectCallWith( + ...input: Parameters + ): ReturnType extends Promise + ? AsyncReturnHelpers + : ReturnHelpers; + expectRepeatedCallsWith( + ...input: Parameters + ): ReturnType extends Promise + ? Omit, 'throws'> + : Omit, 'throws'>; + expectOptionalRepeatedCallsWith( + ...input: Parameters + ): ReturnType extends Promise + ? Omit, 'throws'> + : Omit, 'throws'>; + reset(): void; + assertComplete(): void; +} + +interface MockFunctionState { + expectedCalls: Array>; + actualCalls: Array>; +} + +function formatFunctionCall(name: string, input: unknown[]): string { + return `${name}(${input + .map((arg) => inspect(arg, { depth: null })) + .join(', ')})`; +} + +function formatExpectedAndActualCalls( + name: string, + actualCall?: Call, + expectedCall?: ExpectedCall +): string { + const expectedStr = expectedCall + ? formatFunctionCall(name, expectedCall.input) + + (expectedCall.repeated ? ' (repeated)' : '') + + (expectedCall.optional ? ' (optional)' : '') + : ''; + const actualStr = actualCall + ? formatFunctionCall(name, actualCall.input) + : ''; + const diffStr = + expectedCall && + actualCall && + !deepEqual(actualCall.input, expectedCall.input) + ? diff(expectedCall.input, actualCall.input) + : undefined; + return [`Expected: ${expectedStr}`, `Actual: ${actualStr}`] + .concat(diffStr ? [`Input diff: ${diffStr}`] : []) + .join('\n'); +} + +/** + * Creates a mock function, similar to jest.fn(), but with stricter rules to + * help you avoid making unintentional mistakes. + * + * Example usage: + * + * // Let's say we have a real function, add, that we want to mock + * function add(num1: number, num2: number): number { + * return num1 + num2; + * } + * + * // We can create a mock function that behaves the same way + * const addMock = mockFunction('add'); + * addMock.expectCallWith(1, 2).returns(3); + * addMock(1, 2); // -> returns 3 + * addMock.expectCallWith(1, NaN).throws(new Error('Cant add NaN')); + * addMock(1, NaN); // -> throws Error('Cant add NaN') + * + * // Unlike jest.fn(), mockFunction is strict... + * + * // Each expected call can only be used once, in order + * addMock.expectCallWith(1, 2).returns(3); + * addMock(1, 2); // -> returns 3 + * addMock(1, 2); // -> throws an error because we didn't expect this call + * + * // The actual input must match the expected input + * addMock.expectCallWith(1, 2).returns(3); + * addMock(1, 3); // -> throws an error because the input doesn't match + * + * // Every expected call must be used + * addMock.expectCallWith(1, 2).returns(3); + * addMock.assertComplete(); // -> throws an error because we didn't use the expected call + * + * // You can also expect repeated calls, which is useful for functions that are polled + * addMock.expectedRepeatedCallsWith(1, 2).returns(3); + * addMock(1, 2); // -> returns 3 + * addMock(1, 2); // -> returns 3 + * addMock(1, 3); // -> throws an error because the input doesn't match + * + * Recommendations: + * - Always call mockFunction.assertComplete() to ensure that all expected calls were used + * - If you are going to reuse a mock between test cases, call mockFunction.reset() in afterEach + */ +// eslint-disable-next-line vx/gts-no-return-type-only-generics +export function mockFunction( + name: string +): MockFunction { + const state: MockFunctionState = { + expectedCalls: [], + actualCalls: [], + }; + + // Find the next expected call that should be used for the given actual call. + // We track which expected call each actual call matched and then base the + // next appropriate expected call on that. + function findExpectedCallForActualCallIndex(actualCallIndex: number) { + const previousActualCall = state.actualCalls[actualCallIndex - 1]; + if (!previousActualCall) { + return state.expectedCalls[0]; + } + + // If the previous actual call didn't match any expected call, then we don't + // know what the next expected call should be. + if (previousActualCall.matchingExpectedCallIndex === undefined) { + throw new MockFunctionError( + 'Previous call to mock function did not match expected calls' + ); + } + + // If we have a next expected call, use that + const nextExpectedCallIndex = + previousActualCall.matchingExpectedCallIndex + 1; + if (nextExpectedCallIndex < state.expectedCalls.length) { + return state.expectedCalls[nextExpectedCallIndex]; + } + + // Otherwise, maybe the previous expected call was a repeated expected call, + // so we can use that + const previousExpectedCall = + state.expectedCalls[previousActualCall.matchingExpectedCallIndex]; + if (assertDefined(previousExpectedCall).repeated) { + return previousExpectedCall; + } + + return undefined; + } + + const mock: MockFunction = (...input: Parameters) => { + // Special case - [] and [undefined] are equivalent as args + if (input.length === 1 && input[0] === undefined) { + // eslint-disable-next-line no-param-reassign + input = [] as unknown as Parameters; + } + + const expectedCall = findExpectedCallForActualCallIndex( + state.actualCalls.length + ); + const callMatches = expectedCall && deepEqual(input, expectedCall.input); + + state.actualCalls.push({ + input, + matchingExpectedCallIndex: callMatches + ? state.expectedCalls.indexOf(expectedCall) + : undefined, + }); + + if (!expectedCall) { + const message = `Unexpected call to mock function: ${formatFunctionCall( + name, + input + )}`; + throw new MockFunctionError(message); + } + + if (!callMatches) { + const message = `Mismatched call to mock function:\n${formatExpectedAndActualCalls( + name, + { input }, + expectedCall + )}`; + throw new MockFunctionError(message); + } + + if ('error' in expectedCall) { + throw expectedCall.error; + } + + return expectedCall.output; + }; + + mock.expectCallWith = (...input: Parameters) => ({ + returns(output: ReturnType) { + state.expectedCalls.push({ input, output, repeated: false }); + }, + throws(error: unknown) { + state.expectedCalls.push({ input, error, repeated: false }); + }, + resolves(output: Awaited>) { + state.expectedCalls.push({ + input, + output: Promise.resolve(output) as ReturnType, + repeated: false, + }); + }, + }); + + mock.expectRepeatedCallsWith = (...input: Parameters) => ({ + returns(output: ReturnType) { + state.expectedCalls.push({ + input, + output, + repeated: true, + }); + }, + resolves(output: Awaited>) { + state.expectedCalls.push({ + input, + output: Promise.resolve(output) as ReturnType, + repeated: true, + }); + }, + }); + + mock.expectOptionalRepeatedCallsWith = (...input: Parameters) => ({ + returns(output: ReturnType) { + state.expectedCalls.push({ + input, + output, + repeated: true, + optional: true, + }); + }, + resolves(output: Awaited>) { + state.expectedCalls.push({ + input, + output: Promise.resolve(output) as ReturnType, + repeated: true, + optional: true, + }); + }, + }); + + mock.reset = () => { + state.expectedCalls = []; + state.actualCalls = []; + }; + + mock.assertComplete = () => { + // We want to check: + // - Every actual call matched an expected call + // - Every expected call was used + // So we build a correspondence of actual calls with their expected calls. + + const actualCallsWithTheirExpectedCalls = state.actualCalls.map( + (actualCall, actualCallIndex) => { + const expectedCall = + findExpectedCallForActualCallIndex(actualCallIndex); + return { + actualCall, + expectedCall, + }; + } + ); + + const unusedExpectedCalls = state.expectedCalls.filter( + (expectedCall) => + !expectedCall.optional && + !actualCallsWithTheirExpectedCalls.some( + ({ expectedCall: usedExpectedCall }) => + usedExpectedCall === expectedCall + ) + ); + + const callCorrespondence = [ + ...actualCallsWithTheirExpectedCalls, + ...unusedExpectedCalls.map((expectedCall) => ({ + actualCall: undefined, + expectedCall, + })), + ]; + + // We can only accurately report a correspondence of expected and actual + // calls up to the first mismatched call. After that, we don't know which + // expected calls were used for which actual calls. + const firstMismatchedCallIndex = callCorrespondence.findIndex( + ({ actualCall, expectedCall }) => + !( + actualCall && + expectedCall && + actualCall.matchingExpectedCallIndex !== undefined + ) + ); + + if (firstMismatchedCallIndex !== -1) { + const message = `Mismatch between expected mock function calls and actual mock function calls:\n\n${callCorrespondence + .slice(0, firstMismatchedCallIndex + 1) + .map( + ({ actualCall, expectedCall }, index) => + `Call #${index}\n${formatExpectedAndActualCalls( + name, + actualCall, + expectedCall + )}` + ) + .join('\n\n')}`; + throw new MockFunctionError(message); + } + }; + + return mock; +} diff --git a/libs/test-utils-vitest/src/mock_kiosk.ts b/libs/test-utils-vitest/src/mock_kiosk.ts new file mode 100644 index 0000000000..5c6f41031a --- /dev/null +++ b/libs/test-utils-vitest/src/mock_kiosk.ts @@ -0,0 +1,15 @@ +import { Mocked, vi } from 'vitest'; + +export type MockKiosk = Mocked; + +/** + * Builds a `Kiosk` instance with mock methods. + */ +export function mockKiosk(): MockKiosk { + return { + quit: vi.fn(), + log: vi.fn(), + captureScreenshot: vi.fn().mockResolvedValue(Uint8Array.of()), + showOpenDialog: vi.fn(), + }; +} diff --git a/libs/test-utils-vitest/src/mock_of.ts b/libs/test-utils-vitest/src/mock_of.ts new file mode 100644 index 0000000000..c68da61be8 --- /dev/null +++ b/libs/test-utils-vitest/src/mock_of.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { MockedFunction } from 'vitest'; + +/** + * Returns a properly-typed mock for an already-mocked function. + * + * @example + * + * import * as fs from 'node:fs' + * vi.mock('node:fs') + * const readFileMock = mockOf(fs.readFile) + * readFileMock.mockImplementation(…) + */ +export function mockOf any>( + fn: T +): MockedFunction { + return fn as MockedFunction; +} diff --git a/libs/test-utils-vitest/src/mock_use_audio_controls.ts b/libs/test-utils-vitest/src/mock_use_audio_controls.ts new file mode 100644 index 0000000000..44be98d803 --- /dev/null +++ b/libs/test-utils-vitest/src/mock_use_audio_controls.ts @@ -0,0 +1,17 @@ +import { AudioControls } from '@votingworks/types'; +import { Mocked, vi } from 'vitest'; + +export function mockUseAudioControls(): Mocked { + return { + decreasePlaybackRate: vi.fn(), + decreaseVolume: vi.fn(), + increasePlaybackRate: vi.fn(), + increaseVolume: vi.fn(), + reset: vi.fn(), + replay: vi.fn(), + setControlsEnabled: vi.fn(), + setIsEnabled: vi.fn(), + toggleEnabled: vi.fn(), + togglePause: vi.fn(), + }; +} diff --git a/libs/test-utils-vitest/tsconfig.build.json b/libs/test-utils-vitest/tsconfig.build.json new file mode 100644 index 0000000000..5a67555159 --- /dev/null +++ b/libs/test-utils-vitest/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"], + "exclude": ["**/*.test.ts", "**/*.test.tsx"], + "compilerOptions": { + "noEmit": false, + "rootDir": "src", + "outDir": "build", + "declaration": true + }, + "references": [ + { "path": "../basics/tsconfig.build.json" }, + { "path": "../eslint-plugin-vx/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" } + ] +} diff --git a/libs/test-utils-vitest/tsconfig.json b/libs/test-utils-vitest/tsconfig.json new file mode 100644 index 0000000000..4da5c13aaa --- /dev/null +++ b/libs/test-utils-vitest/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.shared.json", + "include": ["src"], + "exclude": [], + "compilerOptions": { + "module": "node16", + "composite": true, + "noEmit": true, + "esModuleInterop": true, + "lib": ["dom", "dom.iterable", "esnext"], + "skipLibCheck": true, + "noUncheckedIndexedAccess": true + }, + "references": [ + { "path": "../basics/tsconfig.build.json" }, + { "path": "../eslint-plugin-vx/tsconfig.build.json" }, + { "path": "../types/tsconfig.build.json" } + ] +} diff --git a/libs/test-utils-vitest/vitest.config.ts b/libs/test-utils-vitest/vitest.config.ts new file mode 100644 index 0000000000..d1ec6cc46f --- /dev/null +++ b/libs/test-utils-vitest/vitest.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + thresholds: { + lines: 84, + branches: 79, + }, + provider: 'istanbul', + include: ['src/**/*.ts'], + exclude: [ + 'src/**/*.test.ts', + 'src/advance_timers.ts', + 'src/mock_of.ts', + 'src/mock_kiosk.ts', + 'src/mock_use_audio_controls.ts', + ], + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c7939026c..294ba6e6bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5241,6 +5241,55 @@ importers: specifier: 29.1.1 version: 29.1.1(@babel/core@7.22.9)(@jest/types@29.6.1)(esbuild@0.18.17)(jest@29.6.2)(typescript@5.6.2) + libs/test-utils-vitest: + dependencies: + '@testing-library/react': + specifier: ^15.0.7 + version: 15.0.7(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@votingworks/basics': + specifier: workspace:* + version: link:../basics + '@votingworks/types': + specifier: workspace:* + version: link:../types + chalk: + specifier: 4.1.2 + version: 4.1.2 + jest-diff: + specifier: ^29.6.2 + version: 29.6.2 + react: + specifier: 18.3.1 + version: 18.3.1 + vitest: + specifier: ^2.1.8 + version: 2.1.8(@types/node@20.16.0) + devDependencies: + '@types/kiosk-browser': + specifier: workspace:* + version: link:../@types/kiosk-browser + '@types/node': + specifier: 20.16.0 + version: 20.16.0 + '@types/react': + specifier: 18.3.3 + version: 18.3.3 + '@vitest/coverage-istanbul': + specifier: ^2.1.8 + version: 2.1.8(vitest@2.1.8) + eslint-plugin-vx: + specifier: workspace:* + version: link:../eslint-plugin-vx + is-ci-cli: + specifier: 2.2.0 + version: 2.2.0 + lint-staged: + specifier: 11.0.0 + version: 11.0.0 + sort-package-json: + specifier: ^1.50.0 + version: 1.53.1 + libs/types: dependencies: '@antongolub/iso8601': @@ -5879,6 +5928,11 @@ packages: resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} engines: {node: '>=6.9.0'} + /@babel/compat-data@7.26.3: + resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/core@7.20.12: resolution: {integrity: sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==} engines: {node: '>=6.9.0'} @@ -5924,6 +5978,29 @@ packages: transitivePeerDependencies: - supports-color + /@babel/core@7.26.0: + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.23.2(supports-color@5.5.0) + '@babel/types': 7.26.0 + convert-source-map: 2.0.0 + debug: 4.3.4(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/generator@7.22.9: resolution: {integrity: sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==} engines: {node: '>=6.9.0'} @@ -5973,6 +6050,17 @@ packages: lru-cache: 5.1.1 semver: 6.3.1 + /@babel/helper-compilation-targets@7.25.9: + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.26.3 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.3 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: true + /@babel/helper-create-class-features-plugin@7.22.10(@babel/core@7.22.9): resolution: {integrity: sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==} engines: {node: '>=6.9.0'} @@ -6079,6 +6167,16 @@ packages: dependencies: '@babel/types': 7.22.10 + /@babel/helper-module-imports@7.25.9: + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/traverse': 7.23.2(supports-color@5.5.0) + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-module-transforms@7.22.9(@babel/core@7.20.12): resolution: {integrity: sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==} engines: {node: '>=6.9.0'} @@ -6106,6 +6204,20 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.5 + /@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0): + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.23.2(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-optimise-call-expression@7.22.5: resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} engines: {node: '>=6.9.0'} @@ -6195,6 +6307,11 @@ packages: resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==} engines: {node: '>=6.9.0'} + /@babel/helper-validator-option@7.25.9: + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-wrap-function@7.22.10: resolution: {integrity: sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==} engines: {node: '>=6.9.0'} @@ -6214,6 +6331,14 @@ packages: transitivePeerDependencies: - supports-color + /@babel/helpers@7.26.0: + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + dev: true + /@babel/highlight@7.22.5: resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} engines: {node: '>=6.9.0'} @@ -7552,6 +7677,14 @@ packages: requiresBuild: true optional: true + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + optional: true + /@esbuild/android-arm64@0.17.11: resolution: {integrity: sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==} engines: {node: '>=12'} @@ -7577,6 +7710,14 @@ packages: requiresBuild: true optional: true + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-arm@0.17.11: resolution: {integrity: sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==} engines: {node: '>=12'} @@ -7602,6 +7743,14 @@ packages: requiresBuild: true optional: true + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-x64@0.17.11: resolution: {integrity: sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==} engines: {node: '>=12'} @@ -7627,6 +7776,14 @@ packages: requiresBuild: true optional: true + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + /@esbuild/darwin-arm64@0.17.11: resolution: {integrity: sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==} engines: {node: '>=12'} @@ -7652,6 +7809,14 @@ packages: requiresBuild: true optional: true + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + /@esbuild/darwin-x64@0.17.11: resolution: {integrity: sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==} engines: {node: '>=12'} @@ -7677,6 +7842,14 @@ packages: requiresBuild: true optional: true + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + /@esbuild/freebsd-arm64@0.17.11: resolution: {integrity: sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==} engines: {node: '>=12'} @@ -7702,6 +7875,14 @@ packages: requiresBuild: true optional: true + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + /@esbuild/freebsd-x64@0.17.11: resolution: {integrity: sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==} engines: {node: '>=12'} @@ -7727,6 +7908,14 @@ packages: requiresBuild: true optional: true + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + /@esbuild/linux-arm64@0.17.11: resolution: {integrity: sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==} engines: {node: '>=12'} @@ -7752,6 +7941,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-arm@0.17.11: resolution: {integrity: sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==} engines: {node: '>=12'} @@ -7777,6 +7974,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-ia32@0.17.11: resolution: {integrity: sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==} engines: {node: '>=12'} @@ -7802,6 +8007,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-loong64@0.17.11: resolution: {integrity: sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==} engines: {node: '>=12'} @@ -7827,6 +8040,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-mips64el@0.17.11: resolution: {integrity: sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==} engines: {node: '>=12'} @@ -7852,6 +8073,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-ppc64@0.17.11: resolution: {integrity: sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==} engines: {node: '>=12'} @@ -7877,6 +8106,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-riscv64@0.17.11: resolution: {integrity: sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==} engines: {node: '>=12'} @@ -7902,6 +8139,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-s390x@0.17.11: resolution: {integrity: sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==} engines: {node: '>=12'} @@ -7927,6 +8172,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-x64@0.17.11: resolution: {integrity: sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==} engines: {node: '>=12'} @@ -7952,6 +8205,14 @@ packages: requiresBuild: true optional: true + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/netbsd-x64@0.17.11: resolution: {integrity: sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==} engines: {node: '>=12'} @@ -7977,6 +8238,14 @@ packages: requiresBuild: true optional: true + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + /@esbuild/openbsd-x64@0.17.11: resolution: {integrity: sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==} engines: {node: '>=12'} @@ -8002,6 +8271,14 @@ packages: requiresBuild: true optional: true + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + /@esbuild/sunos-x64@0.17.11: resolution: {integrity: sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==} engines: {node: '>=12'} @@ -8027,6 +8304,14 @@ packages: requiresBuild: true optional: true + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + /@esbuild/win32-arm64@0.17.11: resolution: {integrity: sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==} engines: {node: '>=12'} @@ -8052,6 +8337,14 @@ packages: requiresBuild: true optional: true + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-ia32@0.17.11: resolution: {integrity: sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==} engines: {node: '>=12'} @@ -8077,6 +8370,14 @@ packages: requiresBuild: true optional: true + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-x64@0.17.11: resolution: {integrity: sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==} engines: {node: '>=12'} @@ -8102,6 +8403,14 @@ packages: requiresBuild: true optional: true + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -8502,7 +8811,7 @@ packages: resolution: {integrity: sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 callsites: 3.1.0 graceful-fs: 4.2.11 @@ -8619,6 +8928,9 @@ packages: /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + /@jridgewell/trace-mapping@0.3.18: resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} dependencies: @@ -9181,27 +9493,160 @@ packages: resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} dev: false - /@rollup/pluginutils@4.2.1: - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: true + /@rollup/pluginutils@4.2.1: + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: true + + /@rollup/pluginutils@5.0.3: + resolution: {integrity: sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: true + + /@rollup/rollup-android-arm-eabi@4.28.1: + resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-android-arm64@4.28.1: + resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-arm64@4.28.1: + resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-x64@4.28.1: + resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-freebsd-arm64@4.28.1: + resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + + /@rollup/rollup-freebsd-x64@4.28.1: + resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.28.1: + resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.28.1: + resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.28.1: + resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.28.1: + resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-loongarch64-gnu@4.28.1: + resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.28.1: + resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.28.1: + resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.28.1: + resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.28.1: + resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.28.1: + resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.28.1: + resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.28.1: + resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true - /@rollup/pluginutils@5.0.3: - resolution: {integrity: sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@types/estree': 1.0.5 - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: true + /@rollup/rollup-win32-x64-msvc@4.28.1: + resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true /@rooks/use-interval@4.5.0(react@18.3.1): resolution: {integrity: sha512-As0DueIAGLJLYATKPPOCDGqoIlwbhPAcYP14TNTHaAj9/ODdvUYFXAP3jFCRzDNpjXCIgSe4oBuzVVmM526n+Q==} @@ -10249,7 +10694,7 @@ packages: resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} engines: {node: '>=18'} dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.26.2 '@babel/runtime': 7.23.9 '@types/aria-query': 5.0.1 aria-query: 5.3.0 @@ -10673,7 +11118,9 @@ packages: /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true + + /@types/estree@1.0.6: + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} /@types/express-serve-static-core@4.17.31: resolution: {integrity: sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==} @@ -11464,6 +11911,80 @@ packages: - supports-color dev: true + /@vitest/coverage-istanbul@2.1.8(vitest@2.1.8): + resolution: {integrity: sha512-cSaCd8KcWWvgDwEJSXm0NEWZ1YTiJzjicKHy+zOEbUm0gjbbkz+qJf1p8q71uBzSlS7vdnZA8wRLeiwVE3fFTA==} + peerDependencies: + vitest: 2.1.8 + dependencies: + '@istanbuljs/schema': 0.1.3 + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magicast: 0.3.5 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.8(@types/node@20.16.0) + transitivePeerDependencies: + - supports-color + dev: true + + /@vitest/expect@2.1.8: + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} + dependencies: + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + tinyrainbow: 1.2.0 + + /@vitest/mocker@2.1.8(vite@5.4.11): + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + dependencies: + '@vitest/spy': 2.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.17 + vite: 5.4.11(@types/node@20.16.0) + + /@vitest/pretty-format@2.1.8: + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} + dependencies: + tinyrainbow: 1.2.0 + + /@vitest/runner@2.1.8: + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} + dependencies: + '@vitest/utils': 2.1.8 + pathe: 1.1.2 + + /@vitest/snapshot@2.1.8: + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} + dependencies: + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.17 + pathe: 1.1.2 + + /@vitest/spy@2.1.8: + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} + dependencies: + tinyspy: 3.0.2 + + /@vitest/utils@2.1.8: + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} + dependencies: + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + /@votingworks/qrdetect@1.0.1: resolution: {integrity: sha512-Qk2i4NQoQy1tbkX1ADPZecXLDzBC0gaL2DcJUsOL90KXjP1lUE47UgmSWMRIlNNYu5iivpbwKA9Kl7sahgbSxg==} requiresBuild: true @@ -12046,6 +12567,10 @@ packages: object-is: 1.1.5 util: 0.12.4 + /assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + /assign-symbols@1.0.0: resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} engines: {node: '>=0.10.0'} @@ -12176,7 +12701,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/template': 7.22.5 - '@babel/types': 7.22.10 + '@babel/types': 7.26.0 '@types/babel__core': 7.20.1 '@types/babel__traverse': 7.20.1 @@ -12544,6 +13069,17 @@ packages: node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) + /browserslist@4.24.3: + resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001689 + electron-to-chromium: 1.5.74 + node-releases: 2.0.19 + update-browserslist-db: 1.1.1(browserslist@4.24.3) + dev: true + /bs-logger@0.2.6: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} @@ -12639,6 +13175,10 @@ packages: yargs-parser: 20.2.9 dev: true + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + /cacache@12.0.4: resolution: {integrity: sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==} dependencies: @@ -12718,6 +13258,10 @@ packages: /caniuse-lite@1.0.30001519: resolution: {integrity: sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==} + /caniuse-lite@1.0.30001689: + resolution: {integrity: sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==} + dev: true + /canvas@2.11.2: resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} engines: {node: '>=6'} @@ -12736,6 +13280,16 @@ packages: hasBin: true dev: true + /chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -12771,6 +13325,10 @@ packages: resolution: {integrity: sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==} engines: {node: '>=12.20'} + /check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + /cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} dependencies: @@ -13473,7 +14031,6 @@ packages: optional: true dependencies: ms: 2.1.3 - dev: false /decamelize-keys@1.1.0: resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} @@ -13533,6 +14090,10 @@ packages: type-detect: 4.0.8 dev: false + /deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -13833,6 +14394,10 @@ packages: /electron-to-chromium@1.4.485: resolution: {integrity: sha512-1ndQ5IBNEnFirPwvyud69GHL+31FkE09gH/CJ6m3KCbkx3i0EVOrjwz4UNxRmN9H8OVHbC6vMRZGN1yCvjSs9w==} + /electron-to-chromium@1.5.74: + resolution: {integrity: sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==} + dev: true + /elliptic@6.5.7: resolution: {integrity: sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==} dependencies: @@ -13985,6 +14550,9 @@ packages: resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} dev: true + /es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} engines: {node: '>= 0.4'} @@ -14123,10 +14691,45 @@ packages: '@esbuild/win32-ia32': 0.21.2 '@esbuild/win32-x64': 0.21.2 + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} + /escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + dev: true + /escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -14632,6 +15235,11 @@ packages: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -14721,6 +15329,10 @@ packages: resolution: {integrity: sha512-yWnriYB4e8G54M5/fAFj7rCIBiKs1HAACaY13kCz6Ku0dezjS9aMcfcdVK2X8Tv2tEV1BPz/wKfQ7WA4S/d8aA==} dev: true + /expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + /expect@29.6.2: resolution: {integrity: sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -15465,6 +16077,18 @@ packages: path-scurry: 1.10.1 dev: true + /glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + dev: true + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -16544,23 +17168,40 @@ packages: resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} engines: {node: '>=8'} + /istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + /istanbul-lib-instrument@5.2.1: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: '@babel/core': 7.22.9 - '@babel/parser': 7.22.7 + '@babel/parser': 7.26.2 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.1 transitivePeerDependencies: - supports-color + /istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + dependencies: + '@babel/core': 7.26.0 + '@babel/parser': 7.26.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + dev: true + /istanbul-lib-report@3.0.1: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} dependencies: - istanbul-lib-coverage: 3.2.0 + istanbul-lib-coverage: 3.2.2 make-dir: 4.0.0 supports-color: 7.2.0 @@ -16574,6 +17215,17 @@ packages: transitivePeerDependencies: - supports-color + /istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.4(supports-color@5.5.0) + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + dev: true + /istanbul-reports@3.1.6: resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} engines: {node: '>=8'} @@ -16581,6 +17233,14 @@ packages: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + /istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true + /jackspeak@2.2.3: resolution: {integrity: sha512-pF0kfjmg8DJLxDrizHoCZGUFz4P4czQ3HyfW4BU0ffebYkzAVlBywp5zaxW/TM+r0sGbmrQdi8EQQVTJFxnGsQ==} engines: {node: '>=14'} @@ -16590,6 +17250,14 @@ packages: '@pkgjs/parseargs': 0.11.0 dev: true + /jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + /jake@10.8.5: resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} engines: {node: '>=10'} @@ -17403,7 +18071,7 @@ packages: execa: 5.1.1 listr2: 3.14.0(enquirer@2.3.6) log-symbols: 4.1.0 - micromatch: 4.0.5 + micromatch: 4.0.8 normalize-path: 3.0.0 please-upgrade-node: 3.2.0 string-argv: 0.3.1 @@ -17567,11 +18235,18 @@ packages: commander: 9.5.0 dev: true + /loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + /lru-cache@10.0.1: resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} engines: {node: 14 || >=16.14} dev: true + /lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + dev: true + /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: @@ -17603,6 +18278,11 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + /magic-string@0.30.2: resolution: {integrity: sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==} engines: {node: '>=12'} @@ -17610,6 +18290,14 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + source-map-js: 1.2.0 + dev: true + /make-cancellable-promise@1.3.2: resolution: {integrity: sha512-GCXh3bq/WuMbS+Ky4JBPW1hYTOU+znU+Q5m9Pu+pI8EoUqIHk9+tviOKC6/qhHh8C4/As3tzJ69IF32kdz85ww==} dev: false @@ -17965,6 +18653,11 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dev: true + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -18234,6 +18927,10 @@ packages: /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} + /node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + dev: true + /nodemon@3.1.7: resolution: {integrity: sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==} engines: {node: '>=10'} @@ -18599,6 +19296,10 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + /package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + dev: true + /pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} dev: true @@ -18717,6 +19418,14 @@ packages: minipass: 7.0.3 dev: true + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + dev: true + /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -18750,6 +19459,13 @@ packages: resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} dev: true + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + /pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + /pbkdf2@3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} @@ -18818,6 +19534,9 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -18974,6 +19693,14 @@ packages: picocolors: 1.0.0 source-map-js: 1.2.0 + /postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + /prebuild-install@7.1.1: resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} engines: {node: '>=10'} @@ -20267,6 +20994,34 @@ packages: fsevents: 2.3.3 dev: true + /rollup@4.28.1: + resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.28.1 + '@rollup/rollup-android-arm64': 4.28.1 + '@rollup/rollup-darwin-arm64': 4.28.1 + '@rollup/rollup-darwin-x64': 4.28.1 + '@rollup/rollup-freebsd-arm64': 4.28.1 + '@rollup/rollup-freebsd-x64': 4.28.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.28.1 + '@rollup/rollup-linux-arm-musleabihf': 4.28.1 + '@rollup/rollup-linux-arm64-gnu': 4.28.1 + '@rollup/rollup-linux-arm64-musl': 4.28.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.28.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.28.1 + '@rollup/rollup-linux-riscv64-gnu': 4.28.1 + '@rollup/rollup-linux-s390x-gnu': 4.28.1 + '@rollup/rollup-linux-x64-gnu': 4.28.1 + '@rollup/rollup-linux-x64-musl': 4.28.1 + '@rollup/rollup-win32-arm64-msvc': 4.28.1 + '@rollup/rollup-win32-ia32-msvc': 4.28.1 + '@rollup/rollup-win32-x64-msvc': 4.28.1 + fsevents: 2.3.3 + /rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} dev: false @@ -20542,6 +21297,9 @@ packages: object-inspect: 1.13.2 dev: false + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -20677,6 +21435,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + /source-map-resolve@0.5.3: resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} deprecated: See https://github.com/lydell/source-map-resolve#deprecated @@ -20772,6 +21534,9 @@ packages: dependencies: escape-string-regexp: 2.0.0 + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + /standardized-audio-context@25.3.63: resolution: {integrity: sha512-6hB0GbZOx3Q0npxg1QMZTN/jioom3jMkVcr3EqZbwywytqykevtuCgZuaUJqntTzv7q2mEW9KsCUtFCpbWpCkw==} dependencies: @@ -20792,6 +21557,9 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + /std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + /store2@2.14.2: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true @@ -21344,6 +22112,15 @@ packages: glob: 7.2.3 minimatch: 3.1.2 + /test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + dev: true + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -21382,6 +22159,24 @@ packages: /tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + /tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + /tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + + /tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + /tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + /tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + /tippy.js@6.3.7: resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} dependencies: @@ -21897,6 +22692,17 @@ packages: escalade: 3.1.1 picocolors: 1.0.0 + /update-browserslist-db@1.1.1(browserslist@4.24.3): + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.24.3 + escalade: 3.2.0 + picocolors: 1.1.1 + dev: true + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -22062,7 +22868,7 @@ packages: resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 @@ -22144,6 +22950,27 @@ packages: teex: 1.0.1 dev: false + /vite-node@2.1.8(@types/node@20.16.0): + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.7 + es-module-lexer: 1.5.4 + pathe: 1.1.2 + vite: 5.4.11(@types/node@20.16.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + /vite@4.5.2(@types/node@20.16.0): resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==} engines: {node: ^14.18.0 || >=16.0.0} @@ -22180,6 +23007,101 @@ packages: fsevents: 2.3.3 dev: true + /vite@5.4.11(@types/node@20.16.0): + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.16.0 + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.28.1 + optionalDependencies: + fsevents: 2.3.3 + + /vitest@2.1.8(@types/node@20.16.0): + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 20.16.0 + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.4.11) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + debug: 4.3.7 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.1 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.4.11(@types/node@20.16.0) + vite-node: 2.1.8(@types/node@20.16.0) + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + /vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} dev: false @@ -22413,6 +23335,14 @@ packages: dependencies: isexe: 2.0.0 + /why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + /wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: diff --git a/vxsuite.code-workspace b/vxsuite.code-workspace index 081f6a2013..2897cec62e 100644 --- a/vxsuite.code-workspace +++ b/vxsuite.code-workspace @@ -192,6 +192,10 @@ "name": "libs/test-utils", "path": "libs/test-utils" }, + { + "name": "libs/test-utils-vitest", + "path": "libs/test-utils-vitest" + }, { "name": "libs/types", "path": "libs/types"