diff --git a/.github/workflows/js_sdk_tests.yml b/.github/workflows/js_sdk_tests.yml index d5725246c0..c374eaa853 100644 --- a/.github/workflows/js_sdk_tests.yml +++ b/.github/workflows/js_sdk_tests.yml @@ -8,7 +8,7 @@ on: type: string default: '' node-only: - description: 'Run only the Node legs (skip Bun, Deno, and Cloudflare)' + description: 'Run only the Node legs (skip Bun, Deno, Cloudflare, and the browser)' required: false type: boolean default: false @@ -29,14 +29,17 @@ jobs: strategy: fail-fast: false # With node-only (set by staging callers) the matrix collapses to the - # Node legs: the Bun/Deno/Cloudflare legs re-run suites the Node legs - # already cover and add sandbox/build load without extra backend - # signal, so the other runtimes are exercised against production only. + # Node legs: the Bun/Deno/Cloudflare/browser legs re-run suites the Node + # legs already cover and add sandbox/build load, so the other runtimes + # are exercised against production only. Note this does drop backend + # signal for the browser leg specifically — it's the only one that + # depends on the API's CORS headers — so a staging-only CORS regression + # surfaces on the production run rather than before merge. matrix: include: >- ${{ inputs.node-only && fromJSON('[{"runtime": "node", "os": "ubuntu-22.04"}, {"runtime": "node", "os": "windows-latest"}]') - || fromJSON('[{"runtime": "node", "os": "ubuntu-22.04"}, {"runtime": "node", "os": "windows-latest"}, {"runtime": "bun", "os": "ubuntu-22.04"}, {"runtime": "deno", "os": "ubuntu-22.04"}, {"runtime": "cloudflare", "os": "ubuntu-22.04"}, {"runtime": "cloudflare-deploy", "os": "ubuntu-22.04"}]') }} + || fromJSON('[{"runtime": "node", "os": "ubuntu-22.04"}, {"runtime": "node", "os": "windows-latest"}, {"runtime": "bun", "os": "ubuntu-22.04"}, {"runtime": "deno", "os": "ubuntu-22.04"}, {"runtime": "cloudflare", "os": "ubuntu-22.04"}, {"runtime": "cloudflare-deploy", "os": "ubuntu-22.04"}, {"runtime": "browser", "os": "ubuntu-22.04"}]') }} runs-on: ${{ matrix.os }} steps: - name: Checkout repository @@ -72,18 +75,17 @@ jobs: run: | pnpm install --frozen-lockfile - # Only the Node runtime runs the vitest `browser` project, which drives - # Chromium through Playwright. + # Only the browser leg needs Chromium, which Playwright drives. - name: Get Playwright version - if: matrix.runtime == 'node' + if: matrix.runtime == 'browser' id: playwright-version run: echo "version=$(node -p "require('playwright/package.json').version")" >> "$GITHUB_OUTPUT" - name: Cache Playwright browsers - if: matrix.runtime == 'node' + if: matrix.runtime == 'browser' uses: actions/cache@v4 with: - path: ${{ matrix.os == 'windows-latest' && '~/AppData/Local/ms-playwright' || '~/.cache/ms-playwright' }} + path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} # The unit bundle test and the Cloudflare deploy config fail in CI when @@ -139,3 +141,12 @@ jobs: env: E2B_API_KEY: ${{ secrets.E2B_API_KEY }} E2B_DOMAIN: ${{ inputs.E2B_DOMAIN }} + + # Full unit + connectionConfig suite inside Chromium (vitest browser mode); + # `pretest:browser` installs the browser and its system dependencies. + - name: Run test suite in the browser + if: matrix.runtime == 'browser' + run: pnpm test:browser + env: + E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + E2B_DOMAIN: ${{ inputs.E2B_DOMAIN }} diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index b11d1b94ff..fc51fd9360 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -35,7 +35,8 @@ "generate:volume-api": "openapi-typescript ../../spec/openapi-volumecontent.yml -x api_key --array-length --alphabetize --output src/volume/schema.gen.ts", "generate:mcp": "json2ts -i ./../../spec/mcp-server.json -o src/sandbox/mcp.d.ts --unreachableDefinitions --style.singleQuote --no-style.semi", "check-deps": "knip", - "pretest": "npx playwright install --with-deps chromium", + "pretest:browser": "npx playwright install --with-deps chromium", + "test:browser": "vitest run --config tests/runtimes/browser/vitest.config.mts", "test:bun": "bunx --bun vitest run --project unit --project connectionConfig --project template", "test:cf": "vitest run --config tests/runtimes/cloudflare/vitest.config.mts", "test:cf:deploy": "vitest run --config tests/runtimes/cloudflare-deploy/vitest.config.mts", @@ -47,13 +48,9 @@ "devDependencies": { "@cloudflare/vitest-pool-workers": "^0.18.7", "@redocly/cli": "2.39.0", - "@testing-library/react": "^16.2.0", "@types/node": "^20.19.19", "@types/platform": "^1.3.6", - "@types/react": "^19.2.0", - "@types/react-dom": "^19.2.0", "@typescript/native": "npm:typescript@^7.0.2", - "@vitejs/plugin-react": "^4.3.4", "@vitest/browser": "^4.1.10", "@vitest/browser-playwright": "^4.1.10", "dotenv": "^16.4.5", @@ -64,12 +61,9 @@ "npm-run-all": "^4.1.5", "openapi-typescript": "^7.9.1", "playwright": "^1.55.1", - "react": "^19.2.0", - "react-dom": "^19.2.0", "tsdown": "^0.22.3", "typescript": "npm:@typescript/typescript6@^6.0.2", "vitest": "^4.1.10", - "vitest-browser-react": "^2.2.0", "wrangler": "^4.113.0" }, "files": [ diff --git a/packages/js-sdk/tests/api/http2.test.ts b/packages/js-sdk/tests/api/http2.test.ts index 262bf96bf3..3238e16b72 100644 --- a/packages/js-sdk/tests/api/http2.test.ts +++ b/packages/js-sdk/tests/api/http2.test.ts @@ -3,8 +3,6 @@ import { afterEach, expect, test, vi } from 'vitest' afterEach(() => { vi.restoreAllMocks() vi.resetModules() - vi.doUnmock('undici') - vi.doUnmock('../../src/utils') delete process.env.E2B_API_CONNECTIONS delete process.env.E2B_API_INFLIGHT_REQUESTS }) diff --git a/packages/js-sdk/tests/api/list.test.ts b/packages/js-sdk/tests/api/list.test.ts index 3960b9fffd..745d616afe 100644 --- a/packages/js-sdk/tests/api/list.test.ts +++ b/packages/js-sdk/tests/api/list.test.ts @@ -1,5 +1,4 @@ import { assert } from 'vitest' -import { randomUUID } from 'crypto' import { Sandbox, SandboxInfo } from '../../src' import { sandboxTest, isDebug } from '../setup.js' @@ -20,7 +19,7 @@ sandboxTest.skipIf(isDebug)( ) sandboxTest.skipIf(isDebug)('list sandboxes with filter', async () => { - const uniqueId = randomUUID() + const uniqueId = crypto.randomUUID() const extraSbx = await Sandbox.create({ metadata: { uniqueId } }) try { @@ -237,7 +236,7 @@ sandboxTest.skipIf(isDebug)( ) sandboxTest.skipIf(isDebug)('list sandboxes with filter', async () => { - const uniqueId = randomUUID() + const uniqueId = crypto.randomUUID() const extraSbx = await Sandbox.create({ metadata: { uniqueId } }) try { diff --git a/packages/js-sdk/tests/connectionConfig.test.ts b/packages/js-sdk/tests/connectionConfig.test.ts index 1af6edc8c4..843c6b1cc7 100644 --- a/packages/js-sdk/tests/connectionConfig.test.ts +++ b/packages/js-sdk/tests/connectionConfig.test.ts @@ -4,6 +4,7 @@ import { setupRequestController, wrapStreamWithConnectionCleanup, } from '../src/connectionConfig' +import { runtime } from '../src/utils' // Store original env vars to restore after tests let originalEnv: { [key: string]: string | undefined } @@ -61,21 +62,26 @@ test('api_url has correct priority', () => { assert.equal(config.apiUrl, 'http://localhost:8080') }) -test('sandbox_url defaults to stable sandbox host in production', () => { - delete process.env.E2B_SANDBOX_URL - delete process.env.E2B_DOMAIN - delete process.env.E2B_DEBUG - - const config = new ConnectionConfig() - - assert.equal( - config.getSandboxUrl('sbx-test', { - sandboxDomain: 'e2b.app', - envdPort: 49983, - }), - 'https://sandbox.e2b.app' - ) -}) +// The stable host is deliberately not used in a browser (CORS); the browser +// side of this branch is asserted in connectionConfig.browser.test.ts. +test.skipIf(runtime === 'browser')( + 'sandbox_url defaults to stable sandbox host in production', + () => { + delete process.env.E2B_SANDBOX_URL + delete process.env.E2B_DOMAIN + delete process.env.E2B_DEBUG + + const config = new ConnectionConfig() + + assert.equal( + config.getSandboxUrl('sbx-test', { + sandboxDomain: 'e2b.app', + envdPort: 49983, + }), + 'https://sandbox.e2b.app' + ) + } +) test('sandbox_direct_url keeps per-sandbox host in production', () => { delete process.env.E2B_SANDBOX_URL diff --git a/packages/js-sdk/tests/envd/http2.test.ts b/packages/js-sdk/tests/envd/http2.test.ts index 9ad6e9ee0f..d1c022e891 100644 --- a/packages/js-sdk/tests/envd/http2.test.ts +++ b/packages/js-sdk/tests/envd/http2.test.ts @@ -3,8 +3,6 @@ import { afterEach, expect, test, vi } from 'vitest' afterEach(() => { vi.restoreAllMocks() vi.resetModules() - vi.doUnmock('undici') - vi.doUnmock('../../src/utils') delete process.env.E2B_ENVD_RPC_CONNECTIONS delete process.env.E2B_ENVD_INFLIGHT_REQUESTS delete process.env.E2B_ENVD_RPC_INFLIGHT_REQUESTS diff --git a/packages/js-sdk/tests/runtime.test.ts b/packages/js-sdk/tests/runtime.test.ts index af2919c7a3..a8d1aaf7d7 100644 --- a/packages/js-sdk/tests/runtime.test.ts +++ b/packages/js-sdk/tests/runtime.test.ts @@ -1,12 +1,13 @@ import { afterEach, expect, test, vi } from 'vitest' -// The unit project also runs under Bun, Deno, and Cloudflare's workerd, where -// the host runtime's own unstubbable marker (globalThis.Bun / globalThis.Deno / -// the Cloudflare-Workers user agent) correctly wins detection — these -// scenarios only exist on a Node host. +// The unit project also runs under Bun, Deno, Cloudflare's workerd, and a real +// browser, where the host runtime's own unstubbable marker (globalThis.Bun / +// globalThis.Deno / the Cloudflare-Workers user agent / `window`) correctly +// wins detection — these scenarios only exist on a Node host. const isNodeHost = typeof (globalThis as any).Bun === 'undefined' && typeof (globalThis as any).Deno === 'undefined' && + typeof (globalThis as any).window === 'undefined' && (globalThis as any).navigator?.userAgent !== 'Cloudflare-Workers' afterEach(() => { diff --git a/packages/js-sdk/tests/runtimes/browser/importMeta.d.ts b/packages/js-sdk/tests/runtimes/browser/importMeta.d.ts new file mode 100644 index 0000000000..b9a3c72f76 --- /dev/null +++ b/packages/js-sdk/tests/runtimes/browser/importMeta.d.ts @@ -0,0 +1,7 @@ +// Vite exposes the runner's `env` config on `import.meta.env`, which the +// browser suite reads instead of `process.env`. The canonical types live in +// `vite/client`, but vite is only a transitive dependency here (via vitest) and +// isn't resolvable from this package, so declare the shape the suite uses. +interface ImportMeta { + readonly env: Record +} diff --git a/packages/js-sdk/tests/runtimes/browser/noProcessGlobal.test.ts b/packages/js-sdk/tests/runtimes/browser/noProcessGlobal.test.ts new file mode 100644 index 0000000000..7c5237c3e6 --- /dev/null +++ b/packages/js-sdk/tests/runtimes/browser/noProcessGlobal.test.ts @@ -0,0 +1,50 @@ +import { assert, test } from 'vitest' + +import { template } from '../../template' + +// Bundlers don't give browser apps a `process` unless asked to, so the SDK has +// to be fully configurable through explicit options — `getEnvVar` +// (src/api/metadata.ts) returns '' when `process` is missing rather than +// throwing. The rest of this suite runs against the `process.env` shim from +// processEnv.setup.ts, which would mask a regression that reintroduces a bare +// `process` read on the create path, so this test drops the shim entirely and +// drives a real sandbox the way a browser app has to. +// +// The SDK is imported dynamically inside the test, after the shim is gone: a +// static import is evaluated at collection time, while the shim is still +// installed, which would let a top-level `process` read anywhere in the module +// graph pass here and still crash a browser app on import. +// +// Config comes from `import.meta.env` (where vitest puts the config's `env`), +// which is also how a Vite app would hand its own build-time values over. +const apiKey = import.meta.env.E2B_API_KEY +const domain = import.meta.env.E2B_DOMAIN || undefined +const isDebug = import.meta.env.E2B_DEBUG !== undefined + +test.skipIf(isDebug)( + 'drives a sandbox with no process global', + async () => { + const shim = Reflect.getOwnPropertyDescriptor(globalThis, 'process') + Reflect.deleteProperty(globalThis, 'process') + assert.equal(typeof process, 'undefined') + + try { + const { Sandbox } = await import('../../../src') + const sandbox = await Sandbox.create(template, { apiKey, domain }) + + try { + await sandbox.files.write('hello.txt', 'Hello World') + assert.equal(await sandbox.files.read('hello.txt'), 'Hello World') + + const result = await sandbox.commands.run('echo "from the browser"') + assert.equal(result.exitCode, 0) + assert.equal(result.stdout.trim(), 'from the browser') + } finally { + await sandbox.kill() + } + } finally { + if (shim) Reflect.defineProperty(globalThis, 'process', shim) + } + }, + 60_000 +) diff --git a/packages/js-sdk/tests/runtimes/browser/processEnv.setup.ts b/packages/js-sdk/tests/runtimes/browser/processEnv.setup.ts new file mode 100644 index 0000000000..b59a0a9a46 --- /dev/null +++ b/packages/js-sdk/tests/runtimes/browser/processEnv.setup.ts @@ -0,0 +1,15 @@ +// Browsers have no `process`: `getEnvVar` (src/api/metadata.ts) resolves to '' +// there, so E2B_* can only reach a browser app through whatever shim its +// bundler injects. Provide that shim — the same thing workerd's nodejs_compat +// gives the Cloudflare suite for free — so the shared suites can configure the +// SDK from the environment like they do on every other runtime. +// +// `process.env` aliases Vite's `import.meta.env`, which is where vitest puts +// the config's `env` and what `vi.stubEnv` writes to in browser mode, so env +// reads and stubbing both behave as they do on Node. +// +// Only `env` is defined: `process.release` must stay absent or the SDK would +// detect the runtime as 'node' instead of 'browser'. Tests that need a +// browser without any `process` at all delete it themselves — see +// noProcessGlobal.test.ts. +Object.assign(globalThis, { process: { env: import.meta.env } }) diff --git a/packages/js-sdk/tests/runtimes/browser/run.test.tsx b/packages/js-sdk/tests/runtimes/browser/run.test.tsx deleted file mode 100644 index dda6780c35..0000000000 --- a/packages/js-sdk/tests/runtimes/browser/run.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { expect, inject, test } from 'vitest' -import { render } from 'vitest-browser-react' -import React from 'react' -import { useEffect, useState } from 'react' - -import { Sandbox } from '../../../src' -import { template } from '../../template' - -function E2BTest() { - const [text, setText] = useState() - - useEffect(() => { - const getText = async () => { - const sandbox = await Sandbox.create(template, { - apiKey: inject('E2B_API_KEY'), - domain: inject('E2B_DOMAIN'), - }) - - try { - await sandbox.commands.run('echo "Hello World" > hello.txt') - const content = await sandbox.files.read('hello.txt') - setText(content) - } finally { - await sandbox.kill() - } - } - - getText() - }, []) - - return
{text}
-} -test('browser test', async () => { - const screen = await render() - await expect - .element(screen.getByText('Hello World'), { timeout: 30_000 }) - .toBeInTheDocument() -}, 40_000) diff --git a/packages/js-sdk/tests/runtimes/browser/vitest.config.mts b/packages/js-sdk/tests/runtimes/browser/vitest.config.mts new file mode 100644 index 0000000000..7d70d0cab5 --- /dev/null +++ b/packages/js-sdk/tests/runtimes/browser/vitest.config.mts @@ -0,0 +1,71 @@ +import { playwright } from '@vitest/browser-playwright' +import { config } from 'dotenv' +import { defineConfig } from 'vitest/config' + +// Real env vars win over `.env`, matching dotenv's own precedence. +const env = { ...config().parsed, ...process.env } + +// Config the shared suites read from the environment. Everything the SDK takes +// is `E2B_`-prefixed; ENABLE_VOLUME_TESTS gates the volume suite. Forwarded by +// name rather than as the whole environment, which would inline every host +// variable into JS served to the browser. +const testEnv = Object.fromEntries( + Object.entries(env).filter( + ([name]) => name.startsWith('E2B_') || name === 'ENABLE_VOLUME_TESTS' + ) +) as Record + +// Runs the unit + connectionConfig projects (same coverage as test:bun / +// test:deno / test:cf) inside a real Chromium via Playwright, against src. +// Nothing is skipped for being a browser: the suites that can't run here are +// Node-only rather than browser-hostile, and they're excluded below. Tests that +// read a response from a server inside the sandbox start a CORS-enabled one +// (`corsHttpServerCmd` in tests/setup.ts), the way a browser app's own server +// would be configured. +export default defineConfig({ + test: { + name: 'browser', + include: [ + 'tests/**/*.test.ts', + // Browser-only suite; tests/runtimes/** is otherwise excluded below. + 'tests/runtimes/browser/**/*.test.ts', + ], + exclude: [ + // Other runtimes' suites, which run under their own configs. This + // suite's own browser-only tests are re-included above; a new runtime + // directory needs adding here. + 'tests/runtimes/cloudflare*/**', + 'tests/template/**', + // Inspects the host-built dist/index.mjs via node:fs, which the browser + // can never see; the Node unit project keeps running it. + 'tests/bundle/**', + // Resolves the `undici`/`undici8` packages off `process.versions.node`. + // The browser never takes that path — `createRuntimeFetch` late-binds + // the global fetch outside Node — so there is nothing to cover here. + 'tests/undici.test.ts', + // These mock the API with msw's `setupServer`, whose `msw/node` entry + // pulls in node:http and can't be served to the browser. Porting them + // means `setupWorker` plus a service worker served from a public dir. + 'tests/sandbox/abortSignal.test.ts', + 'tests/volume/volume.test.ts', + ], + globals: false, + testTimeout: 30_000, + // A real browser has no `process`; the setup file shims `process.env` onto + // `import.meta.env`, where vitest puts `env` below, so the shared suites + // can read their config the way they do on every other runtime. + setupFiles: ['tests/runtimes/browser/processEnv.setup.ts'], + env: testEnv, + browser: { + enabled: true, + // Defaults to `isCI`, so set it explicitly for local runs too. + headless: true, + // These tests drive the SDK and never render anything, so a failure + // screenshot is a picture of a blank page. Off by default it would write + // one PNG per failed test into .vitest-attachments/. + screenshotFailures: false, + provider: playwright(), + instances: [{ browser: 'chromium' }], + }, + }, +}) diff --git a/packages/js-sdk/tests/sandbox/commands/kill.test.ts b/packages/js-sdk/tests/sandbox/commands/kill.test.ts index 4eddcc4460..036615169e 100644 --- a/packages/js-sdk/tests/sandbox/commands/kill.test.ts +++ b/packages/js-sdk/tests/sandbox/commands/kill.test.ts @@ -1,5 +1,5 @@ import { expect } from 'vitest' -import { ProcessExitError } from '../../../src/index.js' +import { CommandExitError } from '../../../src/index.js' import { sandboxTest } from '../../setup.js' @@ -10,7 +10,7 @@ sandboxTest('kill process', async ({ sandbox }) => { await sandbox.commands.kill(pid) await expect(sandbox.commands.run(`kill -0 ${pid}`)).rejects.toThrowError( - ProcessExitError + CommandExitError ) }) diff --git a/packages/js-sdk/tests/sandbox/files/read.test.ts b/packages/js-sdk/tests/sandbox/files/read.test.ts index 85b8f45b4e..d8315402c8 100644 --- a/packages/js-sdk/tests/sandbox/files/read.test.ts +++ b/packages/js-sdk/tests/sandbox/files/read.test.ts @@ -38,11 +38,12 @@ sandboxTest('read file as stream', async ({ sandbox }) => { await sandbox.files.write(filename, content) const stream = await sandbox.files.read(filename, { format: 'stream' }) - const chunks: Uint8Array[] = [] + const decoder = new TextDecoder() + let readContent = '' for await (const chunk of stream as unknown as AsyncIterable) { - chunks.push(chunk) + readContent += decoder.decode(chunk, { stream: true }) } - const readContent = Buffer.concat(chunks).toString('utf-8') + readContent += decoder.decode() assert.equal(readContent, content) }) @@ -71,56 +72,9 @@ sandboxTest('read empty file in all formats', async ({ sandbox }) => { const stream = await sandbox.files.read(filename, { format: 'stream' }) expect(stream).toBeInstanceOf(ReadableStream) - const chunks: Uint8Array[] = [] + let bytesRead = 0 for await (const chunk of stream as unknown as AsyncIterable) { - chunks.push(chunk) + bytesRead += chunk.length } - expect(Buffer.concat(chunks).length).toBe(0) -}) - -sandboxTest('read file as stream', async ({ sandbox }) => { - const filename = 'test_read_stream.txt' - const content = 'Streamed read content. '.repeat(10_000) - - await sandbox.files.write(filename, content) - const stream = await sandbox.files.read(filename, { format: 'stream' }) - - const chunks: Uint8Array[] = [] - for await (const chunk of stream as unknown as AsyncIterable) { - chunks.push(chunk) - } - const readContent = Buffer.concat(chunks).toString('utf-8') - assert.equal(readContent, content) -}) - -sandboxTest('read non-existing file as stream', async ({ sandbox }) => { - const filename = 'non_existing_file.txt' - - await expect( - sandbox.files.read(filename, { format: 'stream' }) - ).rejects.toThrowError(FileNotFoundError) -}) - -sandboxTest('read empty file in all formats', async ({ sandbox }) => { - const filename = 'empty-file-formats.txt' - await sandbox.commands.run(`touch ${filename}`) - - const text = await sandbox.files.read(filename, { format: 'text' }) - expect(text).toBe('') - - const bytes = await sandbox.files.read(filename, { format: 'bytes' }) - expect(bytes).toBeInstanceOf(Uint8Array) - expect(bytes.length).toBe(0) - - const blob = await sandbox.files.read(filename, { format: 'blob' }) - expect(blob).toBeInstanceOf(Blob) - expect(blob.size).toBe(0) - - const stream = await sandbox.files.read(filename, { format: 'stream' }) - expect(stream).toBeInstanceOf(ReadableStream) - const chunks: Uint8Array[] = [] - for await (const chunk of stream as unknown as AsyncIterable) { - chunks.push(chunk) - } - expect(Buffer.concat(chunks).length).toBe(0) + expect(bytesRead).toBe(0) }) diff --git a/packages/js-sdk/tests/sandbox/files/write.test.ts b/packages/js-sdk/tests/sandbox/files/write.test.ts index 61f046cfe0..5116006e3b 100644 --- a/packages/js-sdk/tests/sandbox/files/write.test.ts +++ b/packages/js-sdk/tests/sandbox/files/write.test.ts @@ -1,4 +1,3 @@ -import path from 'path' import { assert } from 'vitest' import { WriteEntry } from '../../../src/sandbox/filesystem' @@ -111,7 +110,7 @@ sandboxTest('write multiple files', async ({ sandbox }) => { const file = files[i] const info = infos[i] - assert.equal(info.name, path.basename(file.path)) + assert.equal(info.name, file.path.split('/').pop()) assert.equal(info.path, file.path) assert.equal(info.type, 'file') @@ -206,7 +205,7 @@ sandboxTest('writeFiles with multiple files', async ({ sandbox }) => { const file = files[i] const info = infos[i] - assert.equal(info.name, path.basename(file.path)) + assert.equal(info.name, file.path.split('/').pop()) assert.equal(info.path, `/home/user/${file.path}`) assert.equal(info.type, 'file') diff --git a/packages/js-sdk/tests/sandbox/git/helpers.ts b/packages/js-sdk/tests/sandbox/git/helpers.ts index 797682f05b..cb19c1d82a 100644 --- a/packages/js-sdk/tests/sandbox/git/helpers.ts +++ b/packages/js-sdk/tests/sandbox/git/helpers.ts @@ -1,5 +1,3 @@ -import { randomUUID } from 'node:crypto' - export const AUTHOR_NAME = 'Sandbox Bot' export const AUTHOR_EMAIL = 'sandbox@example.com' export const USERNAME = 'git' @@ -10,7 +8,7 @@ export const PROTOCOL = 'https' const BASE_DIR = '/tmp/test-git' export async function createBaseDir(sandbox: any) { - const baseDir = `${BASE_DIR}/${randomUUID()}` + const baseDir = `${BASE_DIR}/${crypto.randomUUID()}` await sandbox.commands.run(`rm -rf "${baseDir}" && mkdir -p "${baseDir}"`) return baseDir } diff --git a/packages/js-sdk/tests/sandbox/host.test.ts b/packages/js-sdk/tests/sandbox/host.test.ts index 37e45f68c8..a6712536a1 100644 --- a/packages/js-sdk/tests/sandbox/host.test.ts +++ b/packages/js-sdk/tests/sandbox/host.test.ts @@ -1,11 +1,11 @@ import { assert } from 'vitest' -import { isDebug, sandboxTest, wait } from '../setup.js' +import { isDebug, sandboxTest, wait, corsHttpServerCmd } from '../setup.js' import { catchCmdExitErrorInBackground } from '../cmdHelper.js' sandboxTest( 'ping server in running sandbox', async ({ sandbox }) => { - const cmd = await sandbox.commands.run('python -m http.server 8000', { + const cmd = await sandbox.commands.run(corsHttpServerCmd(8000), { background: true, }) diff --git a/packages/js-sdk/tests/sandbox/lifecyclePayload.test.ts b/packages/js-sdk/tests/sandbox/lifecyclePayload.test.ts index 28a1999b7a..c51c6344ce 100644 --- a/packages/js-sdk/tests/sandbox/lifecyclePayload.test.ts +++ b/packages/js-sdk/tests/sandbox/lifecyclePayload.test.ts @@ -1,7 +1,7 @@ import { assert, expect, test } from 'vitest' import { InvalidArgumentError, Sandbox } from '../../src' -import { isDebug, template, wait } from '../setup.js' +import { isDebug, template, wait, corsHttpServerCmd } from '../setup.js' test.skipIf(isDebug)( 'filesystem-only auto-pause cannot be combined with auto-resume', @@ -123,7 +123,7 @@ test.skipIf(isDebug)( }) try { - await sandbox.commands.run('python3 -m http.server 8000', { + await sandbox.commands.run(corsHttpServerCmd(8000), { background: true, }) diff --git a/packages/js-sdk/tests/sandbox/network.test.ts b/packages/js-sdk/tests/sandbox/network.test.ts index ae8da00415..008d785852 100644 --- a/packages/js-sdk/tests/sandbox/network.test.ts +++ b/packages/js-sdk/tests/sandbox/network.test.ts @@ -1,7 +1,7 @@ import { assert, expect, describe } from 'vitest' import { CommandExitError } from '../../src' -import { sandboxTest, isDebug } from '../setup.js' +import { sandboxTest, isDebug, corsHttpServerCmd } from '../setup.js' describe('allow only 1.1.1.1', () => { sandboxTest.override({ @@ -137,7 +137,7 @@ describe('allowPublicTraffic=false', () => { // Start a simple HTTP server in the sandbox const port = 8080 - sandbox.commands.run(`python3 -m http.server ${port}`, { + sandbox.commands.run(corsHttpServerCmd(port), { background: true, }) @@ -176,7 +176,7 @@ describe('allowPublicTraffic=true', () => { async ({ sandbox }) => { // Start a simple HTTP server in the sandbox const port = 8080 - sandbox.commands.run(`python3 -m http.server ${port}`, { + sandbox.commands.run(corsHttpServerCmd(port), { background: true, }) diff --git a/packages/js-sdk/tests/sandbox/pty/kill.test.ts b/packages/js-sdk/tests/sandbox/pty/kill.test.ts index f602515770..d0693f7a41 100644 --- a/packages/js-sdk/tests/sandbox/pty/kill.test.ts +++ b/packages/js-sdk/tests/sandbox/pty/kill.test.ts @@ -1,6 +1,6 @@ import { sandboxTest } from '../../setup' import { assert, expect } from 'vitest' -import { ProcessExitError } from '../../../src/index.js' +import { CommandExitError } from '../../../src/index.js' sandboxTest('kill PTY', async ({ sandbox }) => { const terminal = await sandbox.pty.create({ @@ -15,7 +15,7 @@ sandboxTest('kill PTY', async ({ sandbox }) => { // The PTY process should no longer be running. await expect( sandbox.commands.run(`kill -0 ${terminal.pid}`) - ).rejects.toThrowError(ProcessExitError) + ).rejects.toThrowError(CommandExitError) }) sandboxTest('kill non-existing PTY', async ({ sandbox }) => { diff --git a/packages/js-sdk/tests/sandbox/pty/ptyConnect.test.ts b/packages/js-sdk/tests/sandbox/pty/ptyConnect.test.ts index 903da6d105..6ba72d6b6f 100644 --- a/packages/js-sdk/tests/sandbox/pty/ptyConnect.test.ts +++ b/packages/js-sdk/tests/sandbox/pty/ptyConnect.test.ts @@ -18,7 +18,7 @@ sandboxTest('pty connect/reconnect', async ({ sandbox }) => { await sandbox.pty.sendInput( terminal.pid, - new Uint8Array(Buffer.from('echo $FOO\n')) + new TextEncoder().encode('echo $FOO\n') ) // Give time for the command output in the first connection @@ -35,7 +35,7 @@ sandboxTest('pty connect/reconnect', async ({ sandbox }) => { await sandbox.pty.sendInput( terminal.pid, - new Uint8Array(Buffer.from('echo $FOO\nexit\n')) + new TextEncoder().encode('echo $FOO\nexit\n') ) await reconnectHandle.wait() diff --git a/packages/js-sdk/tests/sandbox/pty/ptyCreate.test.ts b/packages/js-sdk/tests/sandbox/pty/ptyCreate.test.ts index 5a0b98c589..d77db612c0 100644 --- a/packages/js-sdk/tests/sandbox/pty/ptyCreate.test.ts +++ b/packages/js-sdk/tests/sandbox/pty/ptyCreate.test.ts @@ -17,7 +17,7 @@ sandboxTest('create PTY', async ({ sandbox }) => { await sandbox.pty.sendInput( terminal.pid, - new Uint8Array(Buffer.from('echo $ABC\nexit\n')) + new TextEncoder().encode('echo $ABC\nexit\n') ) await terminal.wait() diff --git a/packages/js-sdk/tests/sandbox/pty/resize.test.ts b/packages/js-sdk/tests/sandbox/pty/resize.test.ts index 21bb878298..143182b6d8 100644 --- a/packages/js-sdk/tests/sandbox/pty/resize.test.ts +++ b/packages/js-sdk/tests/sandbox/pty/resize.test.ts @@ -16,7 +16,7 @@ sandboxTest('resize', async ({ sandbox }) => { await sandbox.pty.sendInput( terminal.pid, - new Uint8Array(Buffer.from('tput cols\nexit\n')) + new TextEncoder().encode('tput cols\nexit\n') ) await terminal.wait() @@ -33,7 +33,7 @@ sandboxTest('resize', async ({ sandbox }) => { await sandbox.pty.resize(resizedTerminal.pid, { cols: 100, rows: 24 }) await sandbox.pty.sendInput( resizedTerminal.pid, - new Uint8Array(Buffer.from('tput cols\nexit\n')) + new TextEncoder().encode('tput cols\nexit\n') ) await resizedTerminal.wait() diff --git a/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts b/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts index b32d7f23dd..c64c2fbee5 100644 --- a/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts +++ b/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts @@ -8,10 +8,7 @@ sandboxTest('send input', async ({ sandbox }) => { onData: () => null, }) - await sandbox.pty.sendInput( - terminal.pid, - new Uint8Array(Buffer.from('exit\n')) - ) + await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode('exit\n')) await terminal.wait() expect(terminal.exitCode).toBe(0) diff --git a/packages/js-sdk/tests/sandbox/secure.test.ts b/packages/js-sdk/tests/sandbox/secure.test.ts index 90a8712941..e8647031fd 100644 --- a/packages/js-sdk/tests/sandbox/secure.test.ts +++ b/packages/js-sdk/tests/sandbox/secure.test.ts @@ -1,7 +1,20 @@ import { assert, test, describe } from 'vitest' import { getSignature, Sandbox } from '../../src' import { sandboxTest, isDebug } from '../setup' -import { randomUUID, createHash } from 'node:crypto' + +/** + * Recomputes the signature envd expects, independently of the SDK's own + * implementation. WebCrypto rather than node:crypto so the expectation is + * computed the same way on every runtime the suite covers. + */ +async function expectedSignature(raw: string): Promise { + const digest = await crypto.subtle.digest( + 'SHA-256', + new TextEncoder().encode(raw) + ) + const base64 = btoa(String.fromCharCode(...new Uint8Array(digest))) + return 'v1_' + base64.replace(/=+$/, '') +} describe('secure sandbox', () => { sandboxTest.override({ @@ -40,16 +53,12 @@ test.skipIf(isDebug)('signing generation', async () => { const operation = 'read' const path = '/home/user/hello.txt' const user = 'root' - const envdAccessToken = randomUUID() + const envdAccessToken = crypto.randomUUID() const signatureRaw = `${path}:${operation}:${user}:${envdAccessToken}` - const buff = Buffer.from(signatureRaw, 'utf8') - const hash = createHash('sha256').update(buff).digest() - const signature = 'v1_' + hash.toString('base64').replace(/=+$/, '') - const readSignatureExpected = { - signature: signature, + signature: await expectedSignature(signatureRaw), expiration: null, } @@ -67,7 +76,7 @@ test.skipIf(isDebug)('signing generation with expiration', async () => { const operation = 'read' const path = '/home/user/hello.txt' const user = 'root' - const envdAccessToken = randomUUID() + const envdAccessToken = crypto.randomUUID() const expirationInSeconds = 120 const signatureExpiration = expirationInSeconds @@ -75,12 +84,8 @@ test.skipIf(isDebug)('signing generation with expiration', async () => { : null const signatureRaw = `${path}:${operation}:${user}:${envdAccessToken}:${signatureExpiration?.toString()}` - const buff = Buffer.from(signatureRaw, 'utf8') - const hash = createHash('sha256').update(buff).digest() - const signature = 'v1_' + hash.toString('base64').replace(/=+$/, '') - const readSignatureExpected = { - signature: signature, + signature: await expectedSignature(signatureRaw), expiration: signatureExpiration, } diff --git a/packages/js-sdk/tests/sandbox/snapshot.test.ts b/packages/js-sdk/tests/sandbox/snapshot.test.ts index ac0dead40d..47a85cfea1 100644 --- a/packages/js-sdk/tests/sandbox/snapshot.test.ts +++ b/packages/js-sdk/tests/sandbox/snapshot.test.ts @@ -1,6 +1,6 @@ import { assert, describe } from 'vitest' -import { sandboxTest, isDebug } from '../setup.js' +import { sandboxTest, isDebug, corsHttpServerCmd } from '../setup.js' sandboxTest.skipIf(isDebug)( 'pause and resume a sandbox', @@ -140,7 +140,7 @@ sandboxTest.skipIf(isDebug)( sandboxTest.skipIf(isDebug)( 'pause and resume a sandbox with http server', async ({ sandbox }) => { - await sandbox.commands.run('python3 -m http.server 8000', { + await sandbox.commands.run(corsHttpServerCmd(8000), { background: true, }) diff --git a/packages/js-sdk/tests/sandbox/urls.test.ts b/packages/js-sdk/tests/sandbox/urls.test.ts index f20854e75f..7fa6819356 100644 --- a/packages/js-sdk/tests/sandbox/urls.test.ts +++ b/packages/js-sdk/tests/sandbox/urls.test.ts @@ -1,6 +1,7 @@ import { assert, describe, test } from 'vitest' import { getSignature, InvalidArgumentError, Sandbox } from '../../src' +import { runtime } from '../../src/utils' import { TEST_API_KEY } from '../setup' function createSandbox(envdAccessToken?: string) { @@ -19,7 +20,14 @@ describe('sandbox file URLs', () => { test('file URLs use direct sandbox host when envd API uses stable host', async () => { const sandbox = createSandbox() - assert.equal(sandbox['envdApiUrl'], 'https://sandbox.e2b.app') + // The stable host is deliberately not used in a browser (CORS), which + // reaches envd on the direct host — file URLs use the direct host either way. + assert.equal( + sandbox['envdApiUrl'], + runtime === 'browser' + ? 'https://49983-sandbox-id.e2b.app' + : 'https://sandbox.e2b.app' + ) assert.equal(sandbox['envdDirectUrl'], 'https://49983-sandbox-id.e2b.app') assert.equal( await sandbox.downloadUrl('/tmp/a.txt'), diff --git a/packages/js-sdk/tests/setup.ts b/packages/js-sdk/tests/setup.ts index e29c20eba5..c5cc38e269 100644 --- a/packages/js-sdk/tests/setup.ts +++ b/packages/js-sdk/tests/setup.ts @@ -144,6 +144,38 @@ export const isDebug = process.env.E2B_DEBUG !== undefined /** Placeholder API key with a valid format for tests that don't hit the API. */ export const TEST_API_KEY = `e2b_${'0'.repeat(40)}` +/** + * Command that serves the working directory on `port` with CORS enabled, for + * tests that read a response from a server running inside the sandbox. + * + * Use this instead of `python -m http.server`, which sends no CORS headers: a + * browser only exposes a cross-origin response to JS when the server opts in, + * so there the fetch fails as an opaque `TypeError: Failed to fetch` no matter + * what the server answered. Opting in is what a real browser app's own server + * does too, and it's only ever the user's server that has to — everything the + * sandbox proxy answers itself already carries CORS headers (infra#3389). + * + * `Access-Control-Allow-Headers` and `do_OPTIONS` cover requests that carry a + * custom header, which the browser preflights before sending. + */ +export function corsHttpServerCmd(port: number): string { + return `python3 -c " +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer + +class Handler(SimpleHTTPRequestHandler): + def end_headers(self): + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Headers', '*') + SimpleHTTPRequestHandler.end_headers(self) + + def do_OPTIONS(self): + self.send_response(204) + self.end_headers() + +ThreadingHTTPServer(('', ${port}), Handler).serve_forever() +"` +} + function generateRandomString(length: number = 8): string { return Math.random() .toString(36) diff --git a/packages/js-sdk/vitest.config.mts b/packages/js-sdk/vitest.config.mts index 63439b0112..77c2fba23c 100644 --- a/packages/js-sdk/vitest.config.mts +++ b/packages/js-sdk/vitest.config.mts @@ -1,5 +1,4 @@ import { defineConfig } from 'vitest/config' -import { playwright } from '@vitest/browser-playwright' import { config } from 'dotenv' const env = config() @@ -36,23 +35,6 @@ export default defineConfig({ }, }, }, - { - test: { - name: 'browser', - include: ['tests/runtimes/browser/**/*.{test,spec}.tsx'], - browser: { - enabled: true, - headless: true, - instances: [{ browser: 'chromium' }], - provider: playwright(), - // https://playwright.dev - }, - provide: { - E2B_API_KEY: process.env.E2B_API_KEY || env.parsed?.E2B_API_KEY, - E2B_DOMAIN: process.env.E2B_DOMAIN || env.parsed?.E2B_DOMAIN, - }, - }, - }, { test: { name: 'template', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48a23cffe2..d21962d511 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,27 +177,15 @@ importers: '@redocly/cli': specifier: 2.39.0 version: 2.39.0 - '@testing-library/react': - specifier: ^16.2.0 - version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@types/node': specifier: ^20.19.19 version: 20.19.43 '@types/platform': specifier: ^1.3.6 version: 1.3.6 - '@types/react': - specifier: ^19.2.0 - version: 19.2.17 - '@types/react-dom': - specifier: ^19.2.0 - version: 19.2.3(@types/react@19.2.17) '@typescript/native': specifier: npm:typescript@^7.0.2 version: typescript@7.0.2 - '@vitejs/plugin-react': - specifier: ^4.3.4 - version: 4.3.4(vite@6.4.3(@types/node@20.19.43)(jiti@2.4.2)(terser@5.46.0)) '@vitest/browser': specifier: ^4.1.10 version: 4.1.10(bufferutil@4.0.8)(msw@2.12.14(@types/node@20.19.43)(@typescript/typescript6@6.0.2))(utf-8-validate@6.0.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.4.2)(terser@5.46.0))(vitest@4.1.10) @@ -228,12 +216,6 @@ importers: playwright: specifier: ^1.55.1 version: 1.55.1 - react: - specifier: ^19.2.0 - version: 19.2.7 - react-dom: - specifier: ^19.2.0 - version: 19.2.7(react@19.2.7) tsdown: specifier: ^0.22.3 version: 0.22.4(@typescript/typescript6@6.0.2) @@ -243,9 +225,6 @@ importers: vitest: specifier: ^4.1.10 version: 4.1.10(@types/node@20.19.43)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(msw@2.12.14(@types/node@20.19.43)(@typescript/typescript6@6.0.2))(vite@6.4.3(@types/node@20.19.43)(jiti@2.4.2)(terser@5.46.0)) - vitest-browser-react: - specifier: ^2.2.0 - version: 2.2.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.10) wrangler: specifier: ^4.113.0 version: 4.113.0(@types/node@20.19.43)(bufferutil@4.0.8)(utf-8-validate@6.0.3) @@ -262,60 +241,10 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/code-frame@7.29.7': - resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.7': - resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.7': - resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.7': - resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.29.7': - resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.29.7': - resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.29.7': - resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.29.7': - resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.29.6 - - '@babel/helper-plugin-utils@7.26.5': - resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.25.7': - resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.7': - resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} @@ -324,65 +253,15 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.29.7': - resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.29.7': - resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.25.8': - resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/parser@7.27.2': - resolution: {integrity: sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.29.7': resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-transform-react-jsx-self@7.25.9': - resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.29.6 - - '@babel/plugin-transform-react-jsx-source@7.25.9': - resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.29.6 - '@babel/runtime@7.27.1': resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.29.7': - resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.29.7': - resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.25.8': - resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.27.1': - resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} - engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} @@ -1098,9 +977,6 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -1565,43 +1441,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@testing-library/dom@10.4.0': - resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} - engines: {node: '>=18'} - - '@testing-library/react@16.2.0': - resolution: {integrity: sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==} - engines: {node: '>=18'} - peerDependencies: - '@testing-library/dom': ^10.0.0 - '@types/react': ^18.0.0 || ^19.0.0 - '@types/react-dom': ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1639,14 +1481,6 @@ packages: '@types/platform@1.3.6': resolution: {integrity: sha512-ZmSaqHuvzv+jC232cFoz2QqPUkaj6EvMmCrWcx3WRr7xTPVFCMUOTcOq8m2d+Zw1iKRc1kDiaA+jtNrV0hkVew==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react@19.2.17': - resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} - '@types/statuses@2.0.5': resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} @@ -1780,12 +1614,6 @@ packages: resolution: {integrity: sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==} hasBin: true - '@vitejs/plugin-react@4.3.4': - resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^6.4.3 - '@vitest/browser-playwright@4.1.10': resolution: {integrity: sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==} peerDependencies: @@ -1984,10 +1812,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -2005,9 +1829,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -2067,11 +1888,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.24.4: - resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2102,9 +1918,6 @@ packages: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} - caniuse-lite@1.0.30001753: - resolution: {integrity: sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==} - chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -2216,9 +2029,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -2258,10 +2068,6 @@ packages: defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -2273,9 +2079,6 @@ packages: dockerfile-ast@0.7.1: resolution: {integrity: sha512-oX/A4I0EhSkGqrFv0YuvPkBUSYp1XiY8O8zAKc8Djglx8ocz+JfOr8gP0ryRMC2myqvDLagmnZaU9ot1vG2ijw==} - dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} @@ -2303,9 +2106,6 @@ packages: easy-table@1.2.0: resolution: {integrity: sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==} - electron-to-chromium@1.5.80: - resolution: {integrity: sha512-LTrKpW0AqIuHwmlVNV+cjFYTnXtM9K37OGhpe0ZI10ScPSxqVSryZHIY3WnCS5NSYbBODRTZyhRMS2h5FAEqAw==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2466,10 +2266,6 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2824,11 +2620,6 @@ packages: resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true - jsesc@3.0.2: - resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} - engines: {node: '>=6'} - hasBin: true - json-parse-better-errors@1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} @@ -2847,11 +2638,6 @@ packages: json2md@2.0.1: resolution: {integrity: sha512-VbwmZ83qmUfKBS2pUOHlzNKEZFPBeJSbzEok3trMYyboZUgdHNn1XZfc1uT8UZs1GHCrmRUBXCfqw4YmmQuOhw==} - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -2885,13 +2671,6 @@ packages: resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} engines: {node: 20 || >=22} - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -2992,9 +2771,6 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -3225,10 +3001,6 @@ packages: engines: {node: '>=14'} hasBin: true - pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - pretty-ms@9.1.0: resolution: {integrity: sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==} engines: {node: '>=18'} @@ -3258,22 +3030,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@19.2.7: - resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} - peerDependencies: - react: ^19.2.7 - - react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} - engines: {node: '>=0.10.0'} - - react@19.2.7: - resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} - engines: {node: '>=0.10.0'} - read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} engines: {node: '>=4'} @@ -3373,17 +3129,10 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -3639,10 +3388,6 @@ packages: resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} hasBin: true - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3776,12 +3521,6 @@ packages: until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} - update-browserslist-db@1.1.2: - resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - uri-js-replace@1.0.1: resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} @@ -3836,20 +3575,6 @@ packages: yaml: optional: true - vitest-browser-react@2.2.0: - resolution: {integrity: sha512-oY3KM6305kwJMa6nHo92vVtkOsih7mjEf12dLKuphaF+9ywWPEc+qanIBd394SZ6m5LadVEaG6dicvvizOzmjA==} - peerDependencies: - '@types/react': ^18.0.0 || ^19.0.0 - '@types/react-dom': ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - vitest: ^4.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - vitest@4.1.10: resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -3985,9 +3710,6 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -4059,144 +3781,18 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/code-frame@7.29.7': - dependencies: - '@babel/helper-validator-identifier': 7.29.7 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.7': {} - - '@babel/core@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@10.2.2) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.7': - dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.0.2 - - '@babel/helper-compilation-targets@7.29.7': - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - browserslist: 4.24.4 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.29.7': {} - - '@babel/helper-module-imports@7.29.7': - dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.26.5': {} - - '@babel/helper-string-parser@7.25.7': {} - - '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.25.7': {} - '@babel/helper-validator-identifier@7.27.1': {} '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helper-validator-option@7.29.7': {} - - '@babel/helpers@7.29.7': - dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 - - '@babel/parser@7.25.8': - dependencies: - '@babel/types': 7.27.1 - - '@babel/parser@7.27.2': - dependencies: - '@babel/types': 7.27.1 - '@babel/parser@7.29.7': dependencies: '@babel/types': 7.29.7 - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/runtime@7.27.1': {} - '@babel/runtime@7.29.7': {} - - '@babel/template@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - - '@babel/traverse@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 - debug: 4.4.3(supports-color@10.2.2) - transitivePeerDependencies: - - supports-color - - '@babel/types@7.25.8': - dependencies: - '@babel/helper-string-parser': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - to-fast-properties: 2.0.0 - - '@babel/types@7.27.1': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/types@7.29.7': dependencies: '@babel/helper-string-parser': 7.29.7 @@ -4717,11 +4313,7 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + optional: true '@jridgewell/resolve-uri@3.1.2': {} @@ -5083,55 +4675,11 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@testing-library/dom@10.4.0': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/runtime': 7.29.7 - '@types/aria-query': 5.0.4 - aria-query: 5.3.0 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - pretty-format: 27.5.1 - - '@testing-library/react@16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.27.1 - '@testing-library/dom': 10.4.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true - '@types/aria-query@5.0.4': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.25.8 - '@babel/types': 7.25.8 - '@types/babel__generator': 7.6.8 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 - - '@types/babel__generator@7.6.8': - dependencies: - '@babel/types': 7.27.1 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.27.2 - '@babel/types': 7.27.1 - - '@types/babel__traverse@7.20.6': - dependencies: - '@babel/types': 7.27.1 - '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -5166,14 +4714,6 @@ snapshots: '@types/platform@1.3.6': {} - '@types/react-dom@19.2.3(@types/react@19.2.17)': - dependencies: - '@types/react': 19.2.17 - - '@types/react@19.2.17': - dependencies: - csstype: 3.2.3 - '@types/statuses@2.0.5': {} '@types/statuses@2.0.6': {} @@ -5246,17 +4786,6 @@ snapshots: dependencies: '@typescript/old': typescript@6.0.3 - '@vitejs/plugin-react@4.3.4(vite@6.4.3(@types/node@20.19.43)(jiti@2.4.2)(terser@5.46.0))': - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.29.7) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 6.4.3(@types/node@20.19.43)(jiti@2.4.2)(terser@5.46.0) - transitivePeerDependencies: - - supports-color - '@vitest/browser-playwright@4.1.10(bufferutil@4.0.8)(msw@2.12.14(@types/node@20.19.43)(@typescript/typescript6@6.0.2))(playwright@1.55.1)(utf-8-validate@6.0.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.4.2)(terser@5.46.0))(vitest@4.1.10)': dependencies: '@vitest/browser': 4.1.10(bufferutil@4.0.8)(msw@2.12.14(@types/node@20.19.43)(@typescript/typescript6@6.0.2))(utf-8-validate@6.0.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.4.2)(terser@5.46.0))(vitest@4.1.10) @@ -5441,8 +4970,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} ansis@4.3.1: {} @@ -5455,10 +4982,6 @@ snapshots: argparse@2.0.1: {} - aria-query@5.3.0: - dependencies: - dequal: 2.0.3 - array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -5528,13 +5051,6 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.24.4: - dependencies: - caniuse-lite: 1.0.30001753 - electron-to-chromium: 1.5.80 - node-releases: 2.0.19 - update-browserslist-db: 1.1.2(browserslist@4.24.4) - buffer-from@1.1.2: optional: true @@ -5568,8 +5084,6 @@ snapshots: camelcase@7.0.1: {} - caniuse-lite@1.0.30001753: {} - chai@6.2.2: {} chalk@2.4.2: @@ -5675,8 +5189,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - csstype@3.2.3: {} - data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -5726,8 +5238,6 @@ snapshots: defu@6.1.7: {} - dequal@2.0.3: {} - detect-libc@2.1.2: {} dir-glob@3.0.1: @@ -5739,8 +5249,6 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-languageserver-types: 3.17.5 - dom-accessibility-api@0.5.16: {} - dotenv@16.4.5: {} dts-resolver@3.0.0: {} @@ -5775,8 +5283,6 @@ snapshots: optionalDependencies: wcwidth: 1.0.1 - electron-to-chromium@1.5.80: {} - emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -6035,8 +5541,6 @@ snapshots: functions-have-names@1.2.3: {} - gensync@1.0.0-beta.2: {} - get-caller-file@2.0.5: {} get-intrinsic@1.2.1: @@ -6388,8 +5892,6 @@ snapshots: dependencies: argparse: 2.0.1 - jsesc@3.0.2: {} - json-parse-better-errors@1.0.2: {} json-parse-even-better-errors@3.0.0: {} @@ -6412,8 +5914,6 @@ snapshots: dependencies: indento: 1.1.13 - json5@2.2.3: {} - jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -6458,12 +5958,6 @@ snapshots: lru-cache@11.1.0: {} - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lz-string@1.5.0: {} - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -6574,8 +6068,6 @@ snapshots: node-gyp-build@4.8.4: optional: true - node-releases@2.0.19: {} - normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 @@ -6795,12 +6287,6 @@ snapshots: prettier@3.6.2: {} - pretty-format@27.5.1: - dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 - pretty-ms@9.1.0: dependencies: parse-ms: 4.0.0 @@ -6820,17 +6306,6 @@ snapshots: queue-microtask@1.2.3: {} - react-dom@19.2.7(react@19.2.7): - dependencies: - react: 19.2.7 - scheduler: 0.27.0 - - react-is@17.0.2: {} - - react-refresh@0.14.2: {} - - react@19.2.7: {} - read-pkg@3.0.0: dependencies: load-json-file: 4.0.0 @@ -6983,12 +6458,8 @@ snapshots: safer-buffer@2.1.2: {} - scheduler@0.27.0: {} - semver@5.7.2: {} - semver@6.3.1: {} - semver@7.7.2: {} semver@7.8.5: {} @@ -7276,8 +6747,6 @@ snapshots: dependencies: tldts-core: 7.0.19 - to-fast-properties@2.0.0: {} - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -7419,12 +6888,6 @@ snapshots: until-async@3.0.2: {} - update-browserslist-db@1.1.2(browserslist@4.24.4): - dependencies: - browserslist: 4.24.4 - escalade: 3.2.0 - picocolors: 1.1.1 - uri-js-replace@1.0.1: {} utf-8-validate@6.0.3: @@ -7455,15 +6918,6 @@ snapshots: jiti: 2.4.2 terser: 5.46.0 - vitest-browser-react@2.2.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.10): - dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - vitest: 4.1.10(@types/node@20.19.43)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(msw@2.12.14(@types/node@20.19.43)(@typescript/typescript6@6.0.2))(vite@6.4.3(@types/node@20.19.43)(jiti@2.4.2)(terser@5.46.0)) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - vitest@4.1.10(@types/node@20.19.43)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(msw@2.12.14(@types/node@20.19.43)(@typescript/typescript6@6.0.2))(vite@6.4.3(@types/node@20.19.43)(jiti@2.4.2)(terser@5.46.0)): dependencies: '@vitest/expect': 4.1.10 @@ -7616,8 +7070,6 @@ snapshots: y18n@5.0.8: {} - yallist@3.1.1: {} - yallist@5.0.0: {} yaml-ast-parser@0.0.43: {}