From dcc49cd70ed7958f328aee4c06d3e7d02a62dadb Mon Sep 17 00:00:00 2001 From: Edgars Date: Thu, 9 Jul 2026 16:02:34 +0100 Subject: [PATCH] test(wallet): Tier-2 browser-signing e2e (Playwright + mock provider on anvil) Add a headless, deterministic Tier-2 e2e harness that drives the real bridge page (bridgePage.ts) in chromium with an injected mock window.ethereum. eth_sendTransaction is delegated to Node via page.exposeFunction(__glSign), where a viem local account (anvil dev key) actually signs+broadcasts to an ephemeral anvil, so the full loop runs for real: bridge + page JS + session daemon + chain, with zero human/extension. Lanes (all green on anvil): - S1 connect/status/disconnect (descriptor 0600, /api/ping, teardown) - S2 validator-join --wallet browser signs + mines against a recording StakingStub, asserted via real receipt + on-chain callCount - S4 session reuse: two sequential joins over one daemon/tab - S5 config default walletMode=browser; --wallet keystore overrides - S6 user-reject (4001) + tab-closed fail-fast (short env timeouts) Lane B (S3, IC deploy on Docker localnet) is deferred as a nightly follow-up: describe.skip guarded by GENLAYER_E2E_LOCALNET. Prod-safe support changes: GENLAYER_E2E_* env overrides for the timing constants (unset in prod) and a GENLAYER_E2E_NO_OPEN guard so the daemon does not auto-open a system browser under test. New e2e-wallet.yml CI job (informational, not a required gate initially). Playwright specs are kept out of the vitest glob so test:coverage is unaffected. --- .github/workflows/e2e-wallet.yml | 63 +++++++ .gitignore | 7 +- e2e/config-default.e2e.ts | 69 ++++++++ e2e/errors.e2e.ts | 128 ++++++++++++++ e2e/fixtures/StakingStub.json | 114 ++++++++++++ e2e/fixtures/StakingStub.sol | 33 ++++ e2e/fixtures/chain.ts | 141 +++++++++++++++ e2e/fixtures/cli.ts | 257 +++++++++++++++++++++++++++ e2e/fixtures/mockProvider.ts | 64 +++++++ e2e/helpers/bridgePage.ts | 116 +++++++++++++ e2e/lane-a-staking.e2e.ts | 89 ++++++++++ e2e/lane-b-deploy.e2e.ts | 25 +++ e2e/wallet-session.e2e.ts | 105 +++++++++++ package-lock.json | 269 +++++++++++++++++++++++++++++ package.json | 4 + playwright.config.ts | 18 ++ src/lib/wallet/browserBridge.ts | 11 +- src/lib/wallet/sessionConstants.ts | 21 ++- vitest.config.ts | 4 +- 19 files changed, 1529 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/e2e-wallet.yml create mode 100644 e2e/config-default.e2e.ts create mode 100644 e2e/errors.e2e.ts create mode 100644 e2e/fixtures/StakingStub.json create mode 100644 e2e/fixtures/StakingStub.sol create mode 100644 e2e/fixtures/chain.ts create mode 100644 e2e/fixtures/cli.ts create mode 100644 e2e/fixtures/mockProvider.ts create mode 100644 e2e/helpers/bridgePage.ts create mode 100644 e2e/lane-a-staking.e2e.ts create mode 100644 e2e/lane-b-deploy.e2e.ts create mode 100644 e2e/wallet-session.e2e.ts create mode 100644 playwright.config.ts diff --git a/.github/workflows/e2e-wallet.yml b/.github/workflows/e2e-wallet.yml new file mode 100644 index 00000000..0af08e2f --- /dev/null +++ b/.github/workflows/e2e-wallet.yml @@ -0,0 +1,63 @@ +# Tier-2 browser-wallet signing e2e (Playwright + mock provider on anvil). +# +# INFORMATIONAL / NOT A REQUIRED GATE (initially). This job drives a headless +# chromium against the real bridge page with a viem-backed mock window.ethereum +# that signs+broadcasts to an ephemeral anvil, exercising the full +# connect -> sign -> receipt loop with zero MetaMask. Keep it OFF the required +# status checks in branch protection until it has proven stable across a few +# weeks of PRs, then promote it to required. +# +# Distinct filename from the synced cross-repo e2e.yml (the /run-e2e cucumber +# pipeline) so the two never collide. +name: E2E Wallet (Tier 2) + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + - v0.40 + - v0.40-dev + +jobs: + e2e-wallet: + name: Browser-wallet e2e (anvil lanes) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install libsecret runtime + run: sudo apt-get update && sudo apt-get install -y libsecret-1-0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Build the project + run: npm run build + + - name: Install Foundry (anvil) + uses: foundry-rs/foundry-toolchain@v1 + + - name: Install Playwright chromium + run: npx playwright install --with-deps chromium + + - name: Run Tier-2 e2e (anvil lanes) + run: npm run test:e2e + + - name: Upload Playwright report + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: | + playwright-report/ + test-results/ + retention-days: 7 + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index bbf43fff..d3c698e5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,9 @@ node_modules dist .idea coverage -.ollama \ No newline at end of file +.ollama + +# Playwright (Tier-2 e2e) artifacts +test-results +playwright-report +.playwright \ No newline at end of file diff --git a/e2e/config-default.e2e.ts b/e2e/config-default.e2e.ts new file mode 100644 index 00000000..8de7bbe0 --- /dev/null +++ b/e2e/config-default.e2e.ts @@ -0,0 +1,69 @@ +import {test, expect, type Browser} from "@playwright/test"; +import {startAnvil, readStubCallCount, type AnvilHandle} from "./fixtures/chain"; +import {makeScratchEnv, runCli, readDescriptor, isPidAlive, type ScratchEnv} from "./fixtures/cli"; +import {launchBrowser, establishSession, type BridgeDriver} from "./helpers/bridgePage"; + +/** + * S5 — config default `walletMode=browser`. With the config set and a live + * session, a bare `validator-join` (no --wallet) signs via the browser session; + * an explicit `--wallet keystore` overrides it and takes the keystore path + * (which errors here because no keystore exists — proving it did NOT enqueue). + */ +const CHAIN_ID = 61343; +const HASH_RE = /0x[0-9a-fA-F]{64}/; + +test.describe.serial("S5 config default walletMode=browser", () => { + let anvil: AnvilHandle; + let browser: Browser; + let driver: BridgeDriver; + let scratch: ScratchEnv; + + test.beforeAll(async () => { + anvil = await startAnvil({chainId: CHAIN_ID}); + browser = await launchBrowser(); + scratch = makeScratchEnv({ + chainId: CHAIN_ID, + rpcUrl: anvil.rpcUrl, + stubAddress: anvil.stubAddress, + walletMode: "browser", + timing: {longPollMs: 1000}, + }); + ({driver} = await establishSession(browser, anvil, scratch)); + }); + + test.afterAll(async () => { + await driver?.close().catch(() => {}); + const d = readDescriptor(scratch); + if (d && isPidAlive(d.pid)) { + try { + process.kill(d.pid, "SIGKILL"); + } catch { + /* gone */ + } + } + await anvil?.stop(); + }); + + test("bare validator-join signs via session (no --wallet flag)", async () => { + const before = await readStubCallCount(anvil); + const res = await runCli(["staking", "validator-join", "--amount", "1"], scratch); + expect(res.all).toContain("Validator created successfully!"); + expect(res.all.match(HASH_RE)?.[0]).toBeTruthy(); + expect(await readStubCallCount(anvil)).toBe(before + 1); + }); + + test("--wallet keystore overrides config, takes keystore path (no enqueue)", async () => { + const before = await readStubCallCount(anvil); + const res = await runCli( + ["staking", "validator-join", "--amount", "1", "--wallet", "keystore"], + scratch, + ); + // Keystore path selected: it fails on the missing account rather than + // signing via the browser session. + expect(res.all).not.toContain("Validator created successfully!"); + expect(res.all.toLowerCase()).toContain("not found"); + expect(res.exitCode).not.toBe(0); + // The browser session was never used → no new recorded call on the stub. + expect(await readStubCallCount(anvil)).toBe(before); + }); +}); diff --git a/e2e/errors.e2e.ts b/e2e/errors.e2e.ts new file mode 100644 index 00000000..63a1aacb --- /dev/null +++ b/e2e/errors.e2e.ts @@ -0,0 +1,128 @@ +import {test, expect, type Browser} from "@playwright/test"; +import {startAnvil, type AnvilHandle} from "./fixtures/chain"; +import { + makeScratchEnv, + runCli, + readDescriptor, + daemonGet, + isPidAlive, + waitUntil, + type ScratchEnv, +} from "./fixtures/cli"; +import {launchBrowser, establishSession, type BridgeDriver} from "./helpers/bridgePage"; + +/** + * S6 (subset) — deterministic error lanes with short timing overrides so they + * resolve in seconds: + * - user reject (4001): CLI surfaces "Transaction rejected in wallet", exits + * non-zero, and the session stays usable (daemon still pinging). + * - tab closed: page.close() → heartbeat goes stale → the next command fails + * fast with "tab appears to be closed", never hanging on a dead tab. + */ + +test.describe.serial("S6a user reject (4001)", () => { + const CHAIN_ID = 61344; + let anvil: AnvilHandle; + let browser: Browser; + let driver: BridgeDriver; + let scratch: ScratchEnv; + + test.beforeAll(async () => { + anvil = await startAnvil({chainId: CHAIN_ID}); + browser = await launchBrowser(); + scratch = makeScratchEnv({ + chainId: CHAIN_ID, + rpcUrl: anvil.rpcUrl, + stubAddress: anvil.stubAddress, + timing: {longPollMs: 1000}, + }); + ({driver} = await establishSession(browser, anvil, scratch, {behavior: "reject"})); + }); + + test.afterAll(async () => { + await driver?.close().catch(() => {}); + const d = readDescriptor(scratch); + if (d && isPidAlive(d.pid)) { + try { + process.kill(d.pid, "SIGKILL"); + } catch { + /* gone */ + } + } + await anvil?.stop(); + }); + + test("reject surfaces the message, exits non-zero, session survives", async () => { + const res = await runCli( + ["staking", "validator-join", "--amount", "1", "--wallet", "browser"], + scratch, + ); + expect(res.all).toContain("Transaction rejected in wallet"); + expect(res.exitCode).not.toBe(0); + + // Session stays usable: descriptor present, daemon still answers /api/ping. + const d = readDescriptor(scratch); + expect(d).not.toBeNull(); + expect(isPidAlive(d!.pid)).toBe(true); + const ping = await daemonGet(d!, "/api/ping"); + expect(ping.status).toBe(200); + }); +}); + +test.describe.serial("S6b tab closed", () => { + const CHAIN_ID = 61345; + const HEARTBEAT_DEAD_MS = 2000; + let anvil: AnvilHandle; + let browser: Browser; + let driver: BridgeDriver; + let scratch: ScratchEnv; + + test.beforeAll(async () => { + anvil = await startAnvil({chainId: CHAIN_ID}); + browser = await launchBrowser(); + scratch = makeScratchEnv({ + chainId: CHAIN_ID, + rpcUrl: anvil.rpcUrl, + stubAddress: anvil.stubAddress, + timing: {longPollMs: 500, heartbeatDeadMs: HEARTBEAT_DEAD_MS}, + }); + ({driver} = await establishSession(browser, anvil, scratch)); + }); + + test.afterAll(async () => { + await driver?.close().catch(() => {}); + const d = readDescriptor(scratch); + if (d && isPidAlive(d.pid)) { + try { + process.kill(d.pid, "SIGKILL"); + } catch { + /* gone */ + } + } + await anvil?.stop(); + }); + + test("closed tab → fail-fast 'tab appears to be closed'", async () => { + const d = readDescriptor(scratch)!; + await driver.closePage(); + + // Wait for the page heartbeat to go stale (no more polls after close). + const stale = await waitUntil( + async () => { + const {body} = await daemonGet(d, "/api/state"); + const last = (body as {lastPagePollAt?: number}).lastPagePollAt ?? 0; + return last > 0 && Date.now() - last > HEARTBEAT_DEAD_MS; + }, + {timeoutMs: 10_000, intervalMs: 200}, + ); + expect(stale, "heartbeat should go stale after tab close").toBe(true); + + const res = await runCli( + ["staking", "validator-join", "--amount", "1", "--wallet", "browser"], + scratch, + {timeoutMs: 20_000}, + ); + expect(res.all.toLowerCase()).toContain("tab appears to be closed"); + expect(res.exitCode).not.toBe(0); + }); +}); diff --git a/e2e/fixtures/StakingStub.json b/e2e/fixtures/StakingStub.json new file mode 100644 index 00000000..1cd8d180 --- /dev/null +++ b/e2e/fixtures/StakingStub.json @@ -0,0 +1,114 @@ +{ + "abi": [ + { + "type": "function", + "name": "callCount", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "lastAmount", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "lastOperator", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "lastValidator", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "validatorJoin", + "inputs": [ + { + "name": "_operator", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "validatorJoin", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "ValidatorJoin", + "inputs": [ + { + "name": "operator", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "validator", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + } + ], + "bytecode": "0x6080604052348015600e575f5ffd5b5061050e8061001c5f395ff3fe608060405260043610610054575f3560e01c806301fe0a781461005857806330e7349f1461008857806330ebe468146100b25780634b28f9a2146100d05780636eb3cd49146100fa578063829a86d914610124575b5f5ffd5b610072600480360381019061006d919061032f565b61014e565b60405161007f9190610369565b60405180910390f35b348015610093575f5ffd5b5061009c61015f565b6040516100a99190610369565b60405180910390f35b6100ba610184565b6040516100c79190610369565b60405180910390f35b3480156100db575f5ffd5b506100e4610193565b6040516100f1919061039a565b60405180910390f35b348015610105575f5ffd5b5061010e610198565b60405161011b9190610369565b60405180910390f35b34801561012f575f5ffd5b506101386101bd565b604051610145919061039a565b60405180910390f35b5f610158826101c3565b9050919050565b60025f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f61018e336101c3565b905090565b5f5481565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60035481565b5f60015f5f8282546101d591906103e0565b92505081905550335f546040516020016101f0929190610478565b604051602081830303815290604052805190602001205f1c90508160015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060025f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550346003819055507f2b7297b31452d0a13e57c605870ec3c9b4ac6ef33e5cbae7a0f00bc2a3e967828282346040516102c4939291906104a3565b60405180910390a1919050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6102fe826102d5565b9050919050565b61030e816102f4565b8114610318575f5ffd5b50565b5f8135905061032981610305565b92915050565b5f60208284031215610344576103436102d1565b5b5f6103518482850161031b565b91505092915050565b610363816102f4565b82525050565b5f60208201905061037c5f83018461035a565b92915050565b5f819050919050565b61039481610382565b82525050565b5f6020820190506103ad5f83018461038b565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6103ea82610382565b91506103f583610382565b925082820190508082111561040d5761040c6103b3565b5b92915050565b5f8160601b9050919050565b5f61042982610413565b9050919050565b5f61043a8261041f565b9050919050565b61045261044d826102f4565b610430565b82525050565b5f819050919050565b61047261046d82610382565b610458565b82525050565b5f6104838285610441565b6014820191506104938284610461565b6020820191508190509392505050565b5f6060820190506104b65f83018661035a565b6104c3602083018561035a565b6104d0604083018461038b565b94935050505056fea26469706673582212202ea3a8dac16f254ee61dc37f81eb395845a459b3af57d33fab7a95c141e032b664736f6c63430008210033" +} diff --git a/e2e/fixtures/StakingStub.sol b/e2e/fixtures/StakingStub.sol new file mode 100644 index 00000000..758043ee --- /dev/null +++ b/e2e/fixtures/StakingStub.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/// Minimal recording stub for the Tier-2 browser-wallet e2e harness. +/// Mimics the Staking `validatorJoin` selectors and emits the ValidatorJoin +/// event (address operator, address validator, uint256 amount) the CLI decodes. +/// It stands up NO consensus; it only records the call so the sign->broadcast +/// ->receipt loop can be asserted end to end. +contract StakingStub { + event ValidatorJoin(address operator, address validator, uint256 amount); + + uint256 public callCount; + address public lastOperator; + address public lastValidator; + uint256 public lastAmount; + + function validatorJoin() external payable returns (address) { + return _join(msg.sender); + } + + function validatorJoin(address _operator) external payable returns (address) { + return _join(_operator); + } + + function _join(address operator) internal returns (address validator) { + callCount += 1; + validator = address(uint160(uint256(keccak256(abi.encodePacked(msg.sender, callCount))))); + lastOperator = operator; + lastValidator = validator; + lastAmount = msg.value; + emit ValidatorJoin(operator, validator, msg.value); + } +} diff --git a/e2e/fixtures/chain.ts b/e2e/fixtures/chain.ts new file mode 100644 index 00000000..940ce78d --- /dev/null +++ b/e2e/fixtures/chain.ts @@ -0,0 +1,141 @@ +/** + * Ephemeral anvil fixture for the Tier-2 anvil lanes. + * + * Boots a private `anvil` on a random port, deploys the recording StakingStub + * (e2e/fixtures/StakingStub.sol) with anvil dev key #0, and exposes the RPC + + * signer + stub address. Deterministic: anvil auto-mines instantly and the key + * is the well-known anvil account #0, so there is no live-network flakiness. + */ +import {spawn, type ChildProcess} from "node:child_process"; +import {createWalletClient, createPublicClient, http, type Hex} from "viem"; +import {privateKeyToAccount} from "viem/accounts"; +import {readFileSync} from "node:fs"; +import {fileURLToPath} from "node:url"; +import {dirname, resolve} from "node:path"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +/** anvil dev account #0 (public, well-known test key — never used off-anvil). */ +export const ANVIL_KEY_0 = + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" as const; +export const ANVIL_ADDRESS_0 = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" as const; + +const STUB_ARTIFACT = JSON.parse( + readFileSync(resolve(__dirname, "StakingStub.json"), "utf-8"), +) as {abi: unknown[]; bytecode: Hex}; + +export interface AnvilHandle { + rpcUrl: string; + port: number; + chainId: number; + account: {address: `0x${string}`; privateKey: `0x${string}`}; + /** Deployed StakingStub address (the validator-join target). */ + stubAddress: `0x${string}`; + stop: () => Promise; +} + +function makeChain(chainId: number, rpcUrl: string) { + return { + id: chainId, + name: `anvil-e2e-${chainId}`, + nativeCurrency: {name: "Ether", symbol: "ETH", decimals: 18}, + rpcUrls: {default: {http: [rpcUrl]}}, + } as const; +} + +/** Boot anvil, wait until it is listening, deploy the stub, return the handle. */ +export async function startAnvil(opts: {chainId: number}): Promise { + const child: ChildProcess = spawn( + "anvil", + ["--port", "0", "--chain-id", String(opts.chainId), "--accounts", "3"], + {stdio: ["ignore", "pipe", "pipe"]}, + ); + + const port = await new Promise((resolvePort, reject) => { + const timer = setTimeout(() => reject(new Error("anvil did not report a port within 15s")), 15_000); + let buf = ""; + const onData = (chunk: Buffer) => { + buf += chunk.toString(); + const m = buf.match(/Listening on 127\.0\.0\.1:(\d+)/); + if (m) { + clearTimeout(timer); + child.stdout?.off("data", onData); + resolvePort(Number(m[1])); + } + }; + child.stdout?.on("data", onData); + child.once("error", err => { + clearTimeout(timer); + reject(err); + }); + child.once("exit", code => { + clearTimeout(timer); + reject(new Error(`anvil exited early (code ${code})`)); + }); + }); + + const rpcUrl = `http://127.0.0.1:${port}`; + const account = privateKeyToAccount(ANVIL_KEY_0); + const chain = makeChain(opts.chainId, rpcUrl); + + const walletClient = createWalletClient({account, chain, transport: http(rpcUrl)}); + const publicClient = createPublicClient({chain, transport: http(rpcUrl)}); + + // Deploy the recording stub. + const deployHash = await walletClient.deployContract({ + abi: STUB_ARTIFACT.abi as never, + bytecode: STUB_ARTIFACT.bytecode, + account, + chain, + }); + const deployReceipt = await publicClient.waitForTransactionReceipt({hash: deployHash}); + const stubAddress = deployReceipt.contractAddress; + if (!stubAddress) throw new Error("StakingStub deployment produced no contract address"); + + const stop = async (): Promise => { + await new Promise(res => { + if (child.exitCode !== null || child.signalCode) return res(); + child.once("exit", () => res()); + child.kill("SIGKILL"); + // Safety net if the exit event never fires. + setTimeout(() => res(), 2000); + }); + }; + + return { + rpcUrl, + port, + chainId: opts.chainId, + account: {address: ANVIL_ADDRESS_0, privateKey: ANVIL_KEY_0}, + stubAddress: stubAddress as `0x${string}`, + stop, + }; +} + +const STUB_READ_ABI = [ + {name: "callCount", type: "function", stateMutability: "view", inputs: [], outputs: [{type: "uint256"}]}, +] as const; + +/** Read the StakingStub's recorded validator-join count. */ +export async function readStubCallCount(anvil: AnvilHandle): Promise { + const client = createPublicClient({ + chain: makeChain(anvil.chainId, anvil.rpcUrl), + transport: http(anvil.rpcUrl), + }); + const count = await client.readContract({ + address: anvil.stubAddress, + abi: STUB_READ_ABI, + functionName: "callCount", + }); + return Number(count); +} + +/** Assert a tx mined successfully on the anvil chain. */ +export async function receiptSucceeded(anvil: AnvilHandle, hash: `0x${string}`): Promise { + const client = createPublicClient({ + chain: makeChain(anvil.chainId, anvil.rpcUrl), + transport: http(anvil.rpcUrl), + }); + const receipt = await client.getTransactionReceipt({hash}); + return receipt.status === "success"; +} diff --git a/e2e/fixtures/cli.ts b/e2e/fixtures/cli.ts new file mode 100644 index 00000000..11f3a917 --- /dev/null +++ b/e2e/fixtures/cli.ts @@ -0,0 +1,257 @@ +/** + * Hermetic CLI-process fixture for the Tier-2 e2e lanes. + * + * Every command runs the built `dist/index.js` in a child process with a scratch + * HOME, so the session descriptor + config file live under a throwaway + * `/.genlayer` and never touch the developer's real `~/.genlayer` or the + * live network. The scratch config is seeded with a custom network whose + * chain-id / rpc / staking address point at the ephemeral anvil, so + * `ensureChain()` matches and `validator-join` targets the recording stub. + */ +import {spawn, type ChildProcess} from "node:child_process"; +import {mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, statSync} from "node:fs"; +import {tmpdir} from "node:os"; +import {join, resolve} from "node:path"; +import {fileURLToPath} from "node:url"; +import {dirname} from "node:path"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = resolve(__dirname, "..", ".."); +const CLI_ENTRY = join(REPO_ROOT, "dist", "index.js"); + +export const NETWORK_ALIAS = "anvil-e2e"; + +/** Short timing budgets so the error/tab-closed lanes resolve in seconds. */ +export interface TimingOverrides { + longPollMs?: number; + heartbeatDeadMs?: number; + connectTimeoutMs?: number; +} + +export interface ScratchEnv { + home: string; + env: NodeJS.ProcessEnv; + descriptorPath: string; +} + +/** + * Create a scratch HOME with a seeded config pointing at the given anvil chain. + * The returned `env` is passed to every runCli/spawnConnect in the same test. + */ +export function makeScratchEnv(opts: { + chainId: number; + rpcUrl: string; + stubAddress: `0x${string}`; + walletMode?: "browser" | "keystore"; + timing?: TimingOverrides; +}): ScratchEnv { + const home = mkdtempSync(join(tmpdir(), "gl-e2e-")); + const genlayerDir = join(home, ".genlayer"); + mkdirSync(genlayerDir, {recursive: true}); + + const config: Record = { + network: NETWORK_ALIAS, + customNetworks: { + [NETWORK_ALIAS]: { + base: "localnet", + overrides: { + rpcUrl: opts.rpcUrl, + chainId: opts.chainId, + staking: opts.stubAddress, + }, + }, + }, + }; + if (opts.walletMode) config.walletMode = opts.walletMode; + writeFileSync(join(genlayerDir, "genlayer-config.json"), JSON.stringify(config, null, 2)); + + const timing = opts.timing ?? {}; + const env: NodeJS.ProcessEnv = { + ...process.env, + HOME: home, + // Keep chalk/ora output plain so stdout scraping is reliable. + NO_COLOR: "1", + FORCE_COLOR: "0", + // Never auto-open a real browser: the harness drives its own chromium. + GENLAYER_E2E_NO_OPEN: "1", + }; + if (timing.longPollMs) env.GENLAYER_E2E_LONG_POLL_MS = String(timing.longPollMs); + if (timing.heartbeatDeadMs) env.GENLAYER_E2E_HEARTBEAT_DEAD_MS = String(timing.heartbeatDeadMs); + if (timing.connectTimeoutMs) env.GENLAYER_E2E_CONNECT_TIMEOUT_MS = String(timing.connectTimeoutMs); + + return {home, env, descriptorPath: join(genlayerDir, "wallet-session.json")}; +} + +export interface CliResult { + exitCode: number; + stdout: string; + stderr: string; + all: string; +} + +/** Run a CLI command to completion and capture its output + exit code. */ +export function runCli( + args: string[], + scratch: ScratchEnv, + opts: {timeoutMs?: number} = {}, +): Promise { + const timeoutMs = opts.timeoutMs ?? 60_000; + return new Promise((resolvePromise, reject) => { + const child = spawn(process.execPath, [CLI_ENTRY, ...args], {env: scratch.env}); + let stdout = ""; + let stderr = ""; + let all = ""; + child.stdout.on("data", d => { + stdout += d; + all += d; + }); + child.stderr.on("data", d => { + stderr += d; + all += d; + }); + const timer = setTimeout(() => { + child.kill("SIGKILL"); + reject(new Error(`runCli timed out after ${timeoutMs}ms: ${args.join(" ")}\n${all}`)); + }, timeoutMs); + child.once("error", err => { + clearTimeout(timer); + reject(err); + }); + child.once("exit", code => { + clearTimeout(timer); + resolvePromise({exitCode: code ?? 0, stdout, stderr, all}); + }); + }); +} + +export interface ConnectHandle { + child: ChildProcess; + /** Resolves with the bridge session URL scraped from stdout. */ + waitForUrl(timeoutMs?: number): Promise; + /** Resolves when the connect command reports a successful connection. */ + waitForConnected(timeoutMs?: number): Promise; + kill(): void; +} + +const URL_RE = /http:\/\/127\.0\.0\.1:\d+\/#s=[0-9a-fA-F-]+/; + +/** + * Spawn `wallet connect` (which blocks until the browser connects) and expose + * hooks to scrape the printed session URL and await the "Connected as ..." line. + */ +export function spawnConnect(args: string[], scratch: ScratchEnv): ConnectHandle { + const child = spawn(process.execPath, [CLI_ENTRY, "wallet", "connect", ...args], {env: scratch.env}); + let buf = ""; + const listeners: Array<(chunk: string) => void> = []; + const onData = (d: Buffer) => { + const s = d.toString(); + buf += s; + for (const l of listeners) l(buf); + }; + child.stdout.on("data", onData); + child.stderr.on("data", onData); + + const waitFor = (re: RegExp, label: string, timeoutMs: number): Promise => + new Promise((resolvePromise, reject) => { + const check = (text: string) => { + const m = text.match(re); + if (m) { + const idx = listeners.indexOf(check as never); + if (idx >= 0) listeners.splice(idx, 1); + clearTimeout(timer); + resolvePromise(m[0]); + return true; + } + return false; + }; + const timer = setTimeout( + () => reject(new Error(`Timed out waiting for ${label}. Output so far:\n${buf}`)), + timeoutMs, + ); + child.once("exit", () => { + if (!check(buf)) reject(new Error(`connect exited before ${label}. Output:\n${buf}`)); + }); + if (check(buf)) return; + listeners.push(check as never); + }); + + return { + child, + waitForUrl: (timeoutMs = 30_000) => waitFor(URL_RE, "session URL", timeoutMs), + waitForConnected: (timeoutMs = 30_000) => + waitFor(/Connected as (0x[0-9a-fA-F]{40})/, "connection", timeoutMs), + kill: () => { + try { + child.kill("SIGKILL"); + } catch { + // already gone + } + }, + }; +} + +// --- Descriptor / daemon inspection helpers --------------------------------- + +export interface Descriptor { + version: number; + pid: number; + port: number; + token: string; + address: string | null; + chainId: number; + network: string; + rpcUrl: string; + createdAt: number; + lastUsed: number; +} + +export function readDescriptor(scratch: ScratchEnv): Descriptor | null { + if (!existsSync(scratch.descriptorPath)) return null; + try { + return JSON.parse(readFileSync(scratch.descriptorPath, "utf-8")) as Descriptor; + } catch { + return null; + } +} + +/** Octal file mode (e.g. 0o600 → 384) of the descriptor, or null if absent. */ +export function descriptorMode(scratch: ScratchEnv): number | null { + if (!existsSync(scratch.descriptorPath)) return null; + return statSync(scratch.descriptorPath).mode & 0o777; +} + +export function isPidAlive(pid: number): boolean { + try { + process.kill(pid, 0); + return true; + } catch (e: unknown) { + return (e as {code?: string})?.code === "EPERM"; + } +} + +/** Authenticated GET against the running daemon (127.0.0.1:). */ +export async function daemonGet( + descriptor: Descriptor, + path: string, +): Promise<{status: number; body: unknown}> { + const res = await fetch(`http://127.0.0.1:${descriptor.port}${path}`, { + headers: {"X-Bridge-Token": descriptor.token}, + }); + const body = await res.json().catch(() => ({})); + return {status: res.status, body}; +} + +/** Poll until a predicate holds or the deadline passes. */ +export async function waitUntil( + predicate: () => boolean | Promise, + opts: {timeoutMs?: number; intervalMs?: number} = {}, +): Promise { + const timeoutMs = opts.timeoutMs ?? 10_000; + const intervalMs = opts.intervalMs ?? 100; + const deadline = Date.now() + timeoutMs; + for (;;) { + if (await predicate()) return true; + if (Date.now() > deadline) return false; + await new Promise(r => setTimeout(r, intervalMs)); + } +} diff --git a/e2e/fixtures/mockProvider.ts b/e2e/fixtures/mockProvider.ts new file mode 100644 index 00000000..b2ad8ad4 --- /dev/null +++ b/e2e/fixtures/mockProvider.ts @@ -0,0 +1,64 @@ +/** + * Injected `window.ethereum` for the Tier-2 browser-signing e2e lanes. + * + * `installMockProvider` returns a self-invoking script string that Playwright + * installs via `page.addInitScript(...)` BEFORE `bridgePage.ts` runs, so the + * mock always wins the injection race (design §4/§9). The mock is a thin + * EIP-1193 shim: account/chain queries are answered locally, and the one + * privileged operation — `eth_sendTransaction` — is delegated to Node via + * `window.__glSign` (wired in helpers/bridgePage.ts), where a real viem local + * account signs and broadcasts to a real anvil and returns a real tx hash. + * + * No key or RPC ever lives in page JS. `behavior` gives deterministic error + * lanes without any human/extension: + * - "approve" — sign+broadcast for real (happy path) + * - "reject" — throw 4001 (user rejected) on eth_sendTransaction + * - "wrong-network" — throw 4901 on wallet_switchEthereumChain + */ +export interface MockProviderOptions { + address: `0x${string}`; + /** Must match GenLayerChain.id targeted by the bridge's ensureChain(). */ + chainIdHex: string; + behavior?: "approve" | "reject" | "wrong-network"; +} + +export const installMockProvider = (opts: MockProviderOptions): string => { + const behavior = opts.behavior ?? "approve"; + return ` + (() => { + let currentChain = ${JSON.stringify(opts.chainIdHex)}; + const ADDR = ${JSON.stringify(opts.address)}; + window.ethereum = { + isMetaMask: true, + _l: {}, + on(ev, cb) { (this._l[ev] = this._l[ev] || []).push(cb); }, + removeListener() {}, + async request({ method, params }) { + switch (method) { + case "eth_requestAccounts": + case "eth_accounts": + return [ADDR]; + case "eth_chainId": + return currentChain; + case "wallet_switchEthereumChain": + ${ + behavior === "wrong-network" + ? 'throw { code: 4901, message: "Wallet is disconnected from the requested chain." };' + : "currentChain = params[0].chainId; return null;" + } + case "wallet_addEthereumChain": + return null; + case "eth_sendTransaction": + ${ + behavior === "reject" + ? 'throw { code: 4001, message: "User rejected the request." };' + : "return await window.__glSign(params[0]);" + } + default: + throw { code: 4200, message: "unsupported " + method }; + } + }, + }; + })(); + `; +}; diff --git a/e2e/helpers/bridgePage.ts b/e2e/helpers/bridgePage.ts new file mode 100644 index 00000000..cbe1b8ce --- /dev/null +++ b/e2e/helpers/bridgePage.ts @@ -0,0 +1,116 @@ +/** + * Playwright driver for the real bridge page (src/lib/wallet/bridgePage.ts). + * + * Launches a headless chromium, injects the mock `window.ethereum` BEFORE the + * page loads, and wires the page's `window.__glSign` hook to a Node-side viem + * local account that actually signs + broadcasts `eth_sendTransaction` to the + * ephemeral anvil and returns a real tx hash. The full loop therefore runs for + * real — bridge + served page JS + session daemon + chain — with zero human and + * zero extension. + */ +import {chromium, type Browser, type Page} from "@playwright/test"; +import {createWalletClient, http, type Hex} from "viem"; +import {privateKeyToAccount} from "viem/accounts"; +import {installMockProvider, type MockProviderOptions} from "../fixtures/mockProvider"; +import {spawnConnect, type ScratchEnv} from "../fixtures/cli"; +import type {AnvilHandle} from "../fixtures/chain"; + +export interface DriverOptions { + rpcUrl: string; + chainId: number; + privateKey: `0x${string}`; + behavior?: MockProviderOptions["behavior"]; +} + +export interface BridgeDriver { + page: Page; + /** Load the URL with the mock installed and click "Connect wallet". */ + connect(sessionUrl: string): Promise; + /** Close just the tab (simulates the user closing the wallet tab). */ + closePage(): Promise; + /** Tear down the whole browser. */ + close(): Promise; +} + +/** Launch one headless chromium; callers open a driver per session. */ +export async function launchBrowser(): Promise { + return chromium.launch({headless: true}); +} + +export async function openDriver(browser: Browser, opts: DriverOptions): Promise { + const page = await browser.newPage(); + + const account = privateKeyToAccount(opts.privateKey); + const chain = { + id: opts.chainId, + name: `anvil-e2e-${opts.chainId}`, + nativeCurrency: {name: "Ether", symbol: "ETH", decimals: 18}, + rpcUrls: {default: {http: [opts.rpcUrl]}}, + } as const; + const wallet = createWalletClient({account, chain, transport: http(opts.rpcUrl)}); + + // Real signing happens in Node, returning a real on-chain hash. + await page.exposeFunction( + "__glSign", + async (tx: {to: Hex; data?: Hex; value?: string; gas?: string}): Promise => { + const hash = await wallet.sendTransaction({ + account, + chain, + to: tx.to, + data: (tx.data ?? "0x") as Hex, + value: tx.value ? BigInt(tx.value) : undefined, + gas: tx.gas ? BigInt(tx.gas) : undefined, + }); + return hash; + }, + ); + + await page.addInitScript( + installMockProvider({ + address: account.address, + chainIdHex: `0x${opts.chainId.toString(16)}`, + behavior: opts.behavior, + }), + ); + + const connect = async (sessionUrl: string): Promise => { + await page.goto(sessionUrl); + // The page shows the "Connect wallet" button once /api/session resolves. + await page.waitForSelector("#action:not([style*='display: none'])", {timeout: 15_000}); + await page.click("#action"); + }; + + return { + page, + connect, + closePage: () => page.close(), + close: () => browser.close(), + }; +} + +/** + * Full connect dance shared by the lane specs: spawn `wallet connect`, scrape + * its URL, drive the mock page to approve, and wait for "Connected as ...". + * Returns the live driver (keep it open so the page keeps its heartbeat) and + * the connected signer address. + */ +export async function establishSession( + browser: Browser, + anvil: AnvilHandle, + scratch: ScratchEnv, + opts: {behavior?: MockProviderOptions["behavior"]} = {}, +): Promise<{driver: BridgeDriver; address: string}> { + const connect = spawnConnect([], scratch); + const url = await connect.waitForUrl(); + const driver = await openDriver(browser, { + rpcUrl: anvil.rpcUrl, + chainId: anvil.chainId, + privateKey: anvil.account.privateKey, + behavior: opts.behavior, + }); + await driver.connect(url); + const line = await connect.waitForConnected(); + await new Promise(res => connect.child.once("exit", () => res())); + const address = line.match(/0x[0-9a-fA-F]{40}/)?.[0] ?? ""; + return {driver, address}; +} diff --git a/e2e/lane-a-staking.e2e.ts b/e2e/lane-a-staking.e2e.ts new file mode 100644 index 00000000..e95abb31 --- /dev/null +++ b/e2e/lane-a-staking.e2e.ts @@ -0,0 +1,89 @@ +import {test, expect, type Browser} from "@playwright/test"; +import {startAnvil, readStubCallCount, receiptSucceeded, type AnvilHandle} from "./fixtures/chain"; +import {makeScratchEnv, runCli, readDescriptor, isPidAlive, type ScratchEnv} from "./fixtures/cli"; +import {launchBrowser, establishSession, type BridgeDriver} from "./helpers/bridgePage"; + +/** + * Lane A — real sign -> broadcast -> receipt loop against a recording + * StakingStub on anvil. Proves the browser wallet actually signs and the CLI + * observes a real receipt; it does NOT stand up consensus. + * + * S2: one `validator-join --wallet browser` signs and succeeds. + * S4: session reuse — two more sequential joins over the same daemon/tab. + */ +const CHAIN_ID = 61342; +const HASH_RE = /0x[0-9a-fA-F]{64}/; + +test.describe.serial("Lane A staking (validator-join)", () => { + let anvil: AnvilHandle; + let browser: Browser; + let driver: BridgeDriver; + let scratch: ScratchEnv; + + test.beforeAll(async () => { + anvil = await startAnvil({chainId: CHAIN_ID}); + browser = await launchBrowser(); + scratch = makeScratchEnv({ + chainId: CHAIN_ID, + rpcUrl: anvil.rpcUrl, + stubAddress: anvil.stubAddress, + timing: {longPollMs: 1000}, + }); + ({driver} = await establishSession(browser, anvil, scratch)); + }); + + test.afterAll(async () => { + await driver?.close().catch(() => {}); + const d = readDescriptor(scratch); + if (d && isPidAlive(d.pid)) { + try { + process.kill(d.pid, "SIGKILL"); + } catch { + /* gone */ + } + } + await anvil?.stop(); + }); + + test("S2: validator-join --wallet browser signs and mines", async () => { + const before = await readStubCallCount(anvil); + const res = await runCli( + ["staking", "validator-join", "--amount", "1", "--wallet", "browser"], + scratch, + ); + + expect(res.all).toContain("Validator created successfully!"); + const hash = res.all.match(HASH_RE)?.[0] as `0x${string}` | undefined; + expect(hash, "a tx hash should be printed").toBeTruthy(); + expect(await receiptSucceeded(anvil, hash!)).toBe(true); + expect(await readStubCallCount(anvil)).toBe(before + 1); + }); + + test("S4: session reuse — two sequential joins, one tab, distinct hashes", async () => { + const d0 = readDescriptor(scratch); + expect(d0).not.toBeNull(); + const before = await readStubCallCount(anvil); + + const first = await runCli( + ["staking", "validator-join", "--amount", "1", "--wallet", "browser"], + scratch, + ); + const second = await runCli( + ["staking", "validator-join", "--amount", "2", "--wallet", "browser"], + scratch, + ); + + const h1 = first.all.match(HASH_RE)?.[0] as `0x${string}` | undefined; + const h2 = second.all.match(HASH_RE)?.[0] as `0x${string}` | undefined; + expect(h1).toBeTruthy(); + expect(h2).toBeTruthy(); + expect(h1).not.toBe(h2); + expect(await receiptSucceeded(anvil, h1!)).toBe(true); + expect(await receiptSucceeded(anvil, h2!)).toBe(true); + + // Same daemon reused: pid unchanged, single descriptor, both calls recorded. + const d1 = readDescriptor(scratch); + expect(d1!.pid).toBe(d0!.pid); + expect(await readStubCallCount(anvil)).toBe(before + 2); + }); +}); diff --git a/e2e/lane-b-deploy.e2e.ts b/e2e/lane-b-deploy.e2e.ts new file mode 100644 index 00000000..053448eb --- /dev/null +++ b/e2e/lane-b-deploy.e2e.ts @@ -0,0 +1,25 @@ +import {test} from "@playwright/test"; + +/** + * S3 — Lane B: intelligent-contract deploy/write over the browser session. + * + * DEFERRED (nightly / Docker follow-up per design §3, §6). IC deploy goes + * through genlayer-js against ConsensusMain, which only exists on the Docker + * `localnet` studio node (id 61127, RPC :4000/api) — a bare anvil has no + * GenVM/consensus, so this lane cannot run on the per-PR anvil harness. + * + * When implemented, this describe block boots `genlayer localnet up`, connects + * the session against localnet, deploys a trivial `.py` IC fixture through the + * session's eip1193Provider, and asserts the returned contract address + a + * read-back. It is guarded behind GENLAYER_E2E_LOCALNET so it never runs (and + * never fails) on the standard anvil CI job. + */ +const LOCALNET_ENABLED = process.env.GENLAYER_E2E_LOCALNET === "1"; + +test.describe.skip("S3 Lane B IC deploy on Docker localnet (nightly)", () => { + test("deploy .py IC over the browser session", async () => { + // Intentionally unimplemented; see file header. Enable with + // GENLAYER_E2E_LOCALNET=1 and a running Docker localnet. + void LOCALNET_ENABLED; + }); +}); diff --git a/e2e/wallet-session.e2e.ts b/e2e/wallet-session.e2e.ts new file mode 100644 index 00000000..75e35358 --- /dev/null +++ b/e2e/wallet-session.e2e.ts @@ -0,0 +1,105 @@ +import {test, expect, type Browser} from "@playwright/test"; +import {startAnvil, type AnvilHandle} from "./fixtures/chain"; +import { + makeScratchEnv, + spawnConnect, + runCli, + readDescriptor, + descriptorMode, + daemonGet, + isPidAlive, + waitUntil, + type ScratchEnv, +} from "./fixtures/cli"; +import {launchBrowser, openDriver, type BridgeDriver} from "./helpers/bridgePage"; + +/** + * S1 — connect / status / disconnect on anvil. One serial session: connect + * once, inspect it, then tear it down. Assertions target the descriptor file, + * the daemon HTTP surface, and the CLI stdout — not brittle DOM text. + */ +const CHAIN_ID = 61341; + +test.describe.serial("S1 wallet session lifecycle", () => { + let anvil: AnvilHandle; + let browser: Browser; + let driver: BridgeDriver; + let scratch: ScratchEnv; + let daemonPid: number; + + test.beforeAll(async () => { + anvil = await startAnvil({chainId: CHAIN_ID}); + browser = await launchBrowser(); + scratch = makeScratchEnv({ + chainId: CHAIN_ID, + rpcUrl: anvil.rpcUrl, + stubAddress: anvil.stubAddress, + timing: {longPollMs: 1000}, + }); + }); + + test.afterAll(async () => { + await driver?.close().catch(() => {}); + if (daemonPid && isPidAlive(daemonPid)) { + try { + process.kill(daemonPid, "SIGKILL"); + } catch { + /* gone */ + } + } + await anvil?.stop(); + }); + + test("connect: descriptor 0600, daemon reachable, Connected as ...", async () => { + const connect = spawnConnect([], scratch); + const url = await connect.waitForUrl(); + expect(url).toMatch(/#s=/); + + driver = await openDriver(browser, { + rpcUrl: anvil.rpcUrl, + chainId: CHAIN_ID, + privateKey: anvil.account.privateKey, + }); + await driver.connect(url); + + const line = await connect.waitForConnected(); + expect(line.toLowerCase()).toContain(anvil.account.address.toLowerCase()); + await new Promise(res => connect.child.once("exit", () => res())); + + const d = readDescriptor(scratch); + expect(d, "descriptor should exist after connect").not.toBeNull(); + daemonPid = d!.pid; + expect(isPidAlive(d!.pid)).toBe(true); + expect(d!.port).toBeGreaterThan(0); + expect(d!.token).toBeTruthy(); + expect(d!.chainId).toBe(CHAIN_ID); + expect((d!.address ?? "").toLowerCase()).toBe(anvil.account.address.toLowerCase()); + expect(descriptorMode(scratch)).toBe(0o600); + + const ping = await daemonGet(d!, "/api/ping"); + expect(ping.status).toBe(200); + expect((ping.body as {status?: string}).status).toBe("ok"); + }); + + test("status: reports address, connected, empty queue", async () => { + const res = await runCli(["wallet", "status"], scratch); + expect(res.exitCode).toBe(0); + expect(res.all.toLowerCase()).toContain(anvil.account.address.toLowerCase()); + expect(res.all).toContain("connected"); + expect(res.all).toMatch(/queuedTransactions|queued/i); + }); + + test("disconnect: descriptor removed, daemon gone, status reports none", async () => { + const res = await runCli(["wallet", "disconnect"], scratch); + expect(res.all).toContain("Disconnected"); + + const gone = await waitUntil(() => readDescriptor(scratch) === null && !isPidAlive(daemonPid), { + timeoutMs: 8000, + }); + expect(gone, "descriptor removed and daemon pid gone").toBe(true); + + const status = await runCli(["wallet", "status"], scratch); + expect(status.all).toContain("No active wallet session"); + expect(status.exitCode).toBe(1); + }); +}); diff --git a/package-lock.json b/package-lock.json index f7601984..c2dfceca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "genlayer": "dist/index.js" }, "devDependencies": { + "@playwright/test": "^1.61.1", "@release-it/conventional-changelog": "^10.0.1", "@types/dockerode": "^3.3.31", "@types/fs-extra": "^11.0.4", @@ -809,6 +810,7 @@ "version": "4.8.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz", "integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==", + "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -827,6 +829,7 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -836,6 +839,7 @@ "version": "0.21.0", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.6", @@ -850,6 +854,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -860,6 +865,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -872,6 +878,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -881,6 +888,7 @@ "version": "0.15.2", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -893,6 +901,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -916,6 +925,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -926,6 +936,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -935,6 +946,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -947,6 +959,7 @@ "version": "9.34.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -959,6 +972,7 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -968,6 +982,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.15.2", @@ -1012,6 +1027,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -1021,6 +1037,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -1034,6 +1051,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -1047,6 +1065,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -1060,6 +1079,7 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -1859,6 +1879,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1947,6 +1983,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, "license": "MIT" }, "node_modules/@scure/base": { @@ -2140,12 +2177,14 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, "license": "MIT" }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/jsonfile": { @@ -2922,6 +2961,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", "peer": true, "bin": { @@ -2935,6 +2975,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -2980,6 +3021,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -3045,12 +3087,14 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -3074,6 +3118,7 @@ "version": "3.1.9", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -3096,6 +3141,7 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -3117,6 +3163,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -3135,6 +3182,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -3153,6 +3201,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -3217,6 +3266,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3236,6 +3286,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -3251,6 +3302,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -3443,6 +3495,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -3461,6 +3514,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3474,6 +3528,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3490,6 +3545,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3792,6 +3848,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -4071,6 +4128,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -4122,6 +4180,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -4139,6 +4198,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -4156,6 +4216,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -4231,6 +4292,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, "license": "MIT" }, "node_modules/default-browser": { @@ -4265,6 +4327,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -4294,6 +4357,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -4406,6 +4470,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" @@ -4443,6 +4508,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4492,6 +4558,7 @@ "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -4560,6 +4627,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4569,6 +4637,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4584,6 +4653,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4596,6 +4666,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4611,6 +4682,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -4623,6 +4695,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7", @@ -4690,6 +4763,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -4724,7 +4798,9 @@ "version": "9.34.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -4825,6 +4901,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7", @@ -4836,6 +4913,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -4880,6 +4958,7 @@ "version": "2.12.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7" @@ -4897,6 +4976,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -4906,7 +4986,9 @@ "version": "2.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -4939,6 +5021,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -4949,6 +5032,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.1" @@ -4958,6 +5042,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -4970,6 +5055,7 @@ "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" @@ -4979,6 +5065,7 @@ "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -4995,6 +5082,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -5007,6 +5095,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -5017,6 +5106,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5033,6 +5123,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5045,6 +5136,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -5054,6 +5146,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -5066,6 +5159,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", @@ -5083,6 +5177,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5109,6 +5204,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -5121,6 +5217,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -5133,6 +5230,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -5151,6 +5249,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -5305,6 +5404,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -5341,12 +5441,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, "license": "MIT" }, "node_modules/fastq": { @@ -5386,6 +5488,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -5411,6 +5514,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -5440,6 +5544,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -5453,12 +5558,14 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, "license": "ISC" }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -5537,6 +5644,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5546,6 +5654,7 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5566,6 +5675,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5574,6 +5684,7 @@ "node_modules/genlayer-js": { "version": "1.1.8", "resolved": "git+ssh://git@github.com/genlayerlabs/genlayer-js.git#57889281db1e34df49abcf6129ffe6f347e4de4d", + "dev": true, "license": "MIT", "dependencies": { "eslint-plugin-import": "^2.30.0", @@ -5606,6 +5717,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5630,6 +5742,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5656,6 +5769,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -5812,6 +5926,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -5824,6 +5939,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -5836,6 +5952,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, "license": "MIT", "dependencies": { "define-properties": "^1.2.1", @@ -5852,6 +5969,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5899,6 +6017,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5911,6 +6030,7 @@ "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" @@ -5920,6 +6040,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -5932,6 +6053,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.0" @@ -5947,6 +6069,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5959,6 +6082,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -5974,6 +6098,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6099,6 +6224,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -6115,6 +6241,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -6175,6 +6302,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6199,6 +6327,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -6216,6 +6345,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, "license": "MIT", "dependencies": { "async-function": "^1.0.0", @@ -6235,6 +6365,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, "license": "MIT", "dependencies": { "has-bigints": "^1.0.2" @@ -6250,6 +6381,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6276,6 +6408,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6288,6 +6421,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -6303,6 +6437,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6320,6 +6455,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6351,6 +6487,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6360,6 +6497,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -6384,6 +6522,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6402,6 +6541,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -6444,6 +6584,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6456,6 +6597,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6478,6 +6620,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6511,6 +6654,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6529,6 +6673,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6541,6 +6686,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -6579,6 +6725,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6595,6 +6742,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6612,6 +6760,7 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -6639,6 +6788,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6651,6 +6801,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -6666,6 +6817,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6697,12 +6849,14 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/isows": { @@ -6828,6 +6982,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -6903,24 +7058,28 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, "license": "MIT" }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.0" @@ -6957,6 +7116,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -6966,6 +7126,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -6979,6 +7140,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -7035,6 +7197,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, "license": "MIT" }, "node_modules/lodash.uniqby": { @@ -7152,6 +7315,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7370,6 +7534,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, "license": "MIT" }, "node_modules/neo-async": { @@ -7558,6 +7723,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7570,6 +7736,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7579,6 +7746,7 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -7599,6 +7767,7 @@ "version": "2.0.8", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -7617,6 +7786,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -7631,6 +7801,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -7698,6 +7869,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -7755,6 +7927,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.6", @@ -7835,6 +8008,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -7850,6 +8024,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -7906,6 +8081,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -7986,6 +8162,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7995,6 +8172,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8004,6 +8182,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/path-scurry": { @@ -8076,10 +8255,58 @@ "pathe": "^2.0.3" } }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8144,6 +8371,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -8247,6 +8475,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -8404,6 +8633,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -8426,6 +8656,7 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -8553,6 +8784,7 @@ "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -8573,6 +8805,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -8710,6 +8943,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -8749,6 +8983,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8765,6 +9000,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8814,6 +9050,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -8831,6 +9068,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -8846,6 +9084,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -8860,6 +9099,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -8872,6 +9112,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8881,6 +9122,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8900,6 +9142,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8916,6 +9159,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8934,6 +9178,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9171,6 +9416,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9256,6 +9502,7 @@ "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -9277,6 +9524,7 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -9295,6 +9543,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -9351,6 +9600,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -9373,6 +9623,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9397,6 +9648,7 @@ "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" @@ -9409,6 +9661,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9673,6 +9926,7 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", @@ -9710,6 +9964,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -9734,6 +9989,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -9748,6 +10004,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -9767,6 +10024,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -9788,6 +10046,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -9830,6 +10089,7 @@ "version": "0.3.4", "resolved": "https://registry.npmjs.org/typescript-parsec/-/typescript-parsec-0.3.4.tgz", "integrity": "sha512-6RD4xOxp26BTZLopNbqT2iErqNhQZZWb5m5F07/UwGhldGvOAKOl41pZ3fxsFp04bNL+PbgMjNfb6IvJAC/uYQ==", + "dev": true, "license": "MIT" }, "node_modules/uglify-js": { @@ -9850,6 +10110,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -9959,6 +10220,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -10379,6 +10641,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -10394,6 +10657,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, "license": "MIT", "dependencies": { "is-bigint": "^1.1.0", @@ -10413,6 +10677,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -10440,6 +10705,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, "license": "MIT", "dependencies": { "is-map": "^2.0.3", @@ -10458,6 +10724,7 @@ "version": "1.1.19", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -10518,6 +10785,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10800,6 +11068,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 187d8fa9..32805c2e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "test:watch": "vitest --watch", "test:coverage": "vitest run --coverage", "test:smoke": "vitest run --config vitest.smoke.config.ts", + "test:e2e": "playwright test -c playwright.config.ts", + "test:e2e:lane-a": "playwright test -c playwright.config.ts e2e/lane-a-staking.e2e.ts", + "test:e2e:full": "GENLAYER_E2E_LOCALNET=1 playwright test -c playwright.config.ts", "dev": "node scripts/run-esbuild.mjs development", "build": "node scripts/run-esbuild.mjs production", "prepare": "npm run build", @@ -38,6 +41,7 @@ }, "homepage": "https://github.com/yeagerai/genlayer-cli#readme", "devDependencies": { + "@playwright/test": "^1.61.1", "@release-it/conventional-changelog": "^10.0.1", "@types/dockerode": "^3.3.31", "@types/fs-extra": "^11.0.4", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..3233f4a2 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,18 @@ +import {defineConfig} from "@playwright/test"; + +/** + * Tier-2 browser-wallet signing e2e (anvil lanes). Each spec boots an ephemeral + * anvil + spawns the real CLI daemon + drives a headless chromium against the + * real bridge page, so the runs must be serial: the session descriptor, the + * detached daemon, and the loopback bridge port must never race between specs. + */ +export default defineConfig({ + testDir: "./e2e", + testMatch: "**/*.e2e.ts", + fullyParallel: false, + workers: 1, + timeout: 60_000, + retries: 0, + reporter: [["list"]], + // No webServer: the fixtures own chain + daemon lifecycle. +}); diff --git a/src/lib/wallet/browserBridge.ts b/src/lib/wallet/browserBridge.ts index e4abfad2..6e9488b9 100644 --- a/src/lib/wallet/browserBridge.ts +++ b/src/lib/wallet/browserBridge.ts @@ -262,9 +262,14 @@ export class BrowserWalletBridge { process.once("SIGINT", this.sigintHandler); } - await this.openUrl(this.url).catch(() => { - // Non-fatal: user can open the URL manually (headless / SSH). - }); + // The Tier-2 e2e harness drives its own headless chromium against this URL, + // so auto-opening the system browser would just spawn a stray tab. Skipped + // only when the harness sets GENLAYER_E2E_NO_OPEN; production is unaffected. + if (!process.env.GENLAYER_E2E_NO_OPEN) { + await this.openUrl(this.url).catch(() => { + // Non-fatal: user can open the URL manually (headless / SSH). + }); + } return {url: this.url}; } diff --git a/src/lib/wallet/sessionConstants.ts b/src/lib/wallet/sessionConstants.ts index ca241437..5b550601 100644 --- a/src/lib/wallet/sessionConstants.ts +++ b/src/lib/wallet/sessionConstants.ts @@ -4,15 +4,30 @@ * heartbeat / liveness budgets never drift between producer and consumer. */ +/** + * Test-only escape hatch: the Tier-2 e2e harness needs the tab-closed / + * connect-timeout budgets to resolve in seconds, not minutes. A `GENLAYER_E2E_*` + * env override (positive integer, milliseconds) replaces the production default; + * when the env var is unset or invalid the production value is used unchanged, so + * normal runs are completely unaffected. Kept here (the single source of truth) + * so producer and consumer read the identical, possibly-overridden budget. + */ +function envMs(name: string, fallback: number): number { + const raw = typeof process !== "undefined" ? process.env?.[name] : undefined; + if (!raw) return fallback; + const n = Number(raw); + return Number.isFinite(n) && n > 0 ? n : fallback; +} + /** Page long-poll window (existing bridge behaviour). */ -export const LONG_POLL_MS = 25_000; +export const LONG_POLL_MS = envMs("GENLAYER_E2E_LONG_POLL_MS", 25_000); /** * Client + `/api/enqueue` treat the tab as closed after this much silence on * the page heartbeat (~3 missed long-poll windows; tolerates background-tab * throttling). Commands fail fast instead of hanging on a dead tab. */ -export const HEARTBEAT_DEAD_MS = 90_000; +export const HEARTBEAT_DEAD_MS = envMs("GENLAYER_E2E_HEARTBEAT_DEAD_MS", 90_000); /** * Surfaced when the page heartbeat has gone stale (tab closed / crashed). @@ -32,7 +47,7 @@ export const IDLE_TTL_MS = 30 * 60_000; export const DAEMON_READY_TIMEOUT_MS = 10_000; /** Wallet connect wait (existing bridge behaviour). */ -export const CONNECT_TIMEOUT_MS = 180_000; +export const CONNECT_TIMEOUT_MS = envMs("GENLAYER_E2E_CONNECT_TIMEOUT_MS", 180_000); /** Per-tx wallet confirmation wait (existing bridge behaviour). */ export const TX_TIMEOUT_MS = 300_000; diff --git a/vitest.config.ts b/vitest.config.ts index 44466610..bf23772f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,9 +6,9 @@ export default defineConfig({ environment: 'jsdom', testTimeout: 10000, setupFiles: ['tests/setup.ts'], - exclude: [...configDefaults.exclude, 'tests/smoke.test.ts'], + exclude: [...configDefaults.exclude, 'tests/smoke.test.ts', 'e2e/**'], coverage: { - exclude: [...configDefaults.exclude, '*.js', 'tests/**/*.ts', 'src/types', 'scripts', 'templates'], + exclude: [...configDefaults.exclude, '*.js', 'tests/**/*.ts', 'e2e/**', 'src/types', 'scripts', 'templates'], } } }); \ No newline at end of file