From 662dc8e24b1a6062e102cffec6b4b864e4c4dcd7 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:57:12 +0200 Subject: [PATCH 01/26] add e2e tests --- e2e/.env.example | 11 + e2e/.gitignore | 3 + e2e/helpers/client.ts | 19 + e2e/helpers/clipboard.ts | 9 + e2e/helpers/connection.ts | 29 + e2e/helpers/coreApi.ts | 178 ++ e2e/helpers/enrollment.ts | 56 + e2e/helpers/mfa.ts | 27 + e2e/helpers/totp.ts | 8 + e2e/helpers/tunnel.ts | 15 + e2e/helpers/windows.ts | 15 + e2e/helpers/wireguard.ts | 46 + e2e/package.json | 22 + e2e/pnpm-lock.yaml | 3894 ++++++++++++++++++++++++++++ e2e/pnpm-workspace.yaml | 2 + e2e/scripts/provision.mjs | 139 + e2e/tests/enrollment.spec.ts | 57 + e2e/tests/logs.spec.ts | 39 + e2e/tests/wireguard_tunnel.spec.ts | 58 + e2e/tsconfig.json | 13 + e2e/wdio.conf.ts | 124 + 21 files changed, 4764 insertions(+) create mode 100644 e2e/.env.example create mode 100644 e2e/.gitignore create mode 100644 e2e/helpers/client.ts create mode 100644 e2e/helpers/clipboard.ts create mode 100644 e2e/helpers/connection.ts create mode 100644 e2e/helpers/coreApi.ts create mode 100644 e2e/helpers/enrollment.ts create mode 100644 e2e/helpers/mfa.ts create mode 100644 e2e/helpers/totp.ts create mode 100644 e2e/helpers/tunnel.ts create mode 100644 e2e/helpers/windows.ts create mode 100644 e2e/helpers/wireguard.ts create mode 100644 e2e/package.json create mode 100644 e2e/pnpm-lock.yaml create mode 100644 e2e/pnpm-workspace.yaml create mode 100644 e2e/scripts/provision.mjs create mode 100644 e2e/tests/enrollment.spec.ts create mode 100644 e2e/tests/logs.spec.ts create mode 100644 e2e/tests/wireguard_tunnel.spec.ts create mode 100644 e2e/tsconfig.json create mode 100644 e2e/wdio.conf.ts diff --git a/e2e/.env.example b/e2e/.env.example new file mode 100644 index 00000000..914a830c --- /dev/null +++ b/e2e/.env.example @@ -0,0 +1,11 @@ +CORE_URL= +PROXY_URL= +CORE_ADMIN_USER=admin +CORE_ADMIN_PASSWORD= +TEST_USERNAME=e2e_test_user +GATEWAY_VPN_IP=10.10.10.1 +NETWORK_ENDPOINT= +NETWORK_NAME=e2e +NETWORK_ADDRESS=10.10.10.1/24 +NETWORK_PORT=50051 +NETWORK_ALLOWED_IPS=10.10.10.0/24 diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 00000000..b4ad2296 --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +logs/ +.env diff --git a/e2e/helpers/client.ts b/e2e/helpers/client.ts new file mode 100644 index 00000000..b24174f5 --- /dev/null +++ b/e2e/helpers/client.ts @@ -0,0 +1,19 @@ +import { $, browser } from '@wdio/globals'; +import { switchToFullView } from './windows.js'; + +type TauriWindow = { + __TAURI_INTERNALS__: { invoke: (cmd: string, args?: unknown) => Promise }; +}; + +export const resetInstances = async () => { + await switchToFullView(); + await $('a[href="/full/add"]').click(); + await $('#add-page-view').waitForDisplayed(); + await browser.execute(async () => { + const { invoke } = (window as unknown as TauriWindow).__TAURI_INTERNALS__; + const instances = (await invoke('all_instances')) as Array<{ id: number }>; + await Promise.all( + instances.map((instance) => invoke('delete_instance', { instanceId: instance.id })), + ); + }); +}; diff --git a/e2e/helpers/clipboard.ts b/e2e/helpers/clipboard.ts new file mode 100644 index 00000000..5846d58d --- /dev/null +++ b/e2e/helpers/clipboard.ts @@ -0,0 +1,9 @@ +import { spawnSync } from 'node:child_process'; + +export const readClipboard = (): string => { + const result = spawnSync('xclip', ['-selection', 'clipboard', '-o'], { + encoding: 'utf8', + timeout: 5_000, + }); + return result.status === 0 ? result.stdout : ''; +}; diff --git a/e2e/helpers/connection.ts b/e2e/helpers/connection.ts new file mode 100644 index 00000000..20510670 --- /dev/null +++ b/e2e/helpers/connection.ts @@ -0,0 +1,29 @@ +import { $, browser } from '@wdio/globals'; +import { submitTotpCode } from './mfa.js'; +import { canPingGateway } from './tunnel.js'; + +export const connectAndPing = async (totpSecret?: string) => { + const connect = $('.connect-button'); + await connect.waitForClickable(); + await connect.click(); + + if (totpSecret) { + await $('#mfa-totp-view').waitForDisplayed(); + await submitTotpCode( + totpSecret, + '#mfa-totp-view', + async () => { + const verify = $('#mfa-totp-view').$('button=Verify'); + await verify.waitForClickable(); + await verify.click(); + }, + () => $('#mfa-totp-view').isDisplayed().then((shown) => !shown, () => true), + ); + } + + await browser.waitUntil(() => canPingGateway(), { + timeout: 30_000, + interval: 2_000, + timeoutMsg: 'Could not ping the gateway through the VPN', + }); +}; diff --git a/e2e/helpers/coreApi.ts b/e2e/helpers/coreApi.ts new file mode 100644 index 00000000..f5c9a18b --- /dev/null +++ b/e2e/helpers/coreApi.ts @@ -0,0 +1,178 @@ +// Minimal client for the defguard core REST API, used to provision fixtures +// (users, enrollment tokens, location MFA mode) from within specs. + +const MIN_PEER_DISCONNECT_THRESHOLD_WITH_MFA = 120; + +const requireEnv = (name: string): string => { + const value = process.env[name]; + if (!value) { + throw new Error(`Missing required environment variable ${name}`); + } + return value; +}; + +const coreUrl = (): string => requireEnv('CORE_URL'); +const proxyUrl = (): string => requireEnv('PROXY_URL'); + +export type LocationMfaMode = 'disabled' | 'internal' | 'external'; + +export interface EnrollmentFixture { + username: string; + enrollmentToken: string; + enrollmentUrl: string; + // The test owns and deletes this user unless it is pinned via TEST_USERNAME. + ephemeral: boolean; +} + +export class CoreApi { + private cookie = ''; + + private async request(method: string, apiPath: string, body?: unknown): Promise { + const response = await fetch(`${coreUrl()}${apiPath}`, { + method, + redirect: 'manual', + headers: { + 'Content-Type': 'application/json', + ...(this.cookie ? { Cookie: this.cookie } : {}), + }, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + if (response.status >= 300 && response.status < 400) { + throw new Error(`Core API ${method} ${apiPath} redirected — check CORE_URL`); + } + if (!response.ok) { + throw new Error( + `Core API ${method} ${apiPath} failed: ${response.status} ${await response.text()}`, + ); + } + return response; + } + + async login(): Promise { + const response = await this.request('POST', '/api/v1/auth', { + username: process.env.CORE_ADMIN_USER ?? 'admin', + password: requireEnv('CORE_ADMIN_PASSWORD'), + }); + const setCookie = response.headers.get('set-cookie'); + if (!setCookie) { + throw new Error('Core API login did not return a session cookie'); + } + this.cookie = setCookie.split(';')[0]; + } + + async userExists(username: string): Promise { + const response = await fetch(`${coreUrl()}/api/v1/user/${username}`, { + redirect: 'manual', + headers: this.cookie ? { Cookie: this.cookie } : {}, + }); + return response.ok; + } + + async createUser(username: string): Promise { + await this.request('POST', '/api/v1/user', { + username, + first_name: 'E2E', + last_name: 'Test', + email: `${username}@e2e.test`, + }); + } + + async deleteUser(username: string): Promise { + await this.request('DELETE', `/api/v1/user/${username}`); + } + + async listNetworks(): Promise> { + const response = await this.request('GET', '/api/v1/network'); + return (await response.json()) as Array<{ id: number; location_mfa_mode: LocationMfaMode }>; + } + + async findAvailableIp(networkId: number): Promise { + const response = await this.request('GET', `/api/v1/device/network/ip/${networkId}`); + const ips = (await response.json()) as Array<{ ip: string }>; + return ips[0].ip; + } + + // Registers a standalone WireGuard device and returns its generated config. + async addNetworkDevice( + networkId: number, + name: string, + assignedIps: string[], + pubkey: string, + ): Promise { + const response = await this.request('POST', '/api/v1/device/network', { + name, + description: null, + location_id: networkId, + assigned_ips: assignedIps, + wireguard_pubkey: pubkey, + }); + const data = (await response.json()) as { config: { config: string } }; + return data.config.config; + } + + // Core reports settings.mfa_required (which drives the enrollment MFA steps) + // when a location uses 'internal' MFA. Returns the previous mode to restore. + async setLocationMfaMode(networkId: number, mode: LocationMfaMode): Promise { + const current = (await ( + await this.request('GET', `/api/v1/network/${networkId}`) + ).json()) as Record; + const previous = current.location_mfa_mode as LocationMfaMode; + if (previous === mode) { + return previous; + } + const joinList = (value: unknown): string => + Array.isArray(value) ? value.join(',') : ((value as string | null) ?? ''); + await this.request('PUT', `/api/v1/network/${networkId}`, { + name: current.name, + address: joinList(current.address), + endpoint: current.endpoint, + port: current.port, + allowed_ips: joinList(current.allowed_ips) || null, + dns: (current.dns as string | null) ?? null, + mtu: current.mtu, + fwmark: current.fwmark, + allow_all_groups: current.allow_all_groups, + allowed_groups: current.allowed_groups ?? [], + keepalive_interval: current.keepalive_interval, + peer_disconnect_threshold: Math.max( + Number(current.peer_disconnect_threshold ?? 0), + MIN_PEER_DISCONNECT_THRESHOLD_WITH_MFA, + ), + acl_enabled: current.acl_enabled, + acl_default_allow: current.acl_default_allow, + location_mfa_mode: mode, + service_location_mode: current.service_location_mode ?? 'disabled', + }); + return previous; + } + + private async startEnrollment(username: string, ephemeral: boolean): Promise { + const response = await this.request('POST', `/api/v1/user/${username}/start_enrollment`, { + send_enrollment_notification: false, + }); + const data = (await response.json()) as { enrollment_token: string }; + return { + username, + enrollmentToken: data.enrollment_token, + enrollmentUrl: proxyUrl(), + ephemeral, + }; + } + + // A user with a pending enrollment, always (re)created so it has not enrolled. + async createEnrollmentFixture(): Promise { + const pinned = process.env.TEST_USERNAME; + const username = pinned ?? `e2e${Math.floor(Math.random() * 1_000_000)}`; + if (await this.userExists(username)) { + await this.deleteUser(username); + } + await this.createUser(username); + return this.startEnrollment(username, !pinned); + } +} + +export const loggedInCoreApi = async (): Promise => { + const api = new CoreApi(); + await api.login(); + return api; +}; diff --git a/e2e/helpers/enrollment.ts b/e2e/helpers/enrollment.ts new file mode 100644 index 00000000..8dab14b1 --- /dev/null +++ b/e2e/helpers/enrollment.ts @@ -0,0 +1,56 @@ +import { $, expect } from '@wdio/globals'; +import type { EnrollmentFixture } from './coreApi.js'; +import { submitTotpCode } from './mfa.js'; +import { switchToFullView } from './windows.js'; + +export const password = 'E2eTestPassword123!'; + +const clickNext = async () => { + const next = $('.enroll-controls .right button'); + await next.waitForClickable(); + await next.click(); +}; + +export const addInstance = async (fixture: EnrollmentFixture) => { + await switchToFullView(); + const addCard = $('#add-page-view button'); + await addCard.waitForClickable(); + await addCard.click(); + await expect($('#add-instance-view')).toBeDisplayed(); + await $('[data-testid="field-url"]').setValue(fixture.enrollmentUrl); + await $('[data-testid="field-token"]').setValue(fixture.enrollmentToken); + const submit = $('#add-instance-view').$('button=Add Instance'); + await submit.waitForClickable(); + await submit.click(); + await expect($('#welcome-step')).toBeDisplayed(); +}; + +export const setPassword = async () => { + await clickNext(); + await expect($('#password-step')).toBeDisplayed(); + await $('[data-testid="field-password"]').setValue(password); + await $('[data-testid="field-repeat"]').setValue(password); + await clickNext(); +}; + +export const configureTotp = async (): Promise => { + await expect($('#mfa-configuration-step')).toBeDisplayed(); + const secretField = $('#mfa-configuration-step .copy-field .track p'); + await secretField.waitForExist(); + const raw = (await secretField.getProperty('textContent')) as string | null; + const secret = (raw ?? '').replace(/\s/g, '').toUpperCase(); + await submitTotpCode(secret, '#mfa-configuration-step', clickNext, () => + $('#recovery-codes-step').isDisplayed().catch(() => false), + ); + await $('#recovery-codes-step .checkbox').click(); + const complete = $('#recovery-codes-step').$('button=Complete'); + await complete.waitForClickable(); + await complete.click(); + return secret; +}; + +export const finishEnrollment = async () => { + await expect($('#finish-step')).toBeDisplayed(); + await $('#finish-step').$('button=Got it').click(); + await expect($('#overview-page')).toBeDisplayed(); +}; diff --git a/e2e/helpers/mfa.ts b/e2e/helpers/mfa.ts new file mode 100644 index 00000000..18edb63a --- /dev/null +++ b/e2e/helpers/mfa.ts @@ -0,0 +1,27 @@ +import { $, browser } from '@wdio/globals'; +import { totpCode } from './totp.js'; + +const MAX_ATTEMPTS = 3; + +const fillCode = async (scope: string, code: string) => { + const input = $(`${scope} .code-input input`); + await input.click(); + await input.setValue(code); +}; + +export const submitTotpCode = async ( + secret: string, + scope: string, + submit: () => Promise, + accepted: () => Promise, +) => { + for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + await fillCode(scope, totpCode(secret)); + await submit(); + const ok = await browser + .waitUntil(accepted, { timeout: 6_000, interval: 500 }) + .then(() => true, () => false); + if (ok) return; + } + throw new Error('TOTP code was not accepted after several attempts'); +}; diff --git a/e2e/helpers/totp.ts b/e2e/helpers/totp.ts new file mode 100644 index 00000000..87e4a4cc --- /dev/null +++ b/e2e/helpers/totp.ts @@ -0,0 +1,8 @@ +import { Secret, TOTP } from 'otpauth'; + +export const totpCode = (base32Secret: string): string => + new TOTP({ + secret: Secret.fromBase32(base32Secret.replace(/\s/g, '').toUpperCase()), + digits: 6, + period: 30, + }).generate(); diff --git a/e2e/helpers/tunnel.ts b/e2e/helpers/tunnel.ts new file mode 100644 index 00000000..c4279f0b --- /dev/null +++ b/e2e/helpers/tunnel.ts @@ -0,0 +1,15 @@ +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); + +const GATEWAY_VPN_IP = process.env.GATEWAY_VPN_IP ?? '10.10.10.1'; + +export const canPingGateway = async (target = GATEWAY_VPN_IP): Promise => { + try { + await execFileAsync('ping', ['-c', '1', '-W', '5', target]); + return true; + } catch { + return false; + } +}; diff --git a/e2e/helpers/windows.ts b/e2e/helpers/windows.ts new file mode 100644 index 00000000..20f04e45 --- /dev/null +++ b/e2e/helpers/windows.ts @@ -0,0 +1,15 @@ +import { browser } from '@wdio/globals'; + +export const switchToFullView = () => + browser.waitUntil( + async () => { + for (const handle of await browser.getWindowHandles()) { + await browser.switchToWindow(handle); + if ((await browser.getUrl()).includes('/full')) { + return true; + } + } + return false; + }, + { timeoutMsg: 'No full view window found' }, + ); diff --git a/e2e/helpers/wireguard.ts b/e2e/helpers/wireguard.ts new file mode 100644 index 00000000..7c90f7a6 --- /dev/null +++ b/e2e/helpers/wireguard.ts @@ -0,0 +1,46 @@ +import { generateKeyPairSync } from 'node:crypto'; +import type { CoreApi } from './coreApi.js'; + +export const generateWireguardKeys = () => { + const { privateKey, publicKey } = generateKeyPairSync('x25519'); + return { + privateKey: privateKey.export({ type: 'pkcs8', format: 'der' }).subarray(-32).toString('base64'), + publicKey: publicKey.export({ type: 'spki', format: 'der' }).subarray(-32).toString('base64'), + }; +}; + +export type TunnelConfig = { + name: string; + prvkey: string; + pubkey: string; + address: string; + serverPubkey: string; + allowedIps: string; + endpoint: string; + dns: string; + keepalive: string; +}; + +// Registers a network device in core for the given location and turns the +// WireGuard config core returns into the values the client's tunnel wizard needs. +export const provisionTunnel = async ( + core: CoreApi, + networkId: number, + name: string, +): Promise => { + const keys = generateWireguardKeys(); + const ip = await core.findAvailableIp(networkId); + const conf = await core.addNetworkDevice(networkId, name, [ip], keys.publicKey); + const field = (re: RegExp) => conf.match(re)?.[1]?.trim() ?? ''; + return { + name, + prvkey: keys.privateKey, + pubkey: keys.publicKey, + address: field(/Address\s*=\s*(.+)/), + serverPubkey: field(/PublicKey\s*=\s*(.+)/), + allowedIps: field(/AllowedIPs\s*=\s*(.+)/), + endpoint: field(/Endpoint\s*=\s*(.+)/), + dns: field(/DNS\s*=\s*(.+)/), + keepalive: field(/PersistentKeepalive\s*=\s*(.+)/) || '25', + }; +}; diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 00000000..854692bf --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,22 @@ +{ + "name": "e2e", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "test": "wdio run ./wdio.conf.ts", + "provision": "node scripts/provision.mjs" + }, + "devDependencies": { + "@types/node": "^25.9.2", + "@wdio/cli": "^9.20.0", + "@wdio/globals": "^9.27.2", + "@wdio/local-runner": "^9.20.0", + "@wdio/mocha-framework": "^9.20.0", + "@wdio/spec-reporter": "^9.20.0", + "@wdio/types": "^9.27.2", + "otpauth": "^9.4.1", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + } +} diff --git a/e2e/pnpm-lock.yaml b/e2e/pnpm-lock.yaml new file mode 100644 index 00000000..2a28d6d2 --- /dev/null +++ b/e2e/pnpm-lock.yaml @@ -0,0 +1,3894 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^25.9.2 + version: 25.9.2 + '@wdio/cli': + specifier: ^9.20.0 + version: 9.27.2(@types/node@25.9.2)(expect-webdriverio@5.6.8) + '@wdio/globals': + specifier: ^9.27.2 + version: 9.27.2(expect-webdriverio@5.6.8)(webdriverio@9.27.2) + '@wdio/local-runner': + specifier: ^9.20.0 + version: 9.27.2(@wdio/globals@9.27.2)(webdriverio@9.27.2) + '@wdio/mocha-framework': + specifier: ^9.20.0 + version: 9.27.2 + '@wdio/spec-reporter': + specifier: ^9.20.0 + version: 9.27.2 + '@wdio/types': + specifier: ^9.27.2 + version: 9.27.2 + otpauth: + specifier: ^9.4.1 + version: 9.5.1 + tsx: + specifier: ^4.20.6 + version: 4.22.4 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.28.0': + resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.0': + resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.0': + resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.0': + resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.0': + resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.0': + resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.0': + resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.0': + resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.0': + resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.0': + resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.0': + resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.0': + resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.0': + resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.0': + resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.0': + resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.0': + resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.0': + resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.0': + resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.0': + resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.0': + resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.0': + resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.0': + resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.0': + resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.0': + resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.0': + resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.0': + resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jest/diff-sequences@30.4.0': + resolution: {integrity: sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@30.4.1': + resolution: {integrity: sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.4.0': + resolution: {integrity: sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/schemas@30.4.1': + resolution: {integrity: sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/types@30.4.1': + resolution: {integrity: sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@noble/hashes@2.2.0': + resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + engines: {node: '>= 20.19.0'} + + '@nodable/entities@2.1.1': + resolution: {integrity: sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@promptbook/utils@0.69.5': + resolution: {integrity: sha512-xm5Ti/Hp3o4xHrsK9Yy3MS6KbDxYbq485hDsFvxqaNA7equHLPdo8H8faTitTeb14QCDfLW4iwCxdVYu5sn6YQ==} + + '@puppeteer/browsers@2.13.2': + resolution: {integrity: sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==} + engines: {node: '>=18'} + hasBin: true + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@sinclair/typebox@0.34.49': + resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/mocha@10.0.10': + resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} + + '@types/node@20.19.42': + resolution: {integrity: sha512-5L7SUaFC1RyDraj2yRhyBzHTobyXHmohD100CChNtyPyleoq37Mqab5Gn8XEKI04dfN/oqPdpHk38MgcQWHbZg==} + + '@types/node@25.9.2': + resolution: {integrity: sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/sinonjs__fake-timers@8.1.5': + resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/which@2.0.2': + resolution: {integrity: sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/pretty-format@4.1.8': + resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/snapshot@4.1.8': + resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==} + + '@vitest/utils@4.1.8': + resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + + '@wdio/cli@9.27.2': + resolution: {integrity: sha512-DHCtxsAmKu4hMAnEljiJ6v76XidA2A9IgP+5kQipxc7r8Ct22VJfEJnasWKEz35WztATzr6vzhk0JalTHMVunw==} + engines: {node: '>=18.20.0'} + hasBin: true + + '@wdio/config@9.27.2': + resolution: {integrity: sha512-d31AMKrqADuKdw7F3025Aeunboska402xmbkdXpOKp3W8gwXcC/y9xorMNM1Z6/wYr+DDFBYXn9AgbaURPQ8gQ==} + engines: {node: '>=18.20.0'} + + '@wdio/dot-reporter@9.27.2': + resolution: {integrity: sha512-xoBgmACafV4L7e7e3DUN8UM1N+I225oms38JtxtfgrMfvHm8QtcmZWXfycxEGM28Gm2M3NmeV3oso7hZeBk6Ww==} + engines: {node: '>=18.20.0'} + + '@wdio/globals@9.27.2': + resolution: {integrity: sha512-Rx9bqD4/8iR3CNPMWYxywQSCqsR/WGwIYT2Q0uUmrvPxOdYFridDEhVRGO32kQ55UM5+JXzXppxgwGLRQ60fJg==} + engines: {node: '>=18.20.0'} + peerDependencies: + expect-webdriverio: ^5.6.5 + webdriverio: ^9.0.0 + + '@wdio/local-runner@9.27.2': + resolution: {integrity: sha512-VJ9SrOzZSgT8l3QOq+z/+nWZoLMeeEvzivEaVOBFwWOdkvE2JVomE19Ch/OFSXHCoGv3PrvfiiBunphLJ7EZ7A==} + engines: {node: '>=18.20.0'} + + '@wdio/logger@9.18.0': + resolution: {integrity: sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==} + engines: {node: '>=18.20.0'} + + '@wdio/mocha-framework@9.27.2': + resolution: {integrity: sha512-C3XbwffqbK4b80XcGp845FphPlz2VvCepvXfi8TI7uo1ZML/Ybcwjmb35nJeoB6F/jPp/Es60BVG7tFG1UW1iw==} + engines: {node: '>=18.20.0'} + + '@wdio/protocols@9.27.2': + resolution: {integrity: sha512-aek2972uzuoSG5yHLhtFpd463qeB4PklYXbJd7Ta44yKinol+akdPZUc9AQJC9Fxz6kBzxHAp2nfYuppxm+Pqg==} + + '@wdio/repl@9.16.2': + resolution: {integrity: sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==} + engines: {node: '>=18.20.0'} + + '@wdio/reporter@9.27.2': + resolution: {integrity: sha512-JDbBeSM8TMZ3CRTnF1fJqyUJEYDas6k1xjVZnrGrO8L/8xQ8dG2vaC5wGJz6uMSHazyks8pL3g/RS8dTbTUPbg==} + engines: {node: '>=18.20.0'} + + '@wdio/runner@9.27.2': + resolution: {integrity: sha512-FLsJ/FKd5acsNOKMYWayVyyDBY1Zw91kgwZry9h+ghpbK8uzpkmgOPtGxljsABPrFpv02fk1ICUNjlKvzQBYNQ==} + engines: {node: '>=18.20.0'} + peerDependencies: + expect-webdriverio: ^5.6.5 + webdriverio: ^9.0.0 + + '@wdio/spec-reporter@9.27.2': + resolution: {integrity: sha512-CGd71d+fxa9UZUBI8frQucv6Iyq8JfdBzET98H1bBqhtFy8xf1f9AaveQ4VvRr0LQKZ6LwmXRfb/bZJcr0yfag==} + engines: {node: '>=18.20.0'} + + '@wdio/types@9.27.2': + resolution: {integrity: sha512-nBUq2juoaaibrOacn/cZ5IjZvJa6ZAHlh1B4UjMxOVcd7kzZyXJjfwAP3vNnboK4dyCLHyKLM+TpfFMmoO59OQ==} + engines: {node: '>=18.20.0'} + + '@wdio/utils@9.27.2': + resolution: {integrity: sha512-QANs93jABp4BfCrX3Vhmrt5usWz2Zo6F6H1hL1+/ibxwG3qYod68PRQIGssoV2Elhql3IUk6o8iRGTDqV0SmIg==} + engines: {node: '>=18.20.0'} + + '@wdio/xvfb@9.27.2': + resolution: {integrity: sha512-Rj8AP/VYVd5clZFKy+P7zzoXCKshjrog6lcV65nnUzATbUYT/PpUCy6OhEWHTSmLQY2Oc5ztY/IetLSg4nmB3w==} + engines: {node: '>=18'} + + '@zip.js/zip.js@2.8.26': + resolution: {integrity: sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==} + engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + anynum@1.0.0: + resolution: {integrity: sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==} + + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + + async-exit-hook@2.0.1: + resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} + engines: {node: '>=0.12.0'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bare-events@2.9.1: + resolution: {integrity: sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.7.2: + resolution: {integrity: sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.9.1: + resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.1: + resolution: {integrity: sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==} + + bare-stream@2.13.1: + resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.4.5: + resolution: {integrity: sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + basic-ftp@5.3.1: + resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==} + engines: {node: '>=10.0.0'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + + cheerio@1.2.0: + resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} + engines: {node: '>=20.18.1'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + + create-wdio@9.27.2: + resolution: {integrity: sha512-zhulPsBa+NkPbLtRFlZFzijCmvS5n7gkWB/90JwahfebfzB0k6/ZwWue7PwyVgzzD/JUGzX1M4HlaWo6265ESQ==} + engines: {node: '>=12.0.0'} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-shorthand-properties@1.1.2: + resolution: {integrity: sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==} + + css-value@0.0.1: + resolution: {integrity: sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + decamelize@6.0.1: + resolution: {integrity: sha512-G7Cqgaelq68XHJNGlZ7lrNQyhZGsFqpwtGFexqUv4IQdjKoSYF7ipZ9UuTJZUSQXFj/XaoBLuEVIVqr8EJngEQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + + diff@5.2.2: + resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} + engines: {node: '>=0.3.1'} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + easy-table@1.2.0: + resolution: {integrity: sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==} + + edge-paths@3.0.5: + resolution: {integrity: sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==} + engines: {node: '>=14.0.0'} + + edgedriver@6.3.0: + resolution: {integrity: sha512-ggEQL+oEyIcM4nP2QC3AtCQ04o4kDNefRM3hja0odvlPSnsaxiruMxEZ93v3gDCKWYW6BXUr51PPradb+3nffw==} + engines: {node: '>=20.0.0'} + hasBin: true + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encoding-sniffer@0.2.1: + resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + esbuild@0.28.0: + resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + exit-hook@4.0.0: + resolution: {integrity: sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==} + engines: {node: '>=18'} + + expect-webdriverio@5.6.8: + resolution: {integrity: sha512-VfLC9o84B40LEw+zX7UykUptKkscX1rPYY4jaAsQ6KyKL0X0ltDkWzKIUiY9g/u0ApNerrkhM/QEY0TDT8pJOQ==} + engines: {node: '>=20'} + peerDependencies: + '@wdio/globals': ^9.0.0 + '@wdio/logger': ^9.0.0 + webdriverio: ^9.0.0 + + expect@30.4.1: + resolution: {integrity: sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-xml-builder@1.2.0: + resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} + + fast-xml-parser@5.8.0: + resolution: {integrity: sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg==} + hasBin: true + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + filelist@1.0.6: + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-up@6.3.0: + resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + geckodriver@6.1.0: + resolution: {integrity: sha512-ZRXLa4ZaYTTgUO4Eefw+RsQCleugU2QLb1ME7qTYxxuRj51yAhfnXaItXNs5/vUzfIaDHuZ+YnSF005hfp07nQ==} + engines: {node: '>=20.0.0'} + hasBin: true + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-port@7.2.0: + resolution: {integrity: sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg==} + engines: {node: '>=16'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + hosted-git-info@8.1.0: + resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==} + engines: {node: ^18.17.0 || >=20.5.0} + + htmlfy@0.8.1: + resolution: {integrity: sha512-xWROBw9+MEGwxpotll0h672KCaLrKKiCYzsyN8ZgL9cQbVumFnyvsk2JqiB9ELAV1GLj1GG/jxZUjV9OZZi/yQ==} + + htmlparser2@10.1.0: + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inquirer@12.11.1: + resolution: {integrity: sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + + jest-diff@30.4.1: + resolution: {integrity: sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-matcher-utils@30.4.1: + resolution: {integrity: sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-message-util@30.4.1: + resolution: {integrity: sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-mock@30.4.1: + resolution: {integrity: sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-regex-util@30.4.0: + resolution: {integrity: sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@30.4.1: + resolution: {integrity: sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true + + json-parse-even-better-errors@3.0.2: + resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + + lines-and-columns@2.0.4: + resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + locate-app@2.5.0: + resolution: {integrity: sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + lodash.flattendeep@4.4.0: + resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} + + lodash.pickby@4.6.0: + resolution: {integrity: sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==} + + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + + lodash.zip@4.2.0: + resolution: {integrity: sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + loglevel-plugin-prefix@0.8.4: + resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==} + + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + engines: {node: '>= 0.6.0'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mocha@10.8.2: + resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==} + engines: {node: '>= 14.0.0'} + hasBin: true + + modern-tar@0.7.6: + resolution: {integrity: sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==} + engines: {node: '>=18.0.0'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + netmask@2.1.1: + resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} + engines: {node: '>= 0.4.0'} + + normalize-package-data@6.0.2: + resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} + engines: {node: ^16.14.0 || >=18.0.0} + + normalize-package-data@7.0.1: + resolution: {integrity: sha512-linxNAT6M0ebEYZOx2tO6vBEFsVgnPpv+AVjk0wJHfaUIbq31Jm3T6vvZaarnOeWDh8ShnwXuaAyM7WT3RzErA==} + engines: {node: ^18.17.0 || >=20.5.0} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + otpauth@9.5.1: + resolution: {integrity: sha512-fJmDAHc8wImfqqqOXIlBvT1dEKrZK0Cmb2VEgScpNTolCz0PHh6ExUZGv4sLtOsWNaHCQlD+rRqaPgnoxFoZjQ==} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + parse-json@7.1.1: + resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} + engines: {node: '>=16'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + + parse5-parser-stream@7.1.2: + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pretty-format@30.4.1: + resolution: {integrity: sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + query-selector-shadow-dom@1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-is@19.2.7: + resolution: {integrity: sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==} + + read-pkg-up@10.1.0: + resolution: {integrity: sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==} + engines: {node: '>=16'} + + read-pkg@8.1.0: + resolution: {integrity: sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==} + engines: {node: '>=16'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + recursive-readdir@2.2.3: + resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==} + engines: {node: '>=6.0.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resq@1.11.0: + resolution: {integrity: sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==} + + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + + rgb2hex@0.2.5: + resolution: {integrity: sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==} + + run-async@4.0.6: + resolution: {integrity: sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==} + engines: {node: '>=0.12.0'} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safaridriver@1.0.1: + resolution: {integrity: sha512-jkg4434cYgtrIF2AeY/X0Wmd2W73cK5qIEFE3hDrrQenJH/2SDJIXGvPAigfvQTcE9+H31zkiNHbUqcihEiMRA==} + engines: {node: '>=18.0.0'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex2@5.1.1: + resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==} + hasBin: true + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + engines: {node: '>=10'} + hasBin: true + + serialize-error@12.0.0: + resolution: {integrity: sha512-ZYkZLAvKTKQXWuh5XpBw7CdbSzagarX39WyZ2H07CDLC5/KfsRGlIXV8d4+tfqX1M7916mRqR1QfNHSij+c9Pw==} + engines: {node: '>=18'} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.9: + resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spacetrim@0.11.59: + resolution: {integrity: sha512-lLYsktklSRKprreOm7NXReW8YiX2VBjbgmXYEziOoGf/qsJqAEACaDvoTtUOycwjpaSh+bT8eu0KrJn7UNxiCg==} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stream-buffers@3.0.3: + resolution: {integrity: sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw==} + engines: {node: '>= 0.10.0'} + + streamx@2.27.0: + resolution: {integrity: sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strnum@2.4.0: + resolution: {integrity: sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + tar-fs@3.1.2: + resolution: {integrity: sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==} + + tar-stream@3.2.0: + resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.22.4: + resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} + engines: {node: '>=14.16'} + + type-fest@4.26.0: + resolution: {integrity: sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==} + engines: {node: '>=16'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + undici@6.26.0: + resolution: {integrity: sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==} + engines: {node: '>=18.17'} + + undici@7.27.2: + resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==} + engines: {node: '>=20.18.1'} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + urlpattern-polyfill@10.1.0: + resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} + + userhome@1.0.1: + resolution: {integrity: sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==} + engines: {node: '>= 0.8.0'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + wait-port@1.1.0: + resolution: {integrity: sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==} + engines: {node: '>=10'} + hasBin: true + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + webdriver@9.27.2: + resolution: {integrity: sha512-m7JrZucyOa+VMojsKrZSIE7lsl2RtLk2VqqOe7aWtlmRnBQs33/AaaHIY8FJNe2NKfM1rRSEv87GP2zjLUzyog==} + engines: {node: '>=18.20.0'} + + webdriverio@9.27.2: + resolution: {integrity: sha512-kNRTYomUY8ujhPn+eIxru9eQJP1BMmb4JfIdFt8m9mAPxkdNKJScRHSj77/x8yV2a4wKP0lefYfFtK77B+qzfA==} + engines: {node: '>=18.20.0'} + peerDependencies: + puppeteer-core: '>=22.x || <=24.x' + peerDependenciesMeta: + puppeteer-core: + optional: true + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-naming@0.1.0: + resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} + engines: {node: '>=16.0.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.29.7': {} + + '@esbuild/aix-ppc64@0.28.0': + optional: true + + '@esbuild/android-arm64@0.28.0': + optional: true + + '@esbuild/android-arm@0.28.0': + optional: true + + '@esbuild/android-x64@0.28.0': + optional: true + + '@esbuild/darwin-arm64@0.28.0': + optional: true + + '@esbuild/darwin-x64@0.28.0': + optional: true + + '@esbuild/freebsd-arm64@0.28.0': + optional: true + + '@esbuild/freebsd-x64@0.28.0': + optional: true + + '@esbuild/linux-arm64@0.28.0': + optional: true + + '@esbuild/linux-arm@0.28.0': + optional: true + + '@esbuild/linux-ia32@0.28.0': + optional: true + + '@esbuild/linux-loong64@0.28.0': + optional: true + + '@esbuild/linux-mips64el@0.28.0': + optional: true + + '@esbuild/linux-ppc64@0.28.0': + optional: true + + '@esbuild/linux-riscv64@0.28.0': + optional: true + + '@esbuild/linux-s390x@0.28.0': + optional: true + + '@esbuild/linux-x64@0.28.0': + optional: true + + '@esbuild/netbsd-arm64@0.28.0': + optional: true + + '@esbuild/netbsd-x64@0.28.0': + optional: true + + '@esbuild/openbsd-arm64@0.28.0': + optional: true + + '@esbuild/openbsd-x64@0.28.0': + optional: true + + '@esbuild/openharmony-arm64@0.28.0': + optional: true + + '@esbuild/sunos-x64@0.28.0': + optional: true + + '@esbuild/win32-arm64@0.28.0': + optional: true + + '@esbuild/win32-ia32@0.28.0': + optional: true + + '@esbuild/win32-x64@0.28.0': + optional: true + + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@25.9.2)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.9.2) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/confirm@5.1.21(@types/node@25.9.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/type': 3.0.10(@types/node@25.9.2) + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/core@10.3.2(@types/node@25.9.2)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.9.2) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/editor@4.2.23(@types/node@25.9.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/external-editor': 1.0.3(@types/node@25.9.2) + '@inquirer/type': 3.0.10(@types/node@25.9.2) + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/expand@4.0.23(@types/node@25.9.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/type': 3.0.10(@types/node@25.9.2) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/external-editor@1.0.3(@types/node@25.9.2)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@25.9.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/type': 3.0.10(@types/node@25.9.2) + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/number@3.0.23(@types/node@25.9.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/type': 3.0.10(@types/node@25.9.2) + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/password@4.0.23(@types/node@25.9.2)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/type': 3.0.10(@types/node@25.9.2) + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/prompts@7.10.1(@types/node@25.9.2)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.9.2) + '@inquirer/confirm': 5.1.21(@types/node@25.9.2) + '@inquirer/editor': 4.2.23(@types/node@25.9.2) + '@inquirer/expand': 4.0.23(@types/node@25.9.2) + '@inquirer/input': 4.3.1(@types/node@25.9.2) + '@inquirer/number': 3.0.23(@types/node@25.9.2) + '@inquirer/password': 4.0.23(@types/node@25.9.2) + '@inquirer/rawlist': 4.1.11(@types/node@25.9.2) + '@inquirer/search': 3.2.2(@types/node@25.9.2) + '@inquirer/select': 4.4.2(@types/node@25.9.2) + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/rawlist@4.1.11(@types/node@25.9.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/type': 3.0.10(@types/node@25.9.2) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/search@3.2.2(@types/node@25.9.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.9.2) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/select@4.4.2(@types/node@25.9.2)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.9.2) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.9.2 + + '@inquirer/type@3.0.10(@types/node@25.9.2)': + optionalDependencies: + '@types/node': 25.9.2 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jest/diff-sequences@30.4.0': {} + + '@jest/expect-utils@30.4.1': + dependencies: + '@jest/get-type': 30.1.0 + + '@jest/get-type@30.1.0': {} + + '@jest/pattern@30.4.0': + dependencies: + '@types/node': 25.9.2 + jest-regex-util: 30.4.0 + + '@jest/schemas@30.4.1': + dependencies: + '@sinclair/typebox': 0.34.49 + + '@jest/types@30.4.1': + dependencies: + '@jest/pattern': 30.4.0 + '@jest/schemas': 30.4.1 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.9.2 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@noble/hashes@2.2.0': {} + + '@nodable/entities@2.1.1': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@promptbook/utils@0.69.5': + dependencies: + spacetrim: 0.11.59 + + '@puppeteer/browsers@2.13.2': + dependencies: + debug: 4.4.3(supports-color@8.1.1) + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.8.4 + tar-fs: 3.1.2 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + '@sec-ant/readable-stream@0.4.1': {} + + '@sinclair/typebox@0.34.49': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@tootallnate/quickjs-emscripten@0.23.0': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/mocha@10.0.10': {} + + '@types/node@20.19.42': + dependencies: + undici-types: 6.21.0 + + '@types/node@25.9.2': + dependencies: + undici-types: 7.24.6 + + '@types/normalize-package-data@2.4.4': {} + + '@types/sinonjs__fake-timers@8.1.5': {} + + '@types/stack-utils@2.0.3': {} + + '@types/which@2.0.2': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.9.2 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 25.9.2 + optional: true + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@4.1.8': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/snapshot@4.1.8': + dependencies: + '@vitest/pretty-format': 4.1.8 + '@vitest/utils': 4.1.8 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/utils@4.1.8': + dependencies: + '@vitest/pretty-format': 4.1.8 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + '@wdio/cli@9.27.2(@types/node@25.9.2)(expect-webdriverio@5.6.8)': + dependencies: + '@vitest/snapshot': 2.1.9 + '@wdio/config': 9.27.2 + '@wdio/globals': 9.27.2(expect-webdriverio@5.6.8)(webdriverio@9.27.2) + '@wdio/logger': 9.18.0 + '@wdio/protocols': 9.27.2 + '@wdio/types': 9.27.2 + '@wdio/utils': 9.27.2 + async-exit-hook: 2.0.1 + chalk: 5.6.2 + chokidar: 4.0.3 + create-wdio: 9.27.2(@types/node@25.9.2) + dotenv: 17.4.2 + import-meta-resolve: 4.2.0 + lodash.flattendeep: 4.4.0 + lodash.pickby: 4.6.0 + lodash.union: 4.6.0 + read-pkg-up: 10.1.0 + tsx: 4.22.4 + webdriverio: 9.27.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - bare-abort-controller + - bare-buffer + - bufferutil + - expect-webdriverio + - puppeteer-core + - react-native-b4a + - supports-color + - utf-8-validate + + '@wdio/config@9.27.2': + dependencies: + '@wdio/logger': 9.18.0 + '@wdio/types': 9.27.2 + '@wdio/utils': 9.27.2 + deepmerge-ts: 7.1.5 + glob: 10.5.0 + import-meta-resolve: 4.2.0 + jiti: 2.7.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + '@wdio/dot-reporter@9.27.2': + dependencies: + '@wdio/reporter': 9.27.2 + '@wdio/types': 9.27.2 + chalk: 5.6.2 + + '@wdio/globals@9.27.2(expect-webdriverio@5.6.8)(webdriverio@9.27.2)': + dependencies: + expect-webdriverio: 5.6.8(@wdio/globals@9.27.2)(@wdio/logger@9.18.0)(webdriverio@9.27.2) + webdriverio: 9.27.2 + + '@wdio/local-runner@9.27.2(@wdio/globals@9.27.2)(webdriverio@9.27.2)': + dependencies: + '@types/node': 20.19.42 + '@wdio/logger': 9.18.0 + '@wdio/repl': 9.16.2 + '@wdio/runner': 9.27.2(expect-webdriverio@5.6.8)(webdriverio@9.27.2) + '@wdio/types': 9.27.2 + '@wdio/xvfb': 9.27.2 + exit-hook: 4.0.0 + expect-webdriverio: 5.6.8(@wdio/globals@9.27.2)(@wdio/logger@9.18.0)(webdriverio@9.27.2) + split2: 4.2.0 + stream-buffers: 3.0.3 + transitivePeerDependencies: + - '@wdio/globals' + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - utf-8-validate + - webdriverio + + '@wdio/logger@9.18.0': + dependencies: + chalk: 5.6.2 + loglevel: 1.9.2 + loglevel-plugin-prefix: 0.8.4 + safe-regex2: 5.1.1 + strip-ansi: 7.2.0 + + '@wdio/mocha-framework@9.27.2': + dependencies: + '@types/mocha': 10.0.10 + '@types/node': 20.19.42 + '@wdio/logger': 9.18.0 + '@wdio/types': 9.27.2 + '@wdio/utils': 9.27.2 + mocha: 10.8.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + '@wdio/protocols@9.27.2': {} + + '@wdio/repl@9.16.2': + dependencies: + '@types/node': 20.19.42 + + '@wdio/reporter@9.27.2': + dependencies: + '@types/node': 20.19.42 + '@wdio/logger': 9.18.0 + '@wdio/types': 9.27.2 + diff: 8.0.4 + object-inspect: 1.13.4 + + '@wdio/runner@9.27.2(expect-webdriverio@5.6.8)(webdriverio@9.27.2)': + dependencies: + '@types/node': 20.19.42 + '@wdio/config': 9.27.2 + '@wdio/dot-reporter': 9.27.2 + '@wdio/globals': 9.27.2(expect-webdriverio@5.6.8)(webdriverio@9.27.2) + '@wdio/logger': 9.18.0 + '@wdio/types': 9.27.2 + '@wdio/utils': 9.27.2 + deepmerge-ts: 7.1.5 + expect-webdriverio: 5.6.8(@wdio/globals@9.27.2)(@wdio/logger@9.18.0)(webdriverio@9.27.2) + webdriver: 9.27.2 + webdriverio: 9.27.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - utf-8-validate + + '@wdio/spec-reporter@9.27.2': + dependencies: + '@wdio/reporter': 9.27.2 + '@wdio/types': 9.27.2 + chalk: 5.6.2 + easy-table: 1.2.0 + pretty-ms: 9.3.0 + + '@wdio/types@9.27.2': + dependencies: + '@types/node': 20.19.42 + + '@wdio/utils@9.27.2': + dependencies: + '@puppeteer/browsers': 2.13.2 + '@wdio/logger': 9.18.0 + '@wdio/types': 9.27.2 + decamelize: 6.0.1 + deepmerge-ts: 7.1.5 + edgedriver: 6.3.0 + geckodriver: 6.1.0 + get-port: 7.2.0 + import-meta-resolve: 4.2.0 + locate-app: 2.5.0 + mitt: 3.0.1 + safaridriver: 1.0.1 + split2: 4.2.0 + wait-port: 1.1.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + '@wdio/xvfb@9.27.2': + dependencies: + '@wdio/logger': 9.18.0 + + '@zip.js/zip.js@2.8.26': {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + agent-base@7.1.4: {} + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + anynum@1.0.0: {} + + archiver-utils@5.0.2: + dependencies: + glob: 10.5.0 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.18.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.2.0 + zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + async-exit-hook@2.0.1: {} + + async@3.2.6: {} + + b4a@1.8.1: {} + + balanced-match@1.0.2: {} + + bare-events@2.9.1: {} + + bare-fs@4.7.2: + dependencies: + bare-events: 2.9.1 + bare-path: 3.0.1 + bare-stream: 2.13.1(bare-events@2.9.1) + bare-url: 2.4.5 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-os@3.9.1: {} + + bare-path@3.0.1: + dependencies: + bare-os: 3.9.1 + + bare-stream@2.13.1(bare-events@2.9.1): + dependencies: + streamx: 2.27.0 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.9.1 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.4.5: + dependencies: + bare-path: 3.0.1 + + base64-js@1.5.1: {} + + basic-ftp@5.3.1: {} + + binary-extensions@2.3.0: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.15: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browser-stdout@1.3.1: {} + + buffer-crc32@0.2.13: {} + + buffer-crc32@1.0.0: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + camelcase@6.3.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + chardet@2.1.1: {} + + cheerio-select@2.1.0: + dependencies: + boolbase: 1.0.0 + css-select: 5.2.2 + css-what: 6.2.2 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + + cheerio@1.2.0: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + encoding-sniffer: 0.2.1 + htmlparser2: 10.1.0 + parse5: 7.3.0 + parse5-htmlparser2-tree-adapter: 7.1.0 + parse5-parser-stream: 7.1.2 + undici: 7.27.2 + whatwg-mimetype: 4.0.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + ci-info@4.4.0: {} + + cli-width@4.1.0: {} + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: + optional: true + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@14.0.3: {} + + commander@9.5.0: {} + + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + core-util-is@1.0.3: {} + + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + + create-wdio@9.27.2(@types/node@25.9.2): + dependencies: + chalk: 5.6.2 + commander: 14.0.3 + cross-spawn: 7.0.6 + ejs: 3.1.10 + execa: 9.6.1 + import-meta-resolve: 4.2.0 + inquirer: 12.11.1(@types/node@25.9.2) + normalize-package-data: 7.0.1 + read-pkg-up: 10.1.0 + recursive-readdir: 2.2.3 + semver: 7.8.4 + type-fest: 4.41.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-shorthand-properties@1.1.2: {} + + css-value@0.0.1: {} + + css-what@6.2.2: {} + + data-uri-to-buffer@6.0.2: {} + + debug@4.4.3(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@4.0.0: {} + + decamelize@6.0.1: {} + + deep-eql@5.0.2: {} + + deepmerge-ts@7.1.5: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + optional: true + + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + + diff@5.2.2: {} + + diff@8.0.4: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dotenv@17.4.2: {} + + eastasianwidth@0.2.0: {} + + easy-table@1.2.0: + dependencies: + ansi-regex: 5.0.1 + optionalDependencies: + wcwidth: 1.0.1 + + edge-paths@3.0.5: + dependencies: + '@types/which': 2.0.2 + which: 2.0.2 + + edgedriver@6.3.0: + dependencies: + '@wdio/logger': 9.18.0 + '@zip.js/zip.js': 2.8.26 + decamelize: 6.0.1 + edge-paths: 3.0.5 + fast-xml-parser: 5.8.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + which: 6.0.1 + transitivePeerDependencies: + - supports-color + + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encoding-sniffer@0.2.1: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + entities@4.5.0: {} + + entities@6.0.1: {} + + entities@7.0.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + esbuild@0.28.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.0 + '@esbuild/android-arm': 0.28.0 + '@esbuild/android-arm64': 0.28.0 + '@esbuild/android-x64': 0.28.0 + '@esbuild/darwin-arm64': 0.28.0 + '@esbuild/darwin-x64': 0.28.0 + '@esbuild/freebsd-arm64': 0.28.0 + '@esbuild/freebsd-x64': 0.28.0 + '@esbuild/linux-arm': 0.28.0 + '@esbuild/linux-arm64': 0.28.0 + '@esbuild/linux-ia32': 0.28.0 + '@esbuild/linux-loong64': 0.28.0 + '@esbuild/linux-mips64el': 0.28.0 + '@esbuild/linux-ppc64': 0.28.0 + '@esbuild/linux-riscv64': 0.28.0 + '@esbuild/linux-s390x': 0.28.0 + '@esbuild/linux-x64': 0.28.0 + '@esbuild/netbsd-arm64': 0.28.0 + '@esbuild/netbsd-x64': 0.28.0 + '@esbuild/openbsd-arm64': 0.28.0 + '@esbuild/openbsd-x64': 0.28.0 + '@esbuild/openharmony-arm64': 0.28.0 + '@esbuild/sunos-x64': 0.28.0 + '@esbuild/win32-arm64': 0.28.0 + '@esbuild/win32-ia32': 0.28.0 + '@esbuild/win32-x64': 0.28.0 + + escalade@3.2.0: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + esprima@4.0.1: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + event-target-shim@5.0.1: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.9.1 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + exit-hook@4.0.0: {} + + expect-webdriverio@5.6.8(@wdio/globals@9.27.2)(@wdio/logger@9.18.0)(webdriverio@9.27.2): + dependencies: + '@vitest/snapshot': 4.1.8 + '@wdio/globals': 9.27.2(expect-webdriverio@5.6.8)(webdriverio@9.27.2) + '@wdio/logger': 9.18.0 + deep-eql: 5.0.2 + expect: 30.4.1 + jest-matcher-utils: 30.4.1 + webdriverio: 9.27.2 + + expect@30.4.1: + dependencies: + '@jest/expect-utils': 30.4.1 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-util: 30.4.1 + + extract-zip@2.0.1: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@2.0.1: {} + + fast-fifo@1.3.2: {} + + fast-xml-builder@1.2.0: + dependencies: + path-expression-matcher: 1.5.0 + xml-naming: 0.1.0 + + fast-xml-parser@5.8.0: + dependencies: + '@nodable/entities': 2.1.1 + fast-xml-builder: 1.2.0 + path-expression-matcher: 1.5.0 + strnum: 2.4.0 + xml-naming: 0.1.0 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + filelist@1.0.6: + dependencies: + minimatch: 5.1.9 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-up@6.3.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + + flat@5.0.2: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + geckodriver@6.1.0: + dependencies: + '@wdio/logger': 9.18.0 + '@zip.js/zip.js': 2.8.26 + decamelize: 6.0.1 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + modern-tar: 0.7.6 + transitivePeerDependencies: + - supports-color + + get-caller-file@2.0.5: {} + + get-port@7.2.0: {} + + get-stream@5.2.0: + dependencies: + pump: 3.0.4 + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + get-uri@6.0.5: + dependencies: + basic-ftp: 5.3.1 + data-uri-to-buffer: 6.0.2 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.9 + once: 1.4.0 + + graceful-fs@4.2.11: {} + + grapheme-splitter@1.0.4: {} + + has-flag@4.0.0: {} + + he@1.2.0: {} + + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + hosted-git-info@8.1.0: + dependencies: + lru-cache: 10.4.3 + + htmlfy@0.8.1: {} + + htmlparser2@10.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 7.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + human-signals@8.0.1: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + immediate@3.0.6: {} + + import-meta-resolve@4.2.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + inquirer@12.11.1(@types/node@25.9.2): + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.9.2) + '@inquirer/prompts': 7.10.1(@types/node@25.9.2) + '@inquirer/type': 3.0.10(@types/node@25.9.2) + mute-stream: 2.0.0 + run-async: 4.0.6 + rxjs: 7.8.2 + optionalDependencies: + '@types/node': 25.9.2 + + ip-address@10.2.0: {} + + is-arrayish@0.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-plain-obj@2.1.0: {} + + is-plain-obj@4.1.0: {} + + is-stream@2.0.1: {} + + is-stream@4.0.1: {} + + is-unicode-supported@0.1.0: {} + + is-unicode-supported@2.1.0: {} + + isarray@1.0.0: {} + + isexe@2.0.0: {} + + isexe@4.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.6 + picocolors: 1.1.1 + + jest-diff@30.4.1: + dependencies: + '@jest/diff-sequences': 30.4.0 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.4.1 + + jest-matcher-utils@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.4.1 + pretty-format: 30.4.1 + + jest-message-util@30.4.1: + dependencies: + '@babel/code-frame': 7.29.7 + '@jest/types': 30.4.1 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-util: 30.4.1 + picomatch: 4.0.4 + pretty-format: 30.4.1 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.9.2 + jest-util: 30.4.1 + + jest-regex-util@30.4.0: {} + + jest-util@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.9.2 + chalk: 4.1.2 + ci-info: 4.4.0 + graceful-fs: 4.2.11 + picomatch: 4.0.4 + + jiti@2.7.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.2.0: + dependencies: + argparse: 2.0.1 + + json-parse-even-better-errors@3.0.2: {} + + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + lie@3.3.0: + dependencies: + immediate: 3.0.6 + + lines-and-columns@2.0.4: {} + + locate-app@2.5.0: + dependencies: + '@promptbook/utils': 0.69.5 + type-fest: 4.26.0 + userhome: 1.0.1 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + + lodash.clonedeep@4.5.0: {} + + lodash.flattendeep@4.4.0: {} + + lodash.pickby@4.6.0: {} + + lodash.union@4.6.0: {} + + lodash.zip@4.2.0: {} + + lodash@4.18.1: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + loglevel-plugin-prefix@0.8.4: {} + + loglevel@1.9.2: {} + + lru-cache@10.4.3: {} + + lru-cache@7.18.3: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.15 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.1 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.1 + + minipass@7.1.3: {} + + mitt@3.0.1: {} + + mocha@10.8.2: + dependencies: + ansi-colors: 4.1.3 + browser-stdout: 1.3.1 + chokidar: 3.6.0 + debug: 4.4.3(supports-color@8.1.1) + diff: 5.2.2 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 8.1.0 + he: 1.2.0 + js-yaml: 4.2.0 + log-symbols: 4.1.0 + minimatch: 5.1.9 + ms: 2.1.3 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.5.1 + yargs: 16.2.0 + yargs-parser: 20.2.9 + yargs-unparser: 2.0.0 + + modern-tar@0.7.6: {} + + ms@2.1.3: {} + + mute-stream@2.0.0: {} + + netmask@2.1.1: {} + + normalize-package-data@6.0.2: + dependencies: + hosted-git-info: 7.0.2 + semver: 7.8.4 + validate-npm-package-license: 3.0.4 + + normalize-package-data@7.0.1: + dependencies: + hosted-git-info: 8.1.0 + semver: 7.8.4 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-inspect@1.13.4: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + otpauth@9.5.1: + dependencies: + '@noble/hashes': 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.2 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.4 + debug: 4.4.3(supports-color@8.1.1) + get-uri: 6.0.5 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.1.1 + + package-json-from-dist@1.0.1: {} + + pako@1.0.11: {} + + parse-json@7.1.1: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 3.0.2 + lines-and-columns: 2.0.4 + type-fest: 3.13.1 + + parse-ms@4.0.0: {} + + parse5-htmlparser2-tree-adapter@7.1.0: + dependencies: + domhandler: 5.0.3 + parse5: 7.3.0 + + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.3.0 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-exists@4.0.0: {} + + path-exists@5.0.0: {} + + path-expression-matcher@1.5.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + pathe@1.1.2: {} + + pathe@2.0.3: {} + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pretty-format@30.4.1: + dependencies: + '@jest/schemas': 30.4.1 + ansi-styles: 5.2.0 + react-is-18: react-is@18.3.1 + react-is-19: react-is@19.2.7 + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + process-nextick-args@2.0.1: {} + + process@0.11.10: {} + + progress@2.0.3: {} + + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@8.1.1) + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + query-selector-shadow-dom@1.0.1: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + react-is@18.3.1: {} + + react-is@19.2.7: {} + + read-pkg-up@10.1.0: + dependencies: + find-up: 6.3.0 + read-pkg: 8.1.0 + type-fest: 4.41.0 + + read-pkg@8.1.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 6.0.2 + parse-json: 7.1.1 + type-fest: 4.41.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + readdirp@4.1.2: {} + + recursive-readdir@2.2.3: + dependencies: + minimatch: 3.1.5 + + require-directory@2.1.1: {} + + resq@1.11.0: + dependencies: + fast-deep-equal: 2.0.1 + + ret@0.5.0: {} + + rgb2hex@0.2.5: {} + + run-async@4.0.6: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safaridriver@1.0.1: {} + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-regex2@5.1.1: + dependencies: + ret: 0.5.0 + + safer-buffer@2.1.2: {} + + semver@7.8.4: {} + + serialize-error@12.0.0: + dependencies: + type-fest: 4.41.0 + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + setimmediate@1.0.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@8.1.1) + socks: 2.8.9 + transitivePeerDependencies: + - supports-color + + socks@2.8.9: + dependencies: + ip-address: 10.2.0 + smart-buffer: 4.2.0 + + source-map@0.6.1: + optional: true + + spacetrim@0.11.59: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.23 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.23 + + spdx-license-ids@3.0.23: {} + + split2@4.2.0: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stream-buffers@3.0.3: {} + + streamx@2.27.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@4.0.0: {} + + strip-json-comments@3.1.1: {} + + strnum@2.4.0: + dependencies: + anynum: 1.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + tar-fs@3.1.2: + dependencies: + pump: 3.0.4 + tar-stream: 3.2.0 + optionalDependencies: + bare-fs: 4.7.2 + bare-path: 3.0.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@3.2.0: + dependencies: + b4a: 1.8.1 + bare-fs: 4.7.2 + fast-fifo: 1.3.2 + streamx: 2.27.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + teex@1.0.1: + dependencies: + streamx: 2.27.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.1 + transitivePeerDependencies: + - react-native-b4a + + tinyrainbow@1.2.0: {} + + tinyrainbow@3.1.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tslib@2.8.1: {} + + tsx@4.22.4: + dependencies: + esbuild: 0.28.0 + optionalDependencies: + fsevents: 2.3.3 + + type-fest@3.13.1: {} + + type-fest@4.26.0: {} + + type-fest@4.41.0: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + undici-types@7.24.6: {} + + undici@6.26.0: {} + + undici@7.27.2: {} + + unicorn-magic@0.3.0: {} + + urlpattern-polyfill@10.1.0: {} + + userhome@1.0.1: {} + + util-deprecate@1.0.2: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + wait-port@1.1.0: + dependencies: + chalk: 4.1.2 + commander: 9.5.0 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + optional: true + + webdriver@9.27.2: + dependencies: + '@types/node': 20.19.42 + '@types/ws': 8.18.1 + '@wdio/config': 9.27.2 + '@wdio/logger': 9.18.0 + '@wdio/protocols': 9.27.2 + '@wdio/types': 9.27.2 + '@wdio/utils': 9.27.2 + deepmerge-ts: 7.1.5 + https-proxy-agent: 7.0.6 + undici: 6.26.0 + ws: 8.21.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - utf-8-validate + + webdriverio@9.27.2: + dependencies: + '@types/node': 20.19.42 + '@types/sinonjs__fake-timers': 8.1.5 + '@wdio/config': 9.27.2 + '@wdio/logger': 9.18.0 + '@wdio/protocols': 9.27.2 + '@wdio/repl': 9.16.2 + '@wdio/types': 9.27.2 + '@wdio/utils': 9.27.2 + archiver: 7.0.1 + aria-query: 5.3.2 + cheerio: 1.2.0 + css-shorthand-properties: 1.1.2 + css-value: 0.0.1 + grapheme-splitter: 1.0.4 + htmlfy: 0.8.1 + is-plain-obj: 4.1.0 + jszip: 3.10.1 + lodash.clonedeep: 4.5.0 + lodash.zip: 4.2.0 + query-selector-shadow-dom: 1.0.1 + resq: 1.11.0 + rgb2hex: 0.2.5 + serialize-error: 12.0.0 + urlpattern-polyfill: 10.1.0 + webdriver: 9.27.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - utf-8-validate + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@6.0.1: + dependencies: + isexe: 4.0.0 + + workerpool@6.5.1: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + ws@8.21.0: {} + + xml-naming@0.1.0: {} + + y18n@5.0.8: {} + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yocto-queue@0.1.0: {} + + yocto-queue@1.2.2: {} + + yoctocolors-cjs@2.1.3: {} + + yoctocolors@2.1.2: {} + + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0 diff --git a/e2e/pnpm-workspace.yaml b/e2e/pnpm-workspace.yaml new file mode 100644 index 00000000..5ed0b5af --- /dev/null +++ b/e2e/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + esbuild: true diff --git a/e2e/scripts/provision.mjs b/e2e/scripts/provision.mjs new file mode 100644 index 00000000..cf80bc08 --- /dev/null +++ b/e2e/scripts/provision.mjs @@ -0,0 +1,139 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; + +const envFile = path.resolve(import.meta.dirname, '../.env'); +if (fs.existsSync(envFile)) { + for (const line of fs.readFileSync(envFile, 'utf8').split('\n')) { + const match = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/); + if (match && process.env[match[1]] === undefined) { + process.env[match[1]] = match[2]; + } + } +} + +const CORE_URL = requireEnv('CORE_URL'); +const ADMIN_USER = process.env.CORE_ADMIN_USER ?? 'admin'; +const ADMIN_PASSWORD = requireEnv('CORE_ADMIN_PASSWORD'); + +function requireEnv(name) { + const value = process.env[name]; + if (!value) { + console.error(`Missing required environment variable ${name}.`); + process.exit(1); + } + return value; +} + +const NETWORK = { + name: process.env.NETWORK_NAME ?? 'e2e', + address: process.env.NETWORK_ADDRESS ?? '10.10.10.1/24', + endpoint: requireEnv('NETWORK_ENDPOINT'), + port: Number(process.env.NETWORK_PORT ?? 50051), + allowed_ips: process.env.NETWORK_ALLOWED_IPS ?? '10.10.10.0/24', + dns: null, + mtu: 1420, + fwmark: 0, + allow_all_groups: true, + allowed_groups: [], + keepalive_interval: 25, + peer_disconnect_threshold: 300, + acl_enabled: false, + acl_default_allow: false, + location_mfa_mode: 'disabled', + service_location_mode: 'disabled', +}; + +async function waitForCore() { + const deadline = Date.now() + 120_000; + while (Date.now() < deadline) { + try { + const res = await fetch(`${CORE_URL}/api/v1/health`); + if (res.status === 200) return; + } catch { + // not up yet + } + await new Promise((resolve) => setTimeout(resolve, 2000)); + } + throw new Error(`Core did not become healthy at ${CORE_URL} within 120s`); +} + +let cookie = ''; + +async function api(method, apiPath, body) { + const res = await fetch(`${CORE_URL}${apiPath}`, { + method, + redirect: 'manual', + headers: { + 'Content-Type': 'application/json', + ...(cookie ? { Cookie: cookie } : {}), + }, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + if (res.status >= 300 && res.status < 400) { + throw new Error( + `${method} ${apiPath} redirected (${res.status}) to ${res.headers.get('location')} — check CORE_URL`, + ); + } + if (!res.ok) { + const allow = res.headers.get('allow'); + throw new Error( + `${method} ${apiPath} failed: ${res.status}` + + (allow ? ` (Allow: ${allow})` : '') + + ` ${await res.text()}`, + ); + } + return res; +} + +console.log(`Waiting for core at ${CORE_URL}...`); +await waitForCore(); + +console.log('Logging in as admin...'); +let loginRes; +try { + loginRes = await api('POST', '/api/v1/auth', { + username: ADMIN_USER, + password: ADMIN_PASSWORD, + }); +} catch (error) { + if (String(error).includes('405')) { + console.error( + `\nCore at ${CORE_URL} is still in initial-setup mode. ` + + `Open ${CORE_URL} in a browser and complete the setup wizard ` + + '(all the way through the final step — core restarts afterwards), ' + + 'then re-run this script.\n', + ); + process.exit(1); + } + throw error; +} +const setCookie = loginRes.headers.get('set-cookie'); +if (!setCookie) throw new Error('Login did not return a session cookie'); +cookie = setCookie.split(';')[0]; + +const existing = await (await api('GET', '/api/v1/network')).json(); +let network = existing.find((n) => n.name === NETWORK.name); +if (network) { + console.log(`Network "${NETWORK.name}" already exists with id ${network.id}`); +} else { + console.log('Creating VPN network...'); + network = await (await api('POST', '/api/v1/network', NETWORK)).json(); + console.log(`Network created with id ${network.id}`); +} + +const gateways = await ( + await api('GET', `/api/v1/network/${network.id}/gateways`) +).json(); +if (gateways.length === 0) { + console.error( + `\nNo gateway is connected to network "${NETWORK.name}" (id ${network.id}).\n` + + 'Gateway setup is intentionally not automated — add and connect one yourself,\n' + + `then re-run this script. See https://docs.defguard.net for the deployment guide.\n`, + ); + process.exit(1); +} +console.log(`Gateway connected: ${gateways[0].name}`); + +console.log('Provisioning done.'); diff --git a/e2e/tests/enrollment.spec.ts b/e2e/tests/enrollment.spec.ts new file mode 100644 index 00000000..95739760 --- /dev/null +++ b/e2e/tests/enrollment.spec.ts @@ -0,0 +1,57 @@ +import { $, expect } from '@wdio/globals'; +import { resetInstances } from '../helpers/client.js'; +import { connectAndPing } from '../helpers/connection.js'; +import { + type CoreApi, + type EnrollmentFixture, + type LocationMfaMode, + loggedInCoreApi, +} from '../helpers/coreApi.js'; +import { + addInstance, + configureTotp, + finishEnrollment, + setPassword, +} from '../helpers/enrollment.js'; + +describe('enrollment', () => { + let core: CoreApi; + let networkId: number; + let previousMfaMode: LocationMfaMode; + let fixture: EnrollmentFixture; + + beforeEach(async () => { + core = await loggedInCoreApi(); + networkId = (await core.listNetworks())[0].id; + }); + + afterEach(async () => { + await core.setLocationMfaMode(networkId, previousMfaMode); + await resetInstances(); + if (fixture?.ephemeral) { + await core.deleteUser(fixture.username); + } + }); + + it('enrolls a user without MFA and connects to the VPN', async () => { + previousMfaMode = await core.setLocationMfaMode(networkId, 'disabled'); + fixture = await core.createEnrollmentFixture(); + + await addInstance(fixture); + await setPassword(); + await expect($('#mfa-configuration-step')).not.toBeDisplayed(); + await finishEnrollment(); + await connectAndPing(); + }); + + it('enrolls a user with TOTP MFA and connects to the VPN', async () => { + previousMfaMode = await core.setLocationMfaMode(networkId, 'internal'); + fixture = await core.createEnrollmentFixture(); + + await addInstance(fixture); + await setPassword(); + const secret = await configureTotp(); + await finishEnrollment(); + await connectAndPing(secret); + }); +}); diff --git a/e2e/tests/logs.spec.ts b/e2e/tests/logs.spec.ts new file mode 100644 index 00000000..181bd4a9 --- /dev/null +++ b/e2e/tests/logs.spec.ts @@ -0,0 +1,39 @@ +import { $, browser, expect } from '@wdio/globals'; +import { readClipboard } from '../helpers/clipboard.js'; +import { switchToFullView } from '../helpers/windows.js'; + +const openActionsMenu = async () => { + const actions = $('#log-page-view').$('button=Actions'); + await actions.waitForClickable(); + await actions.click(); +}; + +describe('logs', () => { + beforeEach(async () => { + await switchToFullView(); + const logLink = $('a[href="/full/log"]'); + await logLink.waitForClickable(); + await logLink.click(); + await expect($('#log-page-view')).toBeDisplayed(); + await $('#log-page-view .log-container p').waitForExist({ timeout: 15_000 }); + }); + + it('copies logs to the clipboard', async () => { + const sample = ( + (await $('#log-page-view .log-container p').getProperty('textContent')) as string + ).trim(); + await openActionsMenu(); + const copy = $('.menu-item*=Copy to Clipboard'); + await copy.waitForClickable(); + await copy.click(); + await browser.waitUntil(() => readClipboard().includes(sample), { + timeout: 10_000, + timeoutMsg: 'Clipboard does not contain the logs after copying', + }); + }); + + it('offers a logs download', async () => { + await openActionsMenu(); + await expect($('.menu-item*=Download')).toBeDisplayed(); + }); +}); diff --git a/e2e/tests/wireguard_tunnel.spec.ts b/e2e/tests/wireguard_tunnel.spec.ts new file mode 100644 index 00000000..ae978527 --- /dev/null +++ b/e2e/tests/wireguard_tunnel.spec.ts @@ -0,0 +1,58 @@ +import { $, expect } from '@wdio/globals'; +import { type CoreApi, type LocationMfaMode, loggedInCoreApi } from '../helpers/coreApi.js'; +import { switchToFullView } from '../helpers/windows.js'; +import { provisionTunnel } from '../helpers/wireguard.js'; + +const continueStep = async (stepId: string) => { + const button = $(stepId).$('button=Continue'); + await button.waitForClickable(); + await button.click(); +}; + +// Skipped: blocked by https://github.com/DefGuard/client/issues/1006 +describe.skip('WireGuard tunnel', () => { + let core: CoreApi; + let networkId: number; + let previousMfaMode: LocationMfaMode; + + beforeEach(async () => { + core = await loggedInCoreApi(); + networkId = (await core.listNetworks())[0].id; + previousMfaMode = await core.setLocationMfaMode(networkId, 'disabled'); + }); + + afterEach(async () => { + await core.setLocationMfaMode(networkId, previousMfaMode); + }); + + it('adds a tunnel from a core-provisioned config', async () => { + const config = await provisionTunnel(core, networkId, `e2e-tunnel-${Date.now()}`); + + await switchToFullView(); + await $('#add-page-view').$('button=Add tunnel').click(); + await $('#add-tunnel-page').$('button=Add tunnel').click(); + + await expect($('#general-info-step')).toBeDisplayed(); + await $('[data-testid="field-name"]').setValue(config.name); + await $('[data-testid="field-address"]').setValue(config.address); + await continueStep('#general-info-step'); + + await expect($('#keys-step')).toBeDisplayed(); + await $('[data-testid="field-prvkey"]').setValue(config.prvkey); + await $('[data-testid="field-pubkey"]').setValue(config.pubkey); + await continueStep('#keys-step'); + + await expect($('#vpn-server-step')).toBeDisplayed(); + await $('[data-testid="field-server_pubkey"]').setValue(config.serverPubkey); + await $('[data-testid="field-endpoint"]').setValue(config.endpoint); + await $('[data-testid="field-allowed_ips"]').setValue(config.allowedIps); + await $('[data-testid="field-persistent_keep_alive"]').setValue(config.keepalive); + await continueStep('#vpn-server-step'); + + await expect($('#advanced-settings-step')).toBeDisplayed(); + await continueStep('#advanced-settings-step'); + + await expect($('#finish-step')).toBeDisplayed(); + await expect($('#finish-step')).toHaveText('added successfully', { containing: true }); + }); +}); diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 00000000..08bfbff5 --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"] + }, + "include": ["**/*.ts"] +} diff --git a/e2e/wdio.conf.ts b/e2e/wdio.conf.ts new file mode 100644 index 00000000..a6b4a8c1 --- /dev/null +++ b/e2e/wdio.conf.ts @@ -0,0 +1,124 @@ +import { type ChildProcess, spawn, spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +const here = import.meta.dirname; + +const DRIVER_PORT = 4444; +const DRIVER_READY_DELAY_MS = 1_000; +const TEST_TIMEOUT_MS = 120_000; +const WAIT_FOR_TIMEOUT_MS = 15_000; + +const envFile = path.resolve(here, '.env'); +if (fs.existsSync(envFile)) { + process.loadEnvFile(envFile); +} + +const clientBinary = + process.env.CLIENT_BINARY ?? + path.resolve(here, '../src-tauri/target/release/defguard-client'); +const tauriDriverBinary = + process.env.TAURI_DRIVER ?? path.join(os.homedir(), '.cargo', 'bin', 'tauri-driver'); +const nativeDriver = process.env.NATIVE_DRIVER; + +let tauriDriver: ChildProcess | undefined; +let dataDir: string | undefined; + +const killLeftoverClients = () => { + spawnSync('pkill', ['-f', clientBinary]); +}; + +const cleanupWireguardInterfaces = () => { + const listed = spawnSync('ip', ['-j', 'link', 'show', 'type', 'wireguard'], { + encoding: 'utf8', + }); + const links = JSON.parse(listed.stdout || '[]') as Array<{ ifname: string }>; + for (const { ifname } of links) { + if (!/^wg\d+$/.test(ifname)) continue; + if (spawnSync('ip', ['link', 'delete', ifname]).status !== 0) { + spawnSync('sudo', ['-n', 'ip', 'link', 'delete', ifname]); + } + } +}; + +const cleanup = () => { + tauriDriver?.kill(); + tauriDriver = undefined; + killLeftoverClients(); + cleanupWireguardInterfaces(); +}; + +process.on('exit', cleanup); +for (const signal of ['SIGINT', 'SIGTERM'] as const) { + process.on(signal, () => { + cleanup(); + process.exit(130); + }); +} + +export const config: WebdriverIO.Config = { + runner: 'local', + hostname: '127.0.0.1', + port: DRIVER_PORT, + logLevel: 'info', + specs: ['./tests/**/*.spec.ts'], + maxInstances: 1, + capabilities: [ + { + 'wdio:maxInstances': 1, + 'tauri:options': { application: clientBinary }, + } as WebdriverIO.Capabilities, + ], + reporters: ['spec'], + framework: 'mocha', + mochaOpts: { ui: 'bdd', timeout: TEST_TIMEOUT_MS }, + waitforTimeout: WAIT_FOR_TIMEOUT_MS, + connectionRetryTimeout: 120_000, + connectionRetryCount: 2, + + onPrepare: () => { + if (!fs.existsSync(clientBinary)) { + throw new Error( + `Client binary not found at ${clientBinary}. Build it with ` + + '`pnpm tauri build` or set CLIENT_BINARY.', + ); + } + }, + + beforeSession: () => { + killLeftoverClients(); + cleanupWireguardInterfaces(); + dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'defguard-e2e-')); + tauriDriver = spawn( + tauriDriverBinary, + nativeDriver ? ['--native-driver', nativeDriver] : [], + { + stdio: ['ignore', 'inherit', 'inherit'], + env: { + ...process.env, + XDG_DATA_HOME: path.join(dataDir, 'share'), + XDG_CONFIG_HOME: path.join(dataDir, 'config'), + XDG_CACHE_HOME: path.join(dataDir, 'cache'), + }, + }, + ); + tauriDriver.on('error', (error) => { + console.error('tauri-driver failed to start:', error); + process.exit(1); + }); + return new Promise((resolve) => setTimeout(resolve, DRIVER_READY_DELAY_MS)); + }, + + afterTest: () => cleanupWireguardInterfaces(), + + afterSession: () => { + cleanup(); + if (dataDir) { + fs.rmSync(dataDir, { recursive: true, force: true }); + dataDir = undefined; + } + }, + + onComplete: cleanup, +}; From 1193b9ae98308796975edd70d70152389171a121 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:12:29 +0200 Subject: [PATCH 02/26] e2e workflow --- .github/workflows/e2e.yaml | 93 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 .github/workflows/e2e.yaml diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml new file mode 100644 index 00000000..6f35fdc0 --- /dev/null +++ b/.github/workflows/e2e.yaml @@ -0,0 +1,93 @@ +name: E2E tests + +on: + workflow_dispatch: + push: + branches: [main, dev, 'release/**', e2e-tests] + paths-ignore: ['*.md', 'LICENSE'] + pull_request: + branches: [main, dev, 'release/**'] + paths-ignore: ['*.md', 'LICENSE'] + +concurrency: + group: e2e + cancel-in-progress: false + +jobs: + e2e: + runs-on: [self-hosted, Linux, X64] + timeout-minutes: 90 + container: + image: ubuntu:24.04 + options: --privileged + env: + DEBIAN_FRONTEND: noninteractive + CLIENT_BINARY: ${{ github.workspace }}/src-tauri/target/release/defguard-client + NATIVE_DRIVER: /usr/bin/WebKitWebDriver + steps: + - name: Install system dependencies + run: | + apt-get update + apt-get install -y git curl ca-certificates build-essential \ + libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev \ + librsvg2-dev patchelf libssl-dev libxdo-dev protobuf-compiler \ + libprotobuf-dev webkit2gtk-driver xvfb wireguard-tools iproute2 \ + iputils-ping procps xclip + + - uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Install pnpm + uses: pnpm/action-setup@v6 + with: + version: 11 + + - uses: actions/setup-node@v6 + with: + node-version: 26 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + workspaces: src-tauri + + - name: Install tauri-driver + run: cargo install tauri-driver --locked + + - name: Build new-ui + run: pnpm install --no-frozen-lockfile && pnpm build + working-directory: new-ui + + - name: Build client + run: pnpm install --no-frozen-lockfile && pnpm tauri build + + - name: Prepare e2e environment + run: | + cp e2e/.env.example e2e/.env + echo "CORE_URL=${{ secrets.E2E_CORE_URL }}" >> e2e/.env + echo "PROXY_URL=${{ secrets.E2E_PROXY_URL }}" >> e2e/.env + echo "CORE_ADMIN_PASSWORD=${{ secrets.E2E_CORE_ADMIN_PASSWORD }}" >> e2e/.env + echo "NETWORK_ENDPOINT=${{ secrets.E2E_NETWORK_ENDPOINT }}" >> e2e/.env + + - name: Check deployment is reachable + run: | + source e2e/.env + curl -sf --max-time 15 "$CORE_URL/api/v1/health" + + - name: Start defguard-service + run: | + setsid src-tauri/target/release/defguard-service < /dev/null & + for _ in $(seq 1 40); do [ -S /var/run/defguard.socket ] && break; sleep 0.5; done + test -S /var/run/defguard.socket + + - name: Install e2e dependencies + run: pnpm install --no-frozen-lockfile + working-directory: e2e + + - name: Run e2e tests + run: xvfb-run -a pnpm test + working-directory: e2e From 3dfb0cca2345973c4184eb881a1e72b829b42605 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:14:12 +0200 Subject: [PATCH 03/26] add sqlx_offline=1 --- .github/workflows/e2e.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 6f35fdc0..892515d1 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -24,6 +24,7 @@ jobs: DEBIAN_FRONTEND: noninteractive CLIENT_BINARY: ${{ github.workspace }}/src-tauri/target/release/defguard-client NATIVE_DRIVER: /usr/bin/WebKitWebDriver + SQLX_OFFLINE: '1' steps: - name: Install system dependencies run: | From b19d4e5d6153e1d63a82c68306e500e06c31d1c6 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:25:41 +0200 Subject: [PATCH 04/26] change order in job --- .github/workflows/e2e.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 892515d1..1315c519 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -39,14 +39,16 @@ jobs: with: submodules: recursive + - uses: actions/setup-node@v6 + with: + node-version: 26 + - name: Install pnpm uses: pnpm/action-setup@v6 with: + cache: true version: 11 - - - uses: actions/setup-node@v6 - with: - node-version: 26 + run_install: false - name: Install Rust stable uses: dtolnay/rust-toolchain@stable From f737d0a0b868d581b32dd1d92f9e013e4d987797 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:41:28 +0200 Subject: [PATCH 05/26] change step in job --- .github/workflows/e2e.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 1315c519..28b6308f 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -50,6 +50,11 @@ jobs: version: 11 run_install: false + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + - name: Install Rust stable uses: dtolnay/rust-toolchain@stable @@ -77,9 +82,7 @@ jobs: echo "NETWORK_ENDPOINT=${{ secrets.E2E_NETWORK_ENDPOINT }}" >> e2e/.env - name: Check deployment is reachable - run: | - source e2e/.env - curl -sf --max-time 15 "$CORE_URL/api/v1/health" + run: curl -sf --max-time 15 "${{ secrets.E2E_CORE_URL }}/api/v1/health" - name: Start defguard-service run: | From 91cbafb035cc5e0bc3f16748c47b44ece181a9ab Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:58:40 +0200 Subject: [PATCH 06/26] add group --- .github/workflows/e2e.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 28b6308f..150d41ff 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -86,6 +86,7 @@ jobs: - name: Start defguard-service run: | + groupadd -f defguard setsid src-tauri/target/release/defguard-service < /dev/null & for _ in $(seq 1 40); do [ -S /var/run/defguard.socket ] && break; sleep 0.5; done test -S /var/run/defguard.socket From 97e197914fb07b438d6eba294025069aa0b0fdca Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:21:22 +0200 Subject: [PATCH 07/26] add onlybuiltdependencies --- e2e/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e/package.json b/e2e/package.json index 854692bf..3be075fa 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -7,6 +7,9 @@ "test": "wdio run ./wdio.conf.ts", "provision": "node scripts/provision.mjs" }, + "pnpm": { + "onlyBuiltDependencies": ["edgedriver", "geckodriver"] + }, "devDependencies": { "@types/node": "^25.9.2", "@wdio/cli": "^9.20.0", From e4cbe970ae5b86af6da0f3eddac2fcada887da4a Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 3 Jul 2026 17:55:23 +0200 Subject: [PATCH 08/26] add to pnpm-workspace --- e2e/package.json | 3 --- e2e/pnpm-workspace.yaml | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 3be075fa..854692bf 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -7,9 +7,6 @@ "test": "wdio run ./wdio.conf.ts", "provision": "node scripts/provision.mjs" }, - "pnpm": { - "onlyBuiltDependencies": ["edgedriver", "geckodriver"] - }, "devDependencies": { "@types/node": "^25.9.2", "@wdio/cli": "^9.20.0", diff --git a/e2e/pnpm-workspace.yaml b/e2e/pnpm-workspace.yaml index 5ed0b5af..97481469 100644 --- a/e2e/pnpm-workspace.yaml +++ b/e2e/pnpm-workspace.yaml @@ -1,2 +1,4 @@ allowBuilds: esbuild: true + edgedriver: true + geckodriver: true From ae338ee1cce2e5a8ae114aa255751ff948d0e485 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Sun, 5 Jul 2026 18:05:54 +0200 Subject: [PATCH 09/26] change node version --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 150d41ff..7cec2114 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -41,7 +41,7 @@ jobs: - uses: actions/setup-node@v6 with: - node-version: 26 + node-version: 22 - name: Install pnpm uses: pnpm/action-setup@v6 From 36eb48b3a328e740f661e0125e2e0cb190eae753 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Sun, 5 Jul 2026 18:30:24 +0200 Subject: [PATCH 10/26] add packages --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 7cec2114..475a98ef 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -33,7 +33,7 @@ jobs: libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev \ librsvg2-dev patchelf libssl-dev libxdo-dev protobuf-compiler \ libprotobuf-dev webkit2gtk-driver xvfb wireguard-tools iproute2 \ - iputils-ping procps xclip + iputils-ping procps xclip desktop-file-utils xdg-utils - uses: actions/checkout@v6 with: From e6f322d4f22a4e78ac070e2301049c2f6c065c98 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Sun, 5 Jul 2026 19:07:17 +0200 Subject: [PATCH 11/26] test wg-tunnel --- .github/workflows/e2e.yaml | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 475a98ef..d615a8b2 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -87,7 +87,9 @@ jobs: - name: Start defguard-service run: | groupadd -f defguard - setsid src-tauri/target/release/defguard-service < /dev/null & + modprobe wireguard || echo "WARN: could not modprobe wireguard" + setsid src-tauri/target/release/defguard-service < /dev/null \ + > "$RUNNER_TEMP/defguard-service.log" 2>&1 & for _ in $(seq 1 40); do [ -S /var/run/defguard.socket ] && break; sleep 0.5; done test -S /var/run/defguard.socket @@ -95,6 +97,31 @@ jobs: run: pnpm install --no-frozen-lockfile working-directory: e2e + - name: Provision core (network + gateway check) + run: pnpm provision + working-directory: e2e + - name: Run e2e tests run: xvfb-run -a pnpm test working-directory: e2e + + - name: Tunnel diagnostics + if: always() + run: | + # Keep this secret-safe: no peer endpoints (NETWORK_ENDPOINT), no keys/tokens. + echo "===== wireguard module =====" + lsmod | grep -i wireguard || echo "wireguard module NOT loaded" + echo "===== wireguard interfaces =====" + wg show interfaces || echo "no wg interfaces" + echo "===== latest handshakes (proves tunnel came up; pubkey + time only) =====" + wg show all latest-handshakes || true + echo "===== transfer (pubkey + bytes only) =====" + wg show all transfer || true + echo "===== wireguard addrs / routes =====" + ip -br addr show type wireguard || true + ip route show table all | grep -Ei 'wg|10\.10\.10\.' || true + echo "===== ping gateway =====" + ping -c 3 -W 5 10.10.10.1 || true + echo "===== defguard-service log (base64 keys redacted) =====" + sed -E 's#[A-Za-z0-9+/]{42}[A-Za-z0-9+/]=##g' \ + "$RUNNER_TEMP/defguard-service.log" 2>/dev/null | tail -n 200 || true From 4d3d5bfc53cf230bc1fb257769af90ec6cab1067 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Sun, 5 Jul 2026 19:29:42 +0200 Subject: [PATCH 12/26] change provision.mjs --- e2e/scripts/provision.mjs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/e2e/scripts/provision.mjs b/e2e/scripts/provision.mjs index cf80bc08..38e9ec4b 100644 --- a/e2e/scripts/provision.mjs +++ b/e2e/scripts/provision.mjs @@ -5,12 +5,7 @@ import path from 'node:path'; const envFile = path.resolve(import.meta.dirname, '../.env'); if (fs.existsSync(envFile)) { - for (const line of fs.readFileSync(envFile, 'utf8').split('\n')) { - const match = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/); - if (match && process.env[match[1]] === undefined) { - process.env[match[1]] = match[2]; - } - } + process.loadEnvFile(envFile); } const CORE_URL = requireEnv('CORE_URL'); From 997195eb87f5b93204ff70d44d8a26dc8acc2c8e Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Sun, 5 Jul 2026 19:54:30 +0200 Subject: [PATCH 13/26] add openresolv --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index d615a8b2..d8d7e175 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -33,7 +33,7 @@ jobs: libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev \ librsvg2-dev patchelf libssl-dev libxdo-dev protobuf-compiler \ libprotobuf-dev webkit2gtk-driver xvfb wireguard-tools iproute2 \ - iputils-ping procps xclip desktop-file-utils xdg-utils + iputils-ping procps xclip desktop-file-utils xdg-utils openresolv - uses: actions/checkout@v6 with: From 688d9149f1cf23ccad59c388f1895fae8f1fe366 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Sun, 5 Jul 2026 20:08:02 +0200 Subject: [PATCH 14/26] resolvconf issue --- .github/workflows/e2e.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index d8d7e175..1e2c806e 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -33,7 +33,7 @@ jobs: libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev \ librsvg2-dev patchelf libssl-dev libxdo-dev protobuf-compiler \ libprotobuf-dev webkit2gtk-driver xvfb wireguard-tools iproute2 \ - iputils-ping procps xclip desktop-file-utils xdg-utils openresolv + iputils-ping procps xclip desktop-file-utils xdg-utils - uses: actions/checkout@v6 with: @@ -84,6 +84,11 @@ jobs: - name: Check deployment is reachable run: curl -sf --max-time 15 "${{ secrets.E2E_CORE_URL }}/api/v1/health" + - name: Stub resolvconf + run: | + printf '#!/bin/sh\ncat >/dev/null 2>&1 || true\nexit 0\n' > /usr/local/sbin/resolvconf + chmod +x /usr/local/sbin/resolvconf + - name: Start defguard-service run: | groupadd -f defguard From 298dd22a5d74a8edbf5722b47821996d4ec58fd8 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Sun, 5 Jul 2026 20:24:59 +0200 Subject: [PATCH 15/26] add maxretires/retrydelay --- e2e/wdio.conf.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/wdio.conf.ts b/e2e/wdio.conf.ts index a6b4a8c1..bd8bdbd1 100644 --- a/e2e/wdio.conf.ts +++ b/e2e/wdio.conf.ts @@ -115,7 +115,7 @@ export const config: WebdriverIO.Config = { afterSession: () => { cleanup(); if (dataDir) { - fs.rmSync(dataDir, { recursive: true, force: true }); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); dataDir = undefined; } }, From f63511e69f9b4c18801b96ee1aa3ef582f4fa828 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:04:20 +0200 Subject: [PATCH 16/26] add biome, remove comments --- .github/workflows/e2e.yaml | 26 +-------- e2e/biome.json | 46 +++++++++++++++ e2e/helpers/connection.ts | 7 ++- e2e/helpers/coreApi.ts | 39 +++++++++---- e2e/helpers/enrollment.ts | 8 ++- e2e/helpers/mfa.ts | 17 ++++-- e2e/helpers/wireguard.ts | 12 ++-- e2e/package.json | 5 +- e2e/pnpm-lock.yaml | 91 ++++++++++++++++++++++++++++++ e2e/scripts/provision.mjs | 19 +------ e2e/tests/logs.spec.ts | 5 +- e2e/tests/wireguard_tunnel.spec.ts | 10 +++- e2e/wdio.conf.ts | 9 ++- 13 files changed, 218 insertions(+), 76 deletions(-) create mode 100644 e2e/biome.json diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 1e2c806e..2838eecf 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -92,9 +92,8 @@ jobs: - name: Start defguard-service run: | groupadd -f defguard - modprobe wireguard || echo "WARN: could not modprobe wireguard" - setsid src-tauri/target/release/defguard-service < /dev/null \ - > "$RUNNER_TEMP/defguard-service.log" 2>&1 & + modprobe wireguard || true + setsid src-tauri/target/release/defguard-service < /dev/null & for _ in $(seq 1 40); do [ -S /var/run/defguard.socket ] && break; sleep 0.5; done test -S /var/run/defguard.socket @@ -109,24 +108,3 @@ jobs: - name: Run e2e tests run: xvfb-run -a pnpm test working-directory: e2e - - - name: Tunnel diagnostics - if: always() - run: | - # Keep this secret-safe: no peer endpoints (NETWORK_ENDPOINT), no keys/tokens. - echo "===== wireguard module =====" - lsmod | grep -i wireguard || echo "wireguard module NOT loaded" - echo "===== wireguard interfaces =====" - wg show interfaces || echo "no wg interfaces" - echo "===== latest handshakes (proves tunnel came up; pubkey + time only) =====" - wg show all latest-handshakes || true - echo "===== transfer (pubkey + bytes only) =====" - wg show all transfer || true - echo "===== wireguard addrs / routes =====" - ip -br addr show type wireguard || true - ip route show table all | grep -Ei 'wg|10\.10\.10\.' || true - echo "===== ping gateway =====" - ping -c 3 -W 5 10.10.10.1 || true - echo "===== defguard-service log (base64 keys redacted) =====" - sed -E 's#[A-Za-z0-9+/]{42}[A-Za-z0-9+/]=##g' \ - "$RUNNER_TEMP/defguard-service.log" 2>/dev/null | tail -n 200 || true diff --git a/e2e/biome.json b/e2e/biome.json new file mode 100644 index 00000000..a9d4d620 --- /dev/null +++ b/e2e/biome.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "includes": ["helpers/**", "tests/**", "scripts/**", "wdio.conf.ts"] + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 90, + "bracketSpacing": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSameLine": false, + "bracketSpacing": true + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/e2e/helpers/connection.ts b/e2e/helpers/connection.ts index 20510670..2ca48174 100644 --- a/e2e/helpers/connection.ts +++ b/e2e/helpers/connection.ts @@ -8,16 +8,17 @@ export const connectAndPing = async (totpSecret?: string) => { await connect.click(); if (totpSecret) { - await $('#mfa-totp-view').waitForDisplayed(); + const view = $('#mfa-totp-view'); + await view.waitForDisplayed(); await submitTotpCode( totpSecret, '#mfa-totp-view', async () => { - const verify = $('#mfa-totp-view').$('button=Verify'); + const verify = view.$('button=Verify'); await verify.waitForClickable(); await verify.click(); }, - () => $('#mfa-totp-view').isDisplayed().then((shown) => !shown, () => true), + async () => !(await view.isDisplayed().catch(() => false)), ); } diff --git a/e2e/helpers/coreApi.ts b/e2e/helpers/coreApi.ts index f5c9a18b..f2e52004 100644 --- a/e2e/helpers/coreApi.ts +++ b/e2e/helpers/coreApi.ts @@ -1,6 +1,3 @@ -// Minimal client for the defguard core REST API, used to provision fixtures -// (users, enrollment tokens, location MFA mode) from within specs. - const MIN_PEER_DISCONNECT_THRESHOLD_WITH_MFA = 120; const requireEnv = (name: string): string => { @@ -27,7 +24,11 @@ export interface EnrollmentFixture { export class CoreApi { private cookie = ''; - private async request(method: string, apiPath: string, body?: unknown): Promise { + private async request( + method: string, + apiPath: string, + body?: unknown, + ): Promise { const response = await fetch(`${coreUrl()}${apiPath}`, { method, redirect: 'manual', @@ -81,9 +82,14 @@ export class CoreApi { await this.request('DELETE', `/api/v1/user/${username}`); } - async listNetworks(): Promise> { + async listNetworks(): Promise< + Array<{ id: number; location_mfa_mode: LocationMfaMode }> + > { const response = await this.request('GET', '/api/v1/network'); - return (await response.json()) as Array<{ id: number; location_mfa_mode: LocationMfaMode }>; + return (await response.json()) as Array<{ + id: number; + location_mfa_mode: LocationMfaMode; + }>; } async findAvailableIp(networkId: number): Promise { @@ -92,7 +98,6 @@ export class CoreApi { return ips[0].ip; } - // Registers a standalone WireGuard device and returns its generated config. async addNetworkDevice( networkId: number, name: string, @@ -112,7 +117,10 @@ export class CoreApi { // Core reports settings.mfa_required (which drives the enrollment MFA steps) // when a location uses 'internal' MFA. Returns the previous mode to restore. - async setLocationMfaMode(networkId: number, mode: LocationMfaMode): Promise { + async setLocationMfaMode( + networkId: number, + mode: LocationMfaMode, + ): Promise { const current = (await ( await this.request('GET', `/api/v1/network/${networkId}`) ).json()) as Record; @@ -146,10 +154,17 @@ export class CoreApi { return previous; } - private async startEnrollment(username: string, ephemeral: boolean): Promise { - const response = await this.request('POST', `/api/v1/user/${username}/start_enrollment`, { - send_enrollment_notification: false, - }); + private async startEnrollment( + username: string, + ephemeral: boolean, + ): Promise { + const response = await this.request( + 'POST', + `/api/v1/user/${username}/start_enrollment`, + { + send_enrollment_notification: false, + }, + ); const data = (await response.json()) as { enrollment_token: string }; return { username, diff --git a/e2e/helpers/enrollment.ts b/e2e/helpers/enrollment.ts index 8dab14b1..fcab1935 100644 --- a/e2e/helpers/enrollment.ts +++ b/e2e/helpers/enrollment.ts @@ -37,10 +37,12 @@ export const configureTotp = async (): Promise => { await expect($('#mfa-configuration-step')).toBeDisplayed(); const secretField = $('#mfa-configuration-step .copy-field .track p'); await secretField.waitForExist(); - const raw = (await secretField.getProperty('textContent')) as string | null; - const secret = (raw ?? '').replace(/\s/g, '').toUpperCase(); + const secret = + ((await secretField.getProperty('textContent')) as string | null)?.trim() ?? ''; await submitTotpCode(secret, '#mfa-configuration-step', clickNext, () => - $('#recovery-codes-step').isDisplayed().catch(() => false), + $('#recovery-codes-step') + .isDisplayed() + .catch(() => false), ); await $('#recovery-codes-step .checkbox').click(); const complete = $('#recovery-codes-step').$('button=Complete'); diff --git a/e2e/helpers/mfa.ts b/e2e/helpers/mfa.ts index 18edb63a..c0fdeea9 100644 --- a/e2e/helpers/mfa.ts +++ b/e2e/helpers/mfa.ts @@ -15,13 +15,18 @@ export const submitTotpCode = async ( submit: () => Promise, accepted: () => Promise, ) => { - for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { await fillCode(scope, totpCode(secret)); await submit(); - const ok = await browser - .waitUntil(accepted, { timeout: 6_000, interval: 500 }) - .then(() => true, () => false); - if (ok) return; + try { + await browser.waitUntil(accepted, { + timeout: 6_000, + interval: 500, + timeoutMsg: 'TOTP code was not accepted after several attempts', + }); + return; + } catch (error) { + if (attempt === MAX_ATTEMPTS) throw error; + } } - throw new Error('TOTP code was not accepted after several attempts'); }; diff --git a/e2e/helpers/wireguard.ts b/e2e/helpers/wireguard.ts index 7c90f7a6..6e107560 100644 --- a/e2e/helpers/wireguard.ts +++ b/e2e/helpers/wireguard.ts @@ -4,8 +4,14 @@ import type { CoreApi } from './coreApi.js'; export const generateWireguardKeys = () => { const { privateKey, publicKey } = generateKeyPairSync('x25519'); return { - privateKey: privateKey.export({ type: 'pkcs8', format: 'der' }).subarray(-32).toString('base64'), - publicKey: publicKey.export({ type: 'spki', format: 'der' }).subarray(-32).toString('base64'), + privateKey: privateKey + .export({ type: 'pkcs8', format: 'der' }) + .subarray(-32) + .toString('base64'), + publicKey: publicKey + .export({ type: 'spki', format: 'der' }) + .subarray(-32) + .toString('base64'), }; }; @@ -21,8 +27,6 @@ export type TunnelConfig = { keepalive: string; }; -// Registers a network device in core for the given location and turns the -// WireGuard config core returns into the values the client's tunnel wizard needs. export const provisionTunnel = async ( core: CoreApi, networkId: number, diff --git a/e2e/package.json b/e2e/package.json index 854692bf..8d0d66d5 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -5,9 +5,12 @@ "type": "module", "scripts": { "test": "wdio run ./wdio.conf.ts", - "provision": "node scripts/provision.mjs" + "provision": "node scripts/provision.mjs", + "fix": "biome check --fix", + "lint": "biome check" }, "devDependencies": { + "@biomejs/biome": "2.4.16", "@types/node": "^25.9.2", "@wdio/cli": "^9.20.0", "@wdio/globals": "^9.27.2", diff --git a/e2e/pnpm-lock.yaml b/e2e/pnpm-lock.yaml index 2a28d6d2..238ee63b 100644 --- a/e2e/pnpm-lock.yaml +++ b/e2e/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@biomejs/biome': + specifier: 2.4.16 + version: 2.4.16 '@types/node': specifier: ^25.9.2 version: 25.9.2 @@ -49,6 +52,59 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} + '@biomejs/biome@2.4.16': + resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.4.16': + resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.4.16': + resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.4.16': + resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.4.16': + resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.4.16': + resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.4.16': + resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.4.16': + resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.4.16': + resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@esbuild/aix-ppc64@0.28.0': resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} engines: {node: '>=18'} @@ -1901,6 +1957,41 @@ snapshots: '@babel/helper-validator-identifier@7.29.7': {} + '@biomejs/biome@2.4.16': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.16 + '@biomejs/cli-darwin-x64': 2.4.16 + '@biomejs/cli-linux-arm64': 2.4.16 + '@biomejs/cli-linux-arm64-musl': 2.4.16 + '@biomejs/cli-linux-x64': 2.4.16 + '@biomejs/cli-linux-x64-musl': 2.4.16 + '@biomejs/cli-win32-arm64': 2.4.16 + '@biomejs/cli-win32-x64': 2.4.16 + + '@biomejs/cli-darwin-arm64@2.4.16': + optional: true + + '@biomejs/cli-darwin-x64@2.4.16': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.4.16': + optional: true + + '@biomejs/cli-linux-arm64@2.4.16': + optional: true + + '@biomejs/cli-linux-x64-musl@2.4.16': + optional: true + + '@biomejs/cli-linux-x64@2.4.16': + optional: true + + '@biomejs/cli-win32-arm64@2.4.16': + optional: true + + '@biomejs/cli-win32-x64@2.4.16': + optional: true + '@esbuild/aix-ppc64@0.28.0': optional: true diff --git a/e2e/scripts/provision.mjs b/e2e/scripts/provision.mjs index 38e9ec4b..98c0763d 100644 --- a/e2e/scripts/provision.mjs +++ b/e2e/scripts/provision.mjs @@ -75,8 +75,8 @@ async function api(method, apiPath, body) { const allow = res.headers.get('allow'); throw new Error( `${method} ${apiPath} failed: ${res.status}` + - (allow ? ` (Allow: ${allow})` : '') + - ` ${await res.text()}`, + (allow ? ` (Allow: ${allow})` : '') + + ` ${await res.text()}`, ); } return res; @@ -94,12 +94,7 @@ try { }); } catch (error) { if (String(error).includes('405')) { - console.error( - `\nCore at ${CORE_URL} is still in initial-setup mode. ` + - `Open ${CORE_URL} in a browser and complete the setup wizard ` + - '(all the way through the final step — core restarts afterwards), ' + - 'then re-run this script.\n', - ); + console.error(`\nCore at ${CORE_URL} is still in initial-setup mode`); process.exit(1); } throw error; @@ -113,7 +108,6 @@ let network = existing.find((n) => n.name === NETWORK.name); if (network) { console.log(`Network "${NETWORK.name}" already exists with id ${network.id}`); } else { - console.log('Creating VPN network...'); network = await (await api('POST', '/api/v1/network', NETWORK)).json(); console.log(`Network created with id ${network.id}`); } @@ -122,13 +116,6 @@ const gateways = await ( await api('GET', `/api/v1/network/${network.id}/gateways`) ).json(); if (gateways.length === 0) { - console.error( - `\nNo gateway is connected to network "${NETWORK.name}" (id ${network.id}).\n` + - 'Gateway setup is intentionally not automated — add and connect one yourself,\n' + - `then re-run this script. See https://docs.defguard.net for the deployment guide.\n`, - ); process.exit(1); } console.log(`Gateway connected: ${gateways[0].name}`); - -console.log('Provisioning done.'); diff --git a/e2e/tests/logs.spec.ts b/e2e/tests/logs.spec.ts index 181bd4a9..020359d4 100644 --- a/e2e/tests/logs.spec.ts +++ b/e2e/tests/logs.spec.ts @@ -19,9 +19,8 @@ describe('logs', () => { }); it('copies logs to the clipboard', async () => { - const sample = ( - (await $('#log-page-view .log-container p').getProperty('textContent')) as string - ).trim(); + const firstLine = $('#log-page-view .log-container p'); + const sample = ((await firstLine.getProperty('textContent')) as string).trim(); await openActionsMenu(); const copy = $('.menu-item*=Copy to Clipboard'); await copy.waitForClickable(); diff --git a/e2e/tests/wireguard_tunnel.spec.ts b/e2e/tests/wireguard_tunnel.spec.ts index ae978527..8d6c5499 100644 --- a/e2e/tests/wireguard_tunnel.spec.ts +++ b/e2e/tests/wireguard_tunnel.spec.ts @@ -1,5 +1,9 @@ import { $, expect } from '@wdio/globals'; -import { type CoreApi, type LocationMfaMode, loggedInCoreApi } from '../helpers/coreApi.js'; +import { + type CoreApi, + type LocationMfaMode, + loggedInCoreApi, +} from '../helpers/coreApi.js'; import { switchToFullView } from '../helpers/windows.js'; import { provisionTunnel } from '../helpers/wireguard.js'; @@ -53,6 +57,8 @@ describe.skip('WireGuard tunnel', () => { await continueStep('#advanced-settings-step'); await expect($('#finish-step')).toBeDisplayed(); - await expect($('#finish-step')).toHaveText('added successfully', { containing: true }); + await expect($('#finish-step')).toHaveText('added successfully', { + containing: true, + }); }); }); diff --git a/e2e/wdio.conf.ts b/e2e/wdio.conf.ts index bd8bdbd1..d2295761 100644 --- a/e2e/wdio.conf.ts +++ b/e2e/wdio.conf.ts @@ -81,7 +81,7 @@ export const config: WebdriverIO.Config = { if (!fs.existsSync(clientBinary)) { throw new Error( `Client binary not found at ${clientBinary}. Build it with ` + - '`pnpm tauri build` or set CLIENT_BINARY.', + '`pnpm tauri build` or set CLIENT_BINARY.', ); } }, @@ -115,7 +115,12 @@ export const config: WebdriverIO.Config = { afterSession: () => { cleanup(); if (dataDir) { - fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(dataDir, { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }); dataDir = undefined; } }, From d680b05936107406453f3fb4309d4acaa2ac9c30 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 6 Jul 2026 11:41:00 +0200 Subject: [PATCH 17/26] test tray view --- e2e/helpers/connection.ts | 22 ++++++++++++++++------ e2e/helpers/windows.ts | 36 ++++++++++++++++++++++-------------- e2e/tests/enrollment.spec.ts | 26 +++++++++++++++++++++----- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/e2e/helpers/connection.ts b/e2e/helpers/connection.ts index 2ca48174..8fa5b93a 100644 --- a/e2e/helpers/connection.ts +++ b/e2e/helpers/connection.ts @@ -2,17 +2,20 @@ import { $, browser } from '@wdio/globals'; import { submitTotpCode } from './mfa.js'; import { canPingGateway } from './tunnel.js'; -export const connectAndPing = async (totpSecret?: string) => { - const connect = $('.connect-button'); - await connect.waitForClickable(); - await connect.click(); +export const FULL_MFA_VIEW = '#mfa-totp-view'; +export const TRAY_MFA_VIEW = '.location-card-mfa-totp-view'; + +export const connectAndPing = async (mfaView: string, totpSecret?: string) => { + const button = $('.connect-button'); + await button.waitForClickable(); + await button.click(); if (totpSecret) { - const view = $('#mfa-totp-view'); + const view = $(mfaView); await view.waitForDisplayed(); await submitTotpCode( totpSecret, - '#mfa-totp-view', + mfaView, async () => { const verify = view.$('button=Verify'); await verify.waitForClickable(); @@ -28,3 +31,10 @@ export const connectAndPing = async (totpSecret?: string) => { timeoutMsg: 'Could not ping the gateway through the VPN', }); }; + +export const disconnect = async () => { + const button = $('.connect-button.connected'); + await button.waitForClickable(); + await button.click(); + await $('.connect-button.disconnected').waitForDisplayed(); +}; diff --git a/e2e/helpers/windows.ts b/e2e/helpers/windows.ts index 20f04e45..49b15043 100644 --- a/e2e/helpers/windows.ts +++ b/e2e/helpers/windows.ts @@ -1,15 +1,23 @@ -import { browser } from '@wdio/globals'; +import { $, browser } from '@wdio/globals'; -export const switchToFullView = () => - browser.waitUntil( - async () => { - for (const handle of await browser.getWindowHandles()) { - await browser.switchToWindow(handle); - if ((await browser.getUrl()).includes('/full')) { - return true; - } - } - return false; - }, - { timeoutMsg: 'No full view window found' }, - ); + +export const switchToFullView = async () => { + for (const handle of await browser.getWindowHandles()) { + await browser.switchToWindow(handle); + const url = await browser.getUrl(); + if (url.includes('/full')) { + return; + } + if (url.includes('/compact')) { + await browser.url('tauri://localhost/full/'); + return; + } + } + throw new Error('No full view window found'); +}; + +export const switchToTrayView = async () => { + await switchToFullView(); + await browser.url('tauri://localhost/compact/'); + await $('#compact-locations-page').waitForDisplayed(); +}; diff --git a/e2e/tests/enrollment.spec.ts b/e2e/tests/enrollment.spec.ts index 95739760..69a75fad 100644 --- a/e2e/tests/enrollment.spec.ts +++ b/e2e/tests/enrollment.spec.ts @@ -1,6 +1,11 @@ import { $, expect } from '@wdio/globals'; import { resetInstances } from '../helpers/client.js'; -import { connectAndPing } from '../helpers/connection.js'; +import { + connectAndPing, + disconnect, + FULL_MFA_VIEW, + TRAY_MFA_VIEW, +} from '../helpers/connection.js'; import { type CoreApi, type EnrollmentFixture, @@ -13,6 +18,7 @@ import { finishEnrollment, setPassword, } from '../helpers/enrollment.js'; +import { switchToTrayView } from '../helpers/windows.js'; describe('enrollment', () => { let core: CoreApi; @@ -33,7 +39,7 @@ describe('enrollment', () => { } }); - it('enrolls a user without MFA and connects to the VPN', async () => { + it('enrolls a user without MFA and connects from the full and tray views', async () => { previousMfaMode = await core.setLocationMfaMode(networkId, 'disabled'); fixture = await core.createEnrollmentFixture(); @@ -41,10 +47,15 @@ describe('enrollment', () => { await setPassword(); await expect($('#mfa-configuration-step')).not.toBeDisplayed(); await finishEnrollment(); - await connectAndPing(); + + await connectAndPing(FULL_MFA_VIEW); + await disconnect(); + + await switchToTrayView(); + await connectAndPing(TRAY_MFA_VIEW); }); - it('enrolls a user with TOTP MFA and connects to the VPN', async () => { + it('enrolls a user with TOTP MFA and connects from the full and tray views', async () => { previousMfaMode = await core.setLocationMfaMode(networkId, 'internal'); fixture = await core.createEnrollmentFixture(); @@ -52,6 +63,11 @@ describe('enrollment', () => { await setPassword(); const secret = await configureTotp(); await finishEnrollment(); - await connectAndPing(secret); + + await connectAndPing(FULL_MFA_VIEW, secret); + await disconnect(); + + await switchToTrayView(); + await connectAndPing(TRAY_MFA_VIEW, secret); }); }); From e3f1893d8a60f99022319bd3a8bbe2ce8ae8580b Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 6 Jul 2026 12:00:38 +0200 Subject: [PATCH 18/26] remove trigger --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 2838eecf..fd53eb50 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -3,7 +3,7 @@ name: E2E tests on: workflow_dispatch: push: - branches: [main, dev, 'release/**', e2e-tests] + branches: [main, dev, 'release/**'] paths-ignore: ['*.md', 'LICENSE'] pull_request: branches: [main, dev, 'release/**'] From 2a72a081d3d68bbf8d0ea6419241d43d2e9dc364 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 6 Jul 2026 12:05:42 +0200 Subject: [PATCH 19/26] remove comments --- e2e/helpers/coreApi.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/e2e/helpers/coreApi.ts b/e2e/helpers/coreApi.ts index f2e52004..17359502 100644 --- a/e2e/helpers/coreApi.ts +++ b/e2e/helpers/coreApi.ts @@ -17,7 +17,6 @@ export interface EnrollmentFixture { username: string; enrollmentToken: string; enrollmentUrl: string; - // The test owns and deletes this user unless it is pinned via TEST_USERNAME. ephemeral: boolean; } @@ -115,8 +114,6 @@ export class CoreApi { return data.config.config; } - // Core reports settings.mfa_required (which drives the enrollment MFA steps) - // when a location uses 'internal' MFA. Returns the previous mode to restore. async setLocationMfaMode( networkId: number, mode: LocationMfaMode, From 69ef930a23498d97dc8112c0a5543a59eb12ffd5 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 6 Jul 2026 12:11:17 +0200 Subject: [PATCH 20/26] pnpm lint job, pnpm fix --- .github/workflows/e2e.yaml | 24 ++++++++++++++++++++++++ e2e/helpers/windows.ts | 1 - 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index fd53eb50..11fc01b3 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -108,3 +108,27 @@ jobs: - name: Run e2e tests run: xvfb-run -a pnpm test working-directory: e2e + + lint-e2e: + runs-on: + - codebuild-defguard-client-runner-${{ github.run_id }}-${{ github.run_attempt }} + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v6 + with: + node-version: 22 + + - name: Install pnpm + uses: pnpm/action-setup@v6 + with: + version: 11 + run_install: false + + - name: Install e2e dependencies + run: pnpm install --no-frozen-lockfile + working-directory: e2e + + - name: Lint e2e + run: pnpm lint + working-directory: e2e diff --git a/e2e/helpers/windows.ts b/e2e/helpers/windows.ts index 49b15043..6fa52636 100644 --- a/e2e/helpers/windows.ts +++ b/e2e/helpers/windows.ts @@ -1,6 +1,5 @@ import { $, browser } from '@wdio/globals'; - export const switchToFullView = async () => { for (const handle of await browser.getWindowHandles()) { await browser.switchToWindow(handle); From a2362124b11c29956de6b68be234ec3c61cdd0b1 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 6 Jul 2026 12:21:55 +0200 Subject: [PATCH 21/26] change e2e workflow --- .github/workflows/e2e.yaml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 11fc01b3..d0fd8021 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -10,8 +10,8 @@ on: paths-ignore: ['*.md', 'LICENSE'] concurrency: - group: e2e - cancel-in-progress: false + group: e2e-${{ github.ref }} + cancel-in-progress: true jobs: e2e: @@ -25,6 +25,8 @@ jobs: CLIENT_BINARY: ${{ github.workspace }}/src-tauri/target/release/defguard-client NATIVE_DRIVER: /usr/bin/WebKitWebDriver SQLX_OFFLINE: '1' + RUSTC_WRAPPER: sccache + SCCACHE_GHA_ENABLED: 'true' steps: - name: Install system dependencies run: | @@ -41,7 +43,7 @@ jobs: - uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 26 - name: Install pnpm uses: pnpm/action-setup@v6 @@ -50,18 +52,11 @@ jobs: version: 11 run_install: false - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - name: Install Rust stable uses: dtolnay/rust-toolchain@stable - - name: Rust cache - uses: Swatinem/rust-cache@v2 - with: - workspaces: src-tauri + - name: Install sccache + uses: mozilla-actions/sccache-action@v0.0.10 - name: Install tauri-driver run: cargo install tauri-driver --locked From 09377bb964c0191b8ebbe2de0c81e7ac45b6fa96 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 6 Jul 2026 12:28:58 +0200 Subject: [PATCH 22/26] change biome root to false --- e2e/biome.json | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/biome.json b/e2e/biome.json index a9d4d620..4ed56c68 100644 --- a/e2e/biome.json +++ b/e2e/biome.json @@ -1,5 +1,6 @@ { "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json", + "root": false, "vcs": { "enabled": false, "clientKind": "git", From 8c246df7b78f54deb9fe31748dae7e305220a8cf Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 6 Jul 2026 12:43:06 +0200 Subject: [PATCH 23/26] downgrade node-versin --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index d0fd8021..b0a5fecb 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -43,7 +43,7 @@ jobs: - uses: actions/setup-node@v6 with: - node-version: 26 + node-version: 22 - name: Install pnpm uses: pnpm/action-setup@v6 From 6d595e5ba74aa2308260b5eb0ab03f4d46db175a Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:44:00 +0200 Subject: [PATCH 24/26] test node version 24 --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index b0a5fecb..2b0a1921 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -43,7 +43,7 @@ jobs: - uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 - name: Install pnpm uses: pnpm/action-setup@v6 From 3f652b73e04a7db1f853f8cac6f48c11cec03ac5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:05:32 +0000 Subject: [PATCH 25/26] chore(nix): update pnpm deps hash --- nix/package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/package.nix b/nix/package.nix index 18cb095d..d61dcbee 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -90,7 +90,7 @@ inherit pname version pnpm; src = ../.; fetcherVersion = 3; - hash = "sha256-vqBzk7E++I1A/dyOSBhzNTd1VkyVi4TMKBOMlAr0+T4="; + hash = "sha256-Ktczlfddb77pNoo16sZTQ19ZpG5IrEpKigFXDEiT42k="; }; # Prefetch pnpm dependencies for the new UI (separate pnpm project). From efc764ed06ff0c0f4a5c5805024d3aef0fa4d6bf Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:27:50 +0200 Subject: [PATCH 26/26] bump node version in e2e-lint --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 2b0a1921..175576bc 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -112,7 +112,7 @@ jobs: - uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 - name: Install pnpm uses: pnpm/action-setup@v6