diff --git a/.github/workflows/vitest-pool-bun.yml b/.github/workflows/vitest-pool-bun.yml new file mode 100644 index 000000000..513cd129a --- /dev/null +++ b/.github/workflows/vitest-pool-bun.yml @@ -0,0 +1,59 @@ +name: 'vitest-pool-bun' + +permissions: {} +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'vitest-pool-bun/**' + - '.github/workflows/vitest-pool-bun.yml' + pull_request: + paths: + - 'vitest-pool-bun/**' + - '.github/workflows/vitest-pool-bun.yml' + +concurrency: + group: '${{ github.workflow }}-${{ github.ref }}' + cancel-in-progress: true + +jobs: + tests: + timeout-minutes: 10 + runs-on: ubuntu-latest + defaults: + run: + working-directory: vitest-pool-bun + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup NodeJS + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 24 + + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.3.14 + + - name: Install dependencies + run: | + npm install + + - name: Run tests (green example suite in Bun) + run: | + npm run demo + + - name: Install dependencies (clickhouse-js root) + working-directory: . + run: | + npm install + + - name: Run selected Node unit tests in Bun pool + working-directory: . + env: + CLICKHOUSE_TEST_SKIP_INIT: '1' + run: | + node vitest-pool-bun/node_modules/vitest/vitest.mjs run --config vitest-pool-bun/examples/clickhouse-node-unit.config.mjs diff --git a/vitest-pool-bun/ENVIRONMENT.md b/vitest-pool-bun/ENVIRONMENT.md new file mode 100644 index 000000000..8d935c632 --- /dev/null +++ b/vitest-pool-bun/ENVIRONMENT.md @@ -0,0 +1,28 @@ +# Environment + +Exact versions used to develop and validate this proof of concept. Other +versions may work but are unverified. + +| Component | Version | Role | +| --------- | ------- | ---- | +| Node.js | `v24.16.0` (host requires `>= 20`) | Host / orchestrator: owns the Vite transform pipeline, scheduling, reporters | +| Bun | `1.3.14` (requires `>= 1.2`, `1.3+` preferred) | Worker runtime: evaluates modules and runs the test callbacks | +| vitest | `4.1.8` (requires `^4.1`) | Custom-pool API (`vitest/node`, `vitest/worker`) | +| flatted | `3.4.2` | JSON-safe (circular-ref-tolerant) serialization across the Node ↔ Bun IPC channel | + +## Reproducing the version check + +```bash +node --version # v24.16.0 +bun --version # 1.3.14 +node -e 'console.log(require("vitest/package.json").version)' # 4.1.8 +``` + +## Notes + +- The **host process is always launched with `node`** (`node node_modules/vitest/vitest.mjs run`). Only the per-file worker is `bun`. +- Package manager: installing with `bun`, `npm`, or `pnpm` is fine; it does not + affect which runtime executes tests. +- Targets **macOS / Linux only**. Windows is an explicit non-goal for the PoC. +- The `bun` executable must be discoverable. Resolution order: + `bunBinary` pool option → `BUN_BINARY` env var → `bun` on `PATH`. diff --git a/vitest-pool-bun/README.md b/vitest-pool-bun/README.md new file mode 100644 index 000000000..d11f8830c --- /dev/null +++ b/vitest-pool-bun/README.md @@ -0,0 +1,140 @@ +# vitest-pool-bun + +> **Proof of concept.** A custom [Vitest](https://vitest.dev) 4.1 pool that +> executes test files **inside the [Bun](https://bun.sh) runtime**, analogous to +> how `@cloudflare/vitest-pool-workers` executes them inside `workerd`. + +The **Node host** owns the Vite transform pipeline (transforming modules, +scheduling, collecting results, running reporters); **Bun** owns test execution +(evaluating the transformed modules and running the test callbacks). The two +roles talk over a JSON-only IPC channel. + +``` +┌────────────────────────┐ WorkerRequest / RPC (JSON IPC) ┌─────────────────────────┐ +│ Node host (Vitest) │ ───────────────────────────────► │ Bun worker │ +│ • Vite transform │ │ • vitest/worker init() │ +│ • scheduling │ ◄─────────────────────────────── │ • module runner │ +│ • reporters │ WorkerResponse / module code │ • runs test callbacks │ +└────────────────────────┘ └─────────────────────────┘ + src/pool-worker.ts (BunPoolWorker) src/worker-entry.ts +``` + +## Why this works (and what made it non-trivial) + +- Bun natively executes TypeScript and imports from disk, so almost none of the + elaborate module-fallback machinery that the `workerd` pool needs is required. +- Vitest's plain (non-`vm`) pool evaluates modules with Vite's + AsyncFunction-based `ESModulesEvaluator`, **not** `node:vm`. Bun's `vm` is only + ~90% conformant, but the AsyncFunction evaluator works cleanly under Bun — this + is the single most important reason the PoC is viable (de-risked in Phase 0). +- Node ↔ Bun IPC only supports JSON serialization. All channel traffic is routed + through `flatted` so circular object graphs and Vitest's serialized errors + survive a JSON-only channel. See [`src/channel.ts`](./src/channel.ts). + +## Requirements + +See [`ENVIRONMENT.md`](./ENVIRONMENT.md) for pinned versions. In short: Node +`>= 20` (host), Bun `>= 1.2` (worker), `vitest@^4.1`. macOS / Linux only. + +## Install + +```bash +# from this package directory +npm install +# Bun must be installed and on PATH (https://bun.sh) +bun --version +``` + +## Configure + +Minimal config via the helper: + +```ts +// vitest.config.ts +import { defineBunPoolConfig } from 'vitest-pool-bun' + +export default defineBunPoolConfig({ + test: { + include: ['tests/**/*.test.ts'], + }, +}) +``` + +Or wire the pool directly: + +```ts +import { defineConfig } from 'vitest/config' +import { bunPool } from 'vitest-pool-bun' + +export default defineConfig({ + test: { + pool: bunPool({ + // bunBinary: '/custom/path/to/bun', // defaults to BUN_BINARY env or `bun` + // bunArgs: ['--smol'], + // env: { MY_FLAG: '1' }, + }), + }, +}) +``` + +## Run + +The host is launched with **node**; the pool spawns **bun** per test file: + +```bash +node node_modules/vitest/vitest.mjs run +``` + +### Demo scripts + +| Script | What it shows | +| ------ | ------------- | +| `npm run demo` | Green example suite executed in Bun (9 passing tests) | +| `npm run demo:failure` | A failing assertion reported with a stack trace pointing at the original `.ts` line | +| `npm run demo:diagnostics` | A crashed Bun worker surfaced as a clear host error (not a hang) | +| `npm run spike` | The throwaway Phase 0 de-risk spike (`bun run spikes/m0-runner.ts`) | + +`npm run demo` performs a clean `build` first, so a fresh clone only needs +`npm install` followed by `npm run demo`. + +## Options (`BunPoolOptions`) + +| Option | Type | Default | Description | +| ------ | ---- | ------- | ----------- | +| `bunBinary` | `string` | `BUN_BINARY` env, else `bun` | Path or command for the Bun executable | +| `bunArgs` | `string[]` | `[]` | Extra args passed to `bun run ` | +| `env` | `Record` | `{}` | Extra env vars for the spawned worker | + +## Layout + +``` +vitest-pool-bun/ +├─ src/ +│ ├─ index.ts # bunPool() + defineBunPoolConfig() +│ ├─ pool-worker.ts # BunPoolWorker (host side, runs under Node) +│ ├─ worker-entry.ts # runs under Bun; calls init() from 'vitest/worker' +│ ├─ channel.ts # flatted-based JSON-safe (de)serialization +│ └─ types.ts # BunPoolOptions +├─ spikes/m0-runner.ts # Phase 0 de-risk spike (throwaway) +├─ examples/ # runnable example suite + configs +├─ ENVIRONMENT.md # pinned versions +└─ VALIDATION.md # what works / partially works / unsupported +``` + +## Known limitations + +This is a proof of concept. See [`VALIDATION.md`](./VALIDATION.md) for the full +matrix. Highlights: + +- **V8 coverage is unsupported** under Bun (`node:inspector` lacks the Profiler + APIs). Use the `istanbul` provider, which works. +- Isolation uses **one Bun process per file** (or a reused process when + `isolate: false`). Thread isolation is intentionally not used: Bun's + `worker_threads` cannot access the parent IPC channel. +- No watch/HMR rerun loop, browser mode, type-testing, or Windows support. +- Performance tuning, worker-reuse pooling, and full `vi.mock` parity are out of + scope for the PoC. + +## License + +Apache-2.0. diff --git a/vitest-pool-bun/VALIDATION.md b/vitest-pool-bun/VALIDATION.md new file mode 100644 index 000000000..492bad8a6 --- /dev/null +++ b/vitest-pool-bun/VALIDATION.md @@ -0,0 +1,79 @@ +# Validation + +What was actually exercised against this PoC, and the boundaries. Versions are +pinned in [`ENVIRONMENT.md`](./ENVIRONMENT.md) (Node 24, Bun 1.3.14, +vitest 4.1.8). Everything below was observed by running the host under `node` +and executing tests in `bun` via this pool. + +Legend: ✅ works · ⚠️ works with caveats · ❌ unsupported · ⛔ not attempted (non-goal) + +## Core execution + +| Capability | Status | Notes | +| ---------- | :----: | ----- | +| Sync tests | ✅ | `examples/tests/smoke.test.ts` | +| Async / `await` tests | ✅ | `examples/tests/async.test.ts`, `concurrent.test.ts` | +| `describe` / nested suites | ✅ | `examples/tests/concurrent.test.ts` | +| Importing local TS modules | ✅ | Resolved + transformed by the host module runner over the channel | +| Importing `node_modules` deps | ✅ | e.g. `vitest` matchers; externalized/transformed via host | +| `setupFiles` | ✅ | `examples/setup.ts` runs in the Bun worker | +| Pass/fail reporting to standard reporters | ✅ | Totals are correct across multiple files | +| Failing-assertion stack traces | ✅ | Point at the original `.ts` source line (host-side Vite source maps); see `npm run demo:failure` | +| Bun globals (`Bun`, `Bun.file`, `Bun.env`, `Bun.version`) | ✅ | `examples/tests/bun-api.test.ts` | + +## Isolation & lifecycle + +| Capability | Status | Notes | +| ---------- | :----: | ----- | +| Process-per-file isolation (`isolate: true`, default) | ✅ | One spawned `bun` per file; Vitest's scheduler owns this | +| Reused-process mode (`isolate: false`) | ✅ | Correct totals; runner reuse handled by Vitest | +| No leaked Bun processes after a run | ✅ | Verified via `pgrep` after exit, both isolation modes | +| Thread isolation (`worker_threads`) | ❌ | Intentionally not used — Bun threads can't access the parent IPC channel | + +## Diagnostics (failure legibility) + +| Scenario | Status | Behaviour | +| -------- | :----: | --------- | +| `bun` not found / bad `bunBinary` | ✅ | Fails fast with an actionable message naming `bunBinary` / `BUN_BINARY`; no hang | +| Soft crash (`process.exit` in a test) | ✅ | Reported as a test error with TS-mapped line | +| Hard crash / channel disconnect (worker `SIGKILL`'d mid-run) | ✅ | Surfaced as `[vitest-pool]: Worker bun emitted error. Caused by: Worker exited unexpectedly`; no indefinite hang | + +## Test-author features + +| Feature | Status | Notes | +| ------- | :----: | ----- | +| Spies (`vi.fn`, `toHaveBeenCalledWith`) | ✅ | Verified | +| Fake timers (`vi.useFakeTimers`, `advanceTimersByTime`) | ✅ | Verified | +| Snapshots (inline & file) | ⚠️ | Mechanism works; not stress-tested for format parity across runtimes (a PoC non-goal) | +| Module mocking (`vi.mock` hoisting) | ⚠️ | Basic factory hoisting verified to work; full `vi.mock` parity is **not** guaranteed and edge cases are untested | + +## Coverage + +| Provider | Status | Notes | +| -------- | :----: | ----- | +| `istanbul` (`@vitest/coverage-istanbul`) | ✅ | Works — instrumentation happens at transform time on the host, counters are plain JS collected in Bun. Reported 100% on the example source | +| `v8` (`@vitest/coverage-v8`) | ❌ | Fails with `Coverage APIs are not supported` — Bun's `node:inspector` lacks the Profiler/coverage APIs | + +## Explicit non-goals (not attempted) + +| Area | Status | +| ---- | :----: | +| Watch mode / HMR rerun loop | ⛔ | +| Browser mode | ⛔ | +| Type testing (`expect-type` / `*.test-d.ts`) | ⛔ | +| Windows support | ⛔ | +| Performance / parallelism tuning, worker-reuse pooling | ⛔ | + +## Performance note + +Independent benchmarks report Bun (JavaScriptCore) can be slower than Node (V8) +on heavily async test workloads. This is a performance characteristic, not a +correctness issue, and was **not** benchmarked here. Treat it as a known caveat +rather than a guarantee in either direction. + +## Topology used + +The PoC uses the **target topology**: a Node host driving a spawned-process Bun +worker over an IPC channel (not the §7 fallback of running the entire Vitest +process under `bun`). The Phase 0 spike confirmed module evaluation works under +Bun before any pool plumbing was built, so the fallback was never needed. diff --git a/vitest-pool-bun/examples/clickhouse-node-unit.config.mjs b/vitest-pool-bun/examples/clickhouse-node-unit.config.mjs new file mode 100644 index 000000000..80a441f08 --- /dev/null +++ b/vitest-pool-bun/examples/clickhouse-node-unit.config.mjs @@ -0,0 +1,26 @@ +import { fileURLToPath } from 'node:url' +import { defineBunPoolConfig } from '../dist/index.js' + +export default defineBunPoolConfig({ + test: { + root: fileURLToPath(new URL('../..', import.meta.url)), + include: [ + 'packages/client-node/__tests__/unit/node_user_agent.test.ts', + 'packages/client-node/__tests__/unit/node_default_logger.test.ts', + ], + watch: false, + }, + resolve: { + alias: { + '@clickhouse/client-common': fileURLToPath( + new URL('../../packages/client-common/src', import.meta.url), + ), + '@clickhouse/client-node': fileURLToPath( + new URL('../../packages/client-node/src', import.meta.url), + ), + '@test': fileURLToPath( + new URL('../../packages/client-common/__tests__', import.meta.url), + ), + }, + }, +}) diff --git a/vitest-pool-bun/examples/diagnostics.config.ts b/vitest-pool-bun/examples/diagnostics.config.ts new file mode 100644 index 000000000..713e30529 --- /dev/null +++ b/vitest-pool-bun/examples/diagnostics.config.ts @@ -0,0 +1,10 @@ +import { defineBunPoolConfig } from '../dist/index.js' + +// Separate config so the crash fixture is never picked up by the demo suite. +export default defineBunPoolConfig({ + test: { + root: __dirname, + include: ['diagnostics/**/*.test.ts'], + watch: false, + }, +}) diff --git a/vitest-pool-bun/examples/diagnostics/crash.test.ts b/vitest-pool-bun/examples/diagnostics/crash.test.ts new file mode 100644 index 000000000..8b54d4116 --- /dev/null +++ b/vitest-pool-bun/examples/diagnostics/crash.test.ts @@ -0,0 +1,15 @@ +import { expect, test } from 'vitest' + +// Diagnostics fixture (NOT part of the default demo suite). +// +// When BUN_POOL_CRASH=1, the test abruptly kills the Bun worker mid-run to +// simulate a runtime crash / channel disconnect. The host (`BunPoolWorker` + +// Vitest's PoolRunner) must surface a clear, actionable error and terminate the +// run instead of hanging indefinitely (Phase 4 / STOP gate 4). +test('worker crash is surfaced, not hung', () => { + if (process.env.BUN_POOL_CRASH === '1') { + // Hard-kill this Bun process while the host is waiting for results. + process.exit(137) + } + expect(true).toBe(true) +}) diff --git a/vitest-pool-bun/examples/expected-failures.config.ts b/vitest-pool-bun/examples/expected-failures.config.ts new file mode 100644 index 000000000..3c067ed72 --- /dev/null +++ b/vitest-pool-bun/examples/expected-failures.config.ts @@ -0,0 +1,11 @@ +import { defineBunPoolConfig } from '../dist/index.js' + +// Separate config so the intentionally-failing fixture is never part of the +// green demo suite. Used to demonstrate failure reporting (STOP gate 2). +export default defineBunPoolConfig({ + test: { + root: __dirname, + include: ['expected-failures/**/*.test.ts'], + watch: false, + }, +}) diff --git a/vitest-pool-bun/examples/expected-failures/assertion.test.ts b/vitest-pool-bun/examples/expected-failures/assertion.test.ts new file mode 100644 index 000000000..0d6d803bc --- /dev/null +++ b/vitest-pool-bun/examples/expected-failures/assertion.test.ts @@ -0,0 +1,12 @@ +import { expect, test } from 'vitest' +import { add } from '../src/math.js' + +// Intentionally failing test (NOT part of the green demo suite). +// +// Demonstrates STOP gate 2: a failing assertion is reported as a real failure +// whose stack trace points at the original TypeScript source line below — the +// `expect(...)` call — proving host-side Vite source maps survive the Bun round +// trip. Run with `npm run demo:failure` (expected to exit non-zero). +test('failing assertion reports a TS-mapped stack', () => { + expect(add(2, 2)).toBe(5) +}) diff --git a/vitest-pool-bun/examples/setup.ts b/vitest-pool-bun/examples/setup.ts new file mode 100644 index 000000000..95f386a0a --- /dev/null +++ b/vitest-pool-bun/examples/setup.ts @@ -0,0 +1,3 @@ +// Runs inside the Bun worker before each test file (Vitest `setupFiles`). +// Used by Phase 2 to prove setup files are executed in the Bun runtime. +;(globalThis as Record).__BUN_POOL_SETUP__ = true diff --git a/vitest-pool-bun/examples/src/math.ts b/vitest-pool-bun/examples/src/math.ts new file mode 100644 index 000000000..75144b780 --- /dev/null +++ b/vitest-pool-bun/examples/src/math.ts @@ -0,0 +1,8 @@ +export function add(a: number, b: number): number { + return a + b +} + +export async function asyncDouble(value: number): Promise { + await new Promise((resolve) => setTimeout(resolve, 1)) + return value * 2 +} diff --git a/vitest-pool-bun/examples/tests/async.test.ts b/vitest-pool-bun/examples/tests/async.test.ts new file mode 100644 index 000000000..fcfac56ab --- /dev/null +++ b/vitest-pool-bun/examples/tests/async.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from 'vitest' +import { add, asyncDouble } from '../src/math.js' + +test('async/await with an imported local module', async () => { + const doubled = await asyncDouble(21) + expect(doubled).toBe(42) + expect(add(40, 2)).toBe(42) +}) diff --git a/vitest-pool-bun/examples/tests/bun-api.test.ts b/vitest-pool-bun/examples/tests/bun-api.test.ts new file mode 100644 index 000000000..5cdd9cda8 --- /dev/null +++ b/vitest-pool-bun/examples/tests/bun-api.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from 'vitest' + +// Phase 4: confirm Bun-specific globals/APIs are usable from inside tests, +// proving execution really happens in the Bun runtime (not a Node shim). + +test('Bun global is present with a version', () => { + expect(typeof Bun).toBe('object') + expect(typeof Bun.version).toBe('string') + expect(Bun.version.length).toBeGreaterThan(0) +}) + +test('Bun.file can read a file from disk', async () => { + const contents = await Bun.file(import.meta.filename).text() + expect(contents).toContain('Bun.file can read a file from disk') +}) + +test('Bun.env exposes environment variables', () => { + expect(typeof Bun.env).toBe('object') +}) diff --git a/vitest-pool-bun/examples/tests/concurrent.test.ts b/vitest-pool-bun/examples/tests/concurrent.test.ts new file mode 100644 index 000000000..1709374fb --- /dev/null +++ b/vitest-pool-bun/examples/tests/concurrent.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, test } from 'vitest' +import { asyncDouble } from '../src/math.js' + +describe('async suite running in Bun', () => { + test('resolves multiple async operations', async () => { + const results = await Promise.all([asyncDouble(1), asyncDouble(2), asyncDouble(3)]) + expect(results).toEqual([2, 4, 6]) + }) + + test('uses Bun timers', async () => { + const start = Date.now() + await new Promise((resolve) => setTimeout(resolve, 5)) + expect(Date.now() - start).toBeGreaterThanOrEqual(4) + }) +}) diff --git a/vitest-pool-bun/examples/tests/smoke.test.ts b/vitest-pool-bun/examples/tests/smoke.test.ts new file mode 100644 index 000000000..6cc7c719c --- /dev/null +++ b/vitest-pool-bun/examples/tests/smoke.test.ts @@ -0,0 +1,15 @@ +import { expect, test } from 'vitest' + +// Proves execution actually happens inside Bun, not the Node host. +test('runs inside the Bun runtime', () => { + expect(typeof Bun).not.toBe('undefined') +}) + +test('1 + 1 === 2', () => { + expect(1 + 1).toBe(2) +}) + +// Proves Vitest `setupFiles` ran inside the Bun worker (see examples/setup.ts). +test('setup file executed in the Bun worker', () => { + expect((globalThis as Record).__BUN_POOL_SETUP__).toBe(true) +}) diff --git a/vitest-pool-bun/examples/vitest.config.ts b/vitest-pool-bun/examples/vitest.config.ts new file mode 100644 index 000000000..4da96889b --- /dev/null +++ b/vitest-pool-bun/examples/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineBunPoolConfig } from '../dist/index.js' + +export default defineBunPoolConfig({ + test: { + include: ['tests/**/*.test.ts'], + setupFiles: ['./setup.ts'], + // The PoC targets a single `vitest run`; disable watch niceties. + watch: false, + }, +}) diff --git a/vitest-pool-bun/package-lock.json b/vitest-pool-bun/package-lock.json new file mode 100644 index 000000000..96188e106 --- /dev/null +++ b/vitest-pool-bun/package-lock.json @@ -0,0 +1,2061 @@ +{ + "name": "vitest-pool-bun", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vitest-pool-bun", + "version": "0.0.0", + "license": "Apache-2.0", + "dependencies": { + "flatted": "^3.3.1" + }, + "devDependencies": { + "@types/node": "^20.19.41", + "@vitest/coverage-istanbul": "^4.1.8", + "@vitest/coverage-v8": "^4.1.8", + "typescript": "^5.6.0", + "vitest": "^4.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "vitest": "^4.1.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "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.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/coverage-istanbul": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-4.1.8.tgz", + "integrity": "sha512-/h514nMZMKI6foh21mVgO1zlCH6pdDamwKMbla1uLU2GMxTlfp0PQMxovWozmzQdCIQYZ2XXEzg2zNZom2zAOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@istanbuljs/schema": "^0.1.3", + "@jridgewell/gen-mapping": "^0.3.13", + "@jridgewell/trace-mapping": "0.3.31", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.1.8" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", + "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.8", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.8", + "vitest": "4.1.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.3.tgz", + "integrity": "sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.366", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.366.tgz", + "integrity": "sha512-OlRuhb688YTCzzU3gXPLn6nGyd+F+53INE1qaKKlu6kETErE8FYsyDh0XqXEU+uBRn0MpCzz2vfNwORhkap8qg==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/vitest-pool-bun/package.json b/vitest-pool-bun/package.json new file mode 100644 index 000000000..dbef7b1c7 --- /dev/null +++ b/vitest-pool-bun/package.json @@ -0,0 +1,42 @@ +{ + "name": "vitest-pool-bun", + "version": "0.0.0", + "private": true, + "description": "Proof-of-concept Vitest 4.1 custom pool that executes test files inside the Bun runtime.", + "type": "module", + "license": "Apache-2.0", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "engines": { + "node": ">=20" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "clean": "rm -rf dist", + "spike": "bun run spikes/m0-runner.ts", + "demo": "npm run build && node node_modules/vitest/vitest.mjs run --root examples", + "demo:failure": "npm run build && node node_modules/vitest/vitest.mjs run --config examples/expected-failures.config.ts", + "demo:diagnostics": "npm run build && BUN_POOL_CRASH=1 node node_modules/vitest/vitest.mjs run --config examples/diagnostics.config.ts" + }, + "dependencies": { + "flatted": "^3.3.1" + }, + "peerDependencies": { + "vitest": "^4.1.0" + }, + "devDependencies": { + "@types/node": "^20.19.41", + "@vitest/coverage-istanbul": "^4.1.8", + "typescript": "^5.6.0", + "vitest": "^4.1.0" + } +} diff --git a/vitest-pool-bun/spikes/m0-runner.ts b/vitest-pool-bun/spikes/m0-runner.ts new file mode 100644 index 000000000..b79dfeeb3 --- /dev/null +++ b/vitest-pool-bun/spikes/m0-runner.ts @@ -0,0 +1,132 @@ +/** + * Phase 0 de-risk spike (THROWAWAY). + * + * Purpose: prove, in isolation and before any pool plumbing exists, that the + * two make-or-break risks are clear under Bun: + * + * §6.1 — Module evaluation. Vitest's plain (non-vm) pool drives Vite's + * `ModuleRunner`, which evaluates transformed modules with the + * AsyncFunction-based `ESModulesEvaluator` (NOT `node:vm`, whose Bun + * conformance is only ~90%). This spike runs that exact evaluator + * under Bun against a trivial pre-transformed test module and confirms + * the test callback executes and its result round-trips. + * + * §6.3 — `vitest/worker` internals. We import `{ init, runBaseTests, + * setupEnvironment }` and confirm they load under Bun (JavaScriptCore) + * without tripping over a missing Node internal at import time. + * + * No Vite server and no real IPC: module contents are served directly from an + * in-memory transport, exactly as the plan's Phase 0 task #2 prescribes. + * + * Run with: bun run spikes/m0-runner.ts + * Expected: prints a collected + passed result for the trivial test, exit 0. + */ +import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner' +// Importing these proves the worker helpers load under Bun (risk §6.3). +import { init, runBaseTests, setupEnvironment } from 'vitest/worker' + +interface SpikeTest { + name: string + fn: () => unknown +} + +const ENTRY = '/virtual-spike.test.ts' + +// A single trivial pre-transformed module. It has no imports/exports, so the +// module runner only ever asks us for this one module. `test` is provided as a +// global by the harness below, mirroring how a real test framework injects it. +const TRANSFORMED_MODULE = ` +test('1 + 1 === 2', () => { + if (1 + 1 !== 2) { + throw new Error('arithmetic is broken under Bun'); + } +}); +` + +async function main(): Promise { + if (typeof Bun === 'undefined') { + throw new Error('spike must be executed with `bun`, not node') + } + + // Sanity-check that the worker helpers are real functions under Bun. + for (const [name, value] of Object.entries({ + init, + runBaseTests, + setupEnvironment, + })) { + if (typeof value !== 'function') { + throw new Error(`vitest/worker export "${name}" did not load under Bun`) + } + } + + const collected: SpikeTest[] = [] + ;(globalThis as Record).test = ( + name: string, + fn: () => unknown, + ) => { + collected.push({ name, fn }) + } + + // The non-vm evaluator Vitest's plain pool relies on. + const runner = new ModuleRunner( + { + transport: { + async invoke(payload: any) { + const { name, data } = payload.data + if (name === 'getBuiltins') { + return { result: [] } + } + if (name === 'fetchModule') { + const [id] = data as [string] + if (id.includes('virtual-spike')) { + return { + result: { + code: TRANSFORMED_MODULE, + file: null, + id: ENTRY, + url: ENTRY, + invalidate: false, + }, + } + } + return { error: { message: `unexpected module request: ${id}` } } + } + return { error: { message: `unsupported invoke: ${name}` } } + }, + }, + hmr: false, + sourcemapInterceptor: false, + }, + new ESModulesEvaluator(), + ) + + await runner.import(ENTRY) + console.log(`collected ${collected.length} test(s)`) + + let passed = 0 + let failed = 0 + for (const t of collected) { + try { + await t.fn() + passed += 1 + console.log(` ✓ ${t.name}`) + } catch (error) { + failed += 1 + console.log(` ✗ ${t.name}: ${(error as Error).message}`) + } + } + + console.log(`result: ${passed} passed, ${failed} failed`) + await runner.close() + + if (failed > 0 || passed === 0) { + process.exit(1) + } + console.log('STOP gate 0: PASSED (test result round-tripped inside Bun)') +} + +main().catch((error) => { + console.error('STOP gate 0: FAILED') + console.error(error) + process.exit(1) +}) diff --git a/vitest-pool-bun/src/channel.ts b/vitest-pool-bun/src/channel.ts new file mode 100644 index 000000000..3f4f9bab4 --- /dev/null +++ b/vitest-pool-bun/src/channel.ts @@ -0,0 +1,44 @@ +/** + * IPC serialization helpers shared by the host (`pool-worker.ts`) and the Bun + * worker (`worker-entry.ts`). + * + * Why this exists (execution plan §6.2): + * + * Node <-> Bun IPC only supports JSON serialization (`serialization: "json"`). + * Structured-clone ("advanced") serialization, which Vitest's built-in `forks` + * pool relies on, only works Bun <-> Bun. That means: + * + * - We cannot lean on structured clone to ship rich objects across the channel. + * - Node's JSON IPC throws synchronously on circular references, which would + * tear the channel down. Vitest's runtime RPC routinely sends object graphs + * (task trees, serialized errors) that may contain cycles. + * + * `flatted` encodes arbitrary (including circular) object graphs into a plain + * string. We send that string across the JSON channel, so the only thing the + * native IPC layer ever has to serialize is a primitive string — always safe. + * + * The transform is symmetric and registered on both ends: + * - worker `init({ serialize: encode, deserialize: decode })` + * - host `BunPoolWorker.send` calls `encode`, `BunPoolWorker.deserialize` + * calls `decode`. + */ +import { parse, stringify } from 'flatted' + +/** Encode an outbound message into a JSON-channel-safe string. */ +export function encode(value: unknown): string { + return stringify(value) +} + +/** + * Decode an inbound message produced by {@link encode}. + * + * Accepts the raw value delivered by the IPC `message` event. When the channel + * already handed us the encoded string we parse it; any non-string value is + * passed through unchanged so the function is safe to call defensively. + */ +export function decode(data: unknown): unknown { + if (typeof data === 'string') { + return parse(data) + } + return data +} diff --git a/vitest-pool-bun/src/index.ts b/vitest-pool-bun/src/index.ts new file mode 100644 index 000000000..d8d47cc7b --- /dev/null +++ b/vitest-pool-bun/src/index.ts @@ -0,0 +1,72 @@ +/** + * vitest-pool-bun — a custom Vitest 4.1 pool that executes test files inside + * the Bun runtime. + * + * The Node host owns the Vite transform pipeline; Bun owns test execution. + * + * @example Minimal usage + * ```ts + * // vitest.config.ts + * import { defineBunPoolConfig } from 'vitest-pool-bun' + * + * export default defineBunPoolConfig({ + * test: { include: ['tests/**\/*.test.ts'] }, + * }) + * ``` + * + * @example Wiring the pool directly + * ```ts + * import { defineConfig } from 'vitest/config' + * import { bunPool } from 'vitest-pool-bun' + * + * export default defineConfig({ + * test: { pool: bunPool() }, + * }) + * ``` + */ +import { defineConfig, type ViteUserConfig } from 'vitest/config' +import type { PoolRunnerInitializer } from 'vitest/node' +import { BunPoolWorker } from './pool-worker.js' +import type { BunPoolOptions } from './types.js' + +export type { BunPoolOptions } from './types.js' +export { BunPoolWorker } from './pool-worker.js' + +/** + * Create the {@link PoolRunnerInitializer} that runs test files in Bun. + * + * Pass the result to Vitest's `test.pool` option. The `name` (`"bun"`) must + * match the worker's name — Vitest routes a test file to this pool when its + * resolved pool name equals `"bun"`. + */ +export function bunPool(options: BunPoolOptions = {}): PoolRunnerInitializer { + return { + name: 'bun', + createPoolWorker: (poolOptions) => new BunPoolWorker(poolOptions, options), + } +} + +/** Options for {@link defineBunPoolConfig}. */ +export interface DefineBunPoolConfigOptions { + /** Options forwarded to {@link bunPool}. */ + pool?: BunPoolOptions +} + +/** + * Convenience wrapper around `defineConfig` that wires {@link bunPool} into + * `test.pool` so users can keep their `vitest.config.ts` minimal. + * + * Any `test.pool` supplied in `config` is overridden by the Bun pool. + */ +export function defineBunPoolConfig( + config: ViteUserConfig = {}, + options: DefineBunPoolConfigOptions = {}, +): ViteUserConfig { + return defineConfig({ + ...config, + test: { + ...config.test, + pool: bunPool(options.pool), + }, + }) as ViteUserConfig +} diff --git a/vitest-pool-bun/src/pool-worker.ts b/vitest-pool-bun/src/pool-worker.ts new file mode 100644 index 000000000..ec3f83a05 --- /dev/null +++ b/vitest-pool-bun/src/pool-worker.ts @@ -0,0 +1,198 @@ +/** + * Host-side pool worker — runs under **Node** (the Vitest orchestrator process). + * + * Responsibilities (execution plan §2 "Host / orchestrator"): + * - Spawn one Bun process running `dist/worker-entry.js`. + * - Bridge Vitest's `PoolWorker` contract (`send`/`on`/`off`/`start`/`stop`/ + * `deserialize`) to that Bun child over a JSON-only IPC channel. + * + * The implementation deliberately mirrors Vitest's built-in `ForksPoolWorker` + * (it is the proven Node-host <-> child-runner reference), with three Bun-specific + * differences: + * 1. The child is `bun`, not `node`, so we use `child_process.spawn` with an + * explicit `ipc` stdio slot instead of `fork`. + * 2. The IPC channel is forced to `serialization: "json"` because structured + * clone is unavailable for Node <-> Bun (plan §6.2). + * 3. Outbound messages are encoded with flatted inside {@link BunPoolWorker.send} + * and inbound messages decoded in {@link BunPoolWorker.deserialize}, because + * `PoolWorker` exposes no host-side `serialize` hook. + */ +import type { ChildProcess } from 'node:child_process' +import { spawn } from 'node:child_process' +import { fileURLToPath } from 'node:url' +import type { PoolOptions, PoolWorker, WorkerRequest } from 'vitest/node' +import { decode, encode } from './channel.js' +import type { BunPoolOptions } from './types.js' + +/** Grace period before a worker that ignored SIGTERM is force-killed. */ +const SIGKILL_TIMEOUT = 1000 + +/** Number of trailing stderr bytes retained for failure diagnostics. */ +const STDERR_TAIL_LIMIT = 8 * 1024 + +function resolveBunBinary(opts: BunPoolOptions): string { + return opts.bunBinary ?? process.env.BUN_BINARY ?? 'bun' +} + +function resolveWorkerEntry(): string { + // Resolves to the compiled sibling `dist/worker-entry.js`. + return fileURLToPath(new URL('./worker-entry.js', import.meta.url)) +} + +export class BunPoolWorker implements PoolWorker { + readonly name = 'bun' + // Vitest may reuse a worker for the next file when `isolate: false`; the + // runtime caches transformed modules on the host so this stays safe. + readonly cacheFs = true + + private readonly bunBinary: string + private readonly workerEntry: string + private readonly env: NodeJS.ProcessEnv + private readonly bunArgs: string[] + private readonly stdout: NodeJS.WritableStream + private readonly stderr: NodeJS.WritableStream + + private child: ChildProcess | undefined + /** Rolling tail of the child's stderr, surfaced when the child dies badly. */ + private stderrTail = '' + + constructor(options: PoolOptions, poolOptions: BunPoolOptions) { + this.bunBinary = resolveBunBinary(poolOptions) + this.bunArgs = poolOptions.bunArgs ?? [] + this.workerEntry = resolveWorkerEntry() + this.env = { + ...options.env, + ...poolOptions.env, + } as NodeJS.ProcessEnv + const logger = options.project.vitest.logger + this.stdout = logger.outputStream as unknown as NodeJS.WritableStream + this.stderr = logger.errorStream as unknown as NodeJS.WritableStream + } + + send(message: WorkerRequest): void { + this.requireChild().send(encode(message)) + } + + on(event: string, callback: (arg: any) => void): void { + this.requireChild().on(event, callback) + } + + off(event: string, callback: (arg: any) => void): void { + this.requireChild().off(event, callback) + } + + /** Inbound transform: decode the flatted payload back into an object. */ + deserialize(data: unknown): unknown { + return decode(data) + } + + async start(): Promise { + if (this.child) { + return + } + const child = spawn(this.bunBinary, ['run', ...this.bunArgs, this.workerEntry], { + env: this.env, + // ['stdin', 'stdout', 'stderr', 'ipc']. JSON serialization is mandatory + // for Node <-> Bun IPC; Bun honours NODE_CHANNEL_SERIALIZATION_MODE that + // Node sets for the child when this option is used. + stdio: ['ignore', 'pipe', 'pipe', 'ipc'], + serialization: 'json', + }) + this.child = child + + if (child.stdout) { + child.stdout.pipe(this.stdout, { end: false }) + } + if (child.stderr) { + child.stderr.on('data', (chunk: Buffer) => { + this.captureStderr(chunk) + }) + child.stderr.pipe(this.stderr, { end: false }) + } + + // Disambiguate "successfully spawned" from "failed to spawn" (e.g. `bun` + // not on PATH). Without this, an async ENOENT fires before Vitest attaches + // its own 'error' listener, and the host hangs until a start timeout. + await new Promise((resolve, reject) => { + const cleanup = () => { + child.off('spawn', onSpawn) + child.off('error', onError) + } + const onSpawn = () => { + cleanup() + resolve() + } + const onError = (error: NodeJS.ErrnoException) => { + cleanup() + this.child = undefined + this.augmentSpawnError(error) + reject(error) + } + child.once('spawn', onSpawn) + child.once('error', onError) + }) + + // Improve the message of any late, post-spawn process error before Vitest's + // PoolRunner reports it (it reads `error.message`). + child.on('error', (error: NodeJS.ErrnoException) => { + this.augmentSpawnError(error) + }) + } + + async stop(): Promise { + const child = this.child + if (!child) { + return + } + const waitForExit = new Promise((resolve) => { + if (child.exitCode != null || child.signalCode != null) { + resolve() + } else { + child.once('exit', () => resolve()) + } + }) + const sigkill = setTimeout(() => child.kill('SIGKILL'), SIGKILL_TIMEOUT) + child.kill() + await waitForExit + clearTimeout(sigkill) + if (child.stdout) { + child.stdout.unpipe(this.stdout) + } + if (child.stderr) { + child.stderr.unpipe(this.stderr) + } + this.child = undefined + } + + private captureStderr(chunk: Buffer): void { + this.stderrTail = (this.stderrTail + chunk.toString('utf-8')).slice( + -STDERR_TAIL_LIMIT, + ) + } + + /** + * Turn an opaque `spawn` failure (most commonly: `bun` not on PATH) into an + * actionable error before Vitest reports a generic "Worker emitted error". + */ + private augmentSpawnError(error: NodeJS.ErrnoException): void { + if (error.code === 'ENOENT') { + error.message = + `[vitest-pool-bun]: failed to spawn the Bun runtime ("${this.bunBinary}"). ` + + 'Install Bun (https://bun.sh) and ensure it is on PATH, or set the ' + + '`bunBinary` pool option / BUN_BINARY environment variable.' + } + const tail = this.stderrTail.trim() + if (tail) { + error.message += `\nBun worker stderr (last ${STDERR_TAIL_LIMIT} bytes):\n${tail}` + } + } + + private requireChild(): ChildProcess { + if (!this.child) { + throw new Error( + '[vitest-pool-bun]: the Bun worker process was torn down or never started.', + ) + } + return this.child + } +} diff --git a/vitest-pool-bun/src/types.ts b/vitest-pool-bun/src/types.ts new file mode 100644 index 000000000..2996f1616 --- /dev/null +++ b/vitest-pool-bun/src/types.ts @@ -0,0 +1,28 @@ +/** + * Public options for the Bun pool. + * + * Kept intentionally small for the proof of concept. Anything not required to + * hit a phase acceptance gate is deferred (see the execution plan, §1 non-goals). + */ +export interface BunPoolOptions { + /** + * Path (or bare command name) of the `bun` executable used to spawn workers. + * + * Resolution order when not provided: + * 1. `process.env.BUN_BINARY` + * 2. the literal `"bun"` (resolved through `PATH`) + */ + bunBinary?: string + + /** + * Extra CLI arguments passed to `bun run `. + * Defaults to `[]`. + */ + bunArgs?: string[] + + /** + * Additional environment variables for the spawned Bun worker process. + * Merged on top of the environment Vitest provides for the task. + */ + env?: Record +} diff --git a/vitest-pool-bun/src/worker-entry.ts b/vitest-pool-bun/src/worker-entry.ts new file mode 100644 index 000000000..15b720347 --- /dev/null +++ b/vitest-pool-bun/src/worker-entry.ts @@ -0,0 +1,59 @@ +/** + * Worker entry point — this file runs **inside the Bun runtime**. + * + * It mirrors Vitest's built-in `workers/forks.ts` wiring, but talks over a + * JSON-only Node <-> Bun IPC channel (see `channel.ts`). The heavy lifting — + * driving Vitest's module runner, fetching transformed modules from the Node + * host over RPC, evaluating them, and running the test callbacks — is done by + * `runBaseTests`/`setupEnvironment` from `vitest/worker`. Our only job here is + * to plumb the channel correctly. + * + * The Node host launches this via `bun run dist/worker-entry.js` with an IPC + * file descriptor, so `process.send` / `process.on("message")` are available + * and behave like Node's `child_process.fork` API. + */ +import { init, runBaseTests, setupEnvironment } from 'vitest/worker' +import { decode, encode } from './channel.js' + +if (typeof process.send !== 'function') { + throw new Error( + '[vitest-pool-bun]: worker entry expected to be spawned with an IPC channel ' + + '(stdio "ipc"); process.send is unavailable.', + ) +} + +// Capture references in case user code overwrites the globals (matches the +// defensive approach taken by Vitest's own forks worker). +const processExit = process.exit.bind(process) +const processSend = process.send.bind(process) +const processOn = process.on.bind(process) +const processOff = process.off.bind(process) +const processRemoveAllListeners = process.removeAllListeners.bind(process) + +function onError(error: unknown): void { + const code = (error as { code?: string } | undefined)?.code + // Avoid an infinite loop of "try to report error -> channel is closed -> error". + if (code === 'ERR_IPC_CHANNEL_CLOSED' || code === 'EPIPE') { + processExit(1) + } +} + +processOn('error', onError) + +init({ + // worker -> host + post: (response) => processSend(response), + // host -> worker + on: (cb) => processOn('message', cb), + off: (cb) => processOff('message', cb), + teardown: () => { + processRemoveAllListeners('message') + processOff('error', onError) + }, + // JSON-only channel: encode everything through flatted (see channel.ts). + serialize: (value) => encode(value), + deserialize: (value) => decode(value), + runTests: (state, traces) => runBaseTests('run', state, traces), + collectTests: (state, traces) => runBaseTests('collect', state, traces), + setup: setupEnvironment, +}) diff --git a/vitest-pool-bun/tsconfig.json b/vitest-pool-bun/tsconfig.json new file mode 100644 index 000000000..570688db7 --- /dev/null +++ b/vitest-pool-bun/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2023"], + "types": ["node"], + "declaration": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "verbatimModuleSyntax": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist", "spikes", "examples"] +}