diff --git a/.syncpackrc b/.syncpackrc index 3cd3ddda55..5aeae05581 100644 --- a/.syncpackrc +++ b/.syncpackrc @@ -1,5 +1,12 @@ { "versionGroups": [ + { + "label": "packages/zql-benchmarks/rn is an Expo app that is deliberately not a pnpm workspace member (see packages/replicache-perf/rn/README.md). It pins the React, TypeScript and Expo versions its React Native release needs, independently of the workspace's.", + "packages": [ + "zql-benchmarks-rn" + ], + "isIgnored": true + }, { "label": "packages/replicache-perf/rn is an Expo app that is deliberately not a pnpm workspace member (see its README). It pins the React, TypeScript and Expo versions its React Native release needs, independently of the workspace's.", "packages": [ diff --git a/oxlint.base.ts b/oxlint.base.ts index cb3f95ed4a..bc1d6f912b 100644 --- a/oxlint.base.ts +++ b/oxlint.base.ts @@ -31,6 +31,7 @@ export const baseConfig = { // expo/tsconfig.base from its own node_modules, which type-aware linting // cannot resolve from the repo root. 'packages/replicache-perf/rn', + 'packages/zql-benchmarks/rn', ], rules: { // Disable unsafe optional chaining - many legitimate patterns in codebase diff --git a/packages/replicache-perf/package.json b/packages/replicache-perf/package.json index a8179f90f3..8645121c72 100644 --- a/packages/replicache-perf/package.json +++ b/packages/replicache-perf/package.json @@ -28,6 +28,7 @@ "playwright": "^1.60.0", "playwright-core": "1.53.2", "replicache": "workspace:*", + "rn-bench": "workspace:*", "shared": "workspace:*", "typescript": "~7.0.2", "vite": "^8.0.13", diff --git a/packages/replicache-perf/src/benchmarks/compare-utf8.ts b/packages/replicache-perf/src/benchmarks/compare-utf8.ts index 7dc535b8dd..f943dfcf04 100644 --- a/packages/replicache-perf/src/benchmarks/compare-utf8.ts +++ b/packages/replicache-perf/src/benchmarks/compare-utf8.ts @@ -1,5 +1,5 @@ import {compareUTF8} from 'compare-utf8'; -import type {Benchmark} from '../benchmark.ts'; +import type {Benchmark} from '../../../../tools/rn-bench/src/benchmark.ts'; import {makeRandomASCIIStrings, makeRandomStrings} from '../data.ts'; const encoder = new TextEncoder(); diff --git a/packages/replicache-perf/src/benchmarks/hash.ts b/packages/replicache-perf/src/benchmarks/hash.ts index 4c72081922..76ab759ea3 100644 --- a/packages/replicache-perf/src/benchmarks/hash.ts +++ b/packages/replicache-perf/src/benchmarks/hash.ts @@ -1,5 +1,5 @@ import {createSHA512} from 'hash-wasm'; -import type {Benchmark} from '../benchmark.ts'; +import type {Benchmark} from '../../../../tools/rn-bench/src/benchmark.ts'; import {makeRandomStrings} from '../data.ts'; const encoder = new TextEncoder(); diff --git a/packages/replicache-perf/src/benchmarks/idb.ts b/packages/replicache-perf/src/benchmarks/idb.ts index c017182ab0..07442c03c1 100644 --- a/packages/replicache-perf/src/benchmarks/idb.ts +++ b/packages/replicache-perf/src/benchmarks/idb.ts @@ -1,7 +1,10 @@ import {deleteDB, type IDBPDatabase, openDB} from 'idb/with-async-ittr'; // @ts-ignore Invalid module when using node16 module resolution. import xbytes from 'xbytes'; -import type {Bencher, Benchmark} from '../benchmark.ts'; +import type { + Bencher, + Benchmark, +} from '../../../../tools/rn-bench/src/benchmark.ts'; import {randomData, type RandomDataType} from '../data.ts'; function benchmarkIDBReadGetAll(opts: { diff --git a/packages/replicache-perf/src/benchmarks/map-loop.ts b/packages/replicache-perf/src/benchmarks/map-loop.ts index 7ecac0cf0e..8b0483efd2 100644 --- a/packages/replicache-perf/src/benchmarks/map-loop.ts +++ b/packages/replicache-perf/src/benchmarks/map-loop.ts @@ -1,4 +1,4 @@ -import type {Benchmark} from '../benchmark.ts'; +import type {Benchmark} from '../../../../tools/rn-bench/src/benchmark.ts'; export function benchmarks(): Array { return [forLoop(), forEach()]; diff --git a/packages/replicache-perf/src/benchmarks/replicache.ts b/packages/replicache-perf/src/benchmarks/replicache.ts index 31ca967a1d..da884675d9 100644 --- a/packages/replicache-perf/src/benchmarks/replicache.ts +++ b/packages/replicache-perf/src/benchmarks/replicache.ts @@ -1,4 +1,8 @@ import {resolver} from '@rocicorp/resolver'; +import type { + Bencher, + Benchmark, +} from '../../../../tools/rn-bench/src/benchmark.ts'; import { closeAndCleanupRep, createIndexDefinitions, @@ -24,7 +28,6 @@ import type { } from '../../../replicache/src/transactions.ts'; import {assert} from '../../../shared/src/asserts.ts'; import {deepEqual, type JSONValue} from '../../../shared/src/json.ts'; -import type {Bencher, Benchmark} from '../benchmark.ts'; import { getTmcwData, jsonArrayTestData, diff --git a/packages/replicache-perf/src/benchmarks/storage.ts b/packages/replicache-perf/src/benchmarks/storage.ts index 59097bfcb5..6d22d00a1f 100644 --- a/packages/replicache-perf/src/benchmarks/storage.ts +++ b/packages/replicache-perf/src/benchmarks/storage.ts @@ -1,4 +1,4 @@ -import type {Benchmark} from '../benchmark.ts'; +import type {Benchmark} from '../../../../tools/rn-bench/src/benchmark.ts'; import {randomString} from '../data.ts'; export function benchmarks(): Benchmark[] { diff --git a/packages/replicache-perf/src/index.ts b/packages/replicache-perf/src/index.ts index 87e8716b37..b72c6308d2 100644 --- a/packages/replicache-perf/src/index.ts +++ b/packages/replicache-perf/src/index.ts @@ -1,5 +1,5 @@ -import {runBenchmark} from './benchmark.ts'; -import {formatAsReplicache} from './format.ts'; +import {runBenchmark} from '../../../tools/rn-bench/src/benchmark.ts'; +import {formatAsReplicache} from '../../../tools/rn-bench/src/format.ts'; import * as m from './perf.ts'; import {benchmarks, findBenchmarks} from './perf.ts'; diff --git a/packages/replicache-perf/src/perf-rn.ts b/packages/replicache-perf/src/perf-rn.ts index 555689dcb1..418130216c 100644 --- a/packages/replicache-perf/src/perf-rn.ts +++ b/packages/replicache-perf/src/perf-rn.ts @@ -1,913 +1,49 @@ /** - * Runs the replicache benchmarks on a React Native device. + * Runs the Replicache benchmarks on a React Native device. * - * The web runner (runner.ts) serves the harness from Vite and drives it with - * Playwright. There is no Playwright for React Native, so the roles are - * inverted: this process serves a small HTTP control API and the Expo app in - * `replicache-perf-rn` pulls one benchmark at a time from it, posts the result - * back, and reloads its JS context between benchmarks the way runner.ts calls - * `page.reload()`. - * - * The only platform-specific parts are booting the device, making the host - * reachable and launching the app; those live behind {@link DeviceDriver}. + * Everything generic — device drivers, the HTTP control server, launching Expo, + * Hermes profiling — lives in `tools/rn-bench`. This file supplies only what is + * specific to this package: which benchmarks exist, the key/value backend axis, + * and the 9.7 MB tmcw fixture, which is served rather than bundled. */ -import { - execFile as execFileCb, - spawn, - type ChildProcess, -} from 'node:child_process'; import {createReadStream} from 'node:fs'; -import * as fs from 'node:fs/promises'; -import * as http from 'node:http'; -import * as os from 'node:os'; import * as path from 'node:path'; -import process from 'node:process'; import {fileURLToPath} from 'node:url'; -import {promisify} from 'node:util'; -import commandLineArgs from 'command-line-args'; -import commandLineUsage from 'command-line-usage'; -import { - type BencherMetricsFormat, - toBencherMetricFormat, -} from './bencher-metric-format.ts'; -import type {BenchmarkResult} from './benchmark.ts'; +import {runRnBench} from '../../../tools/rn-bench/src/runner.ts'; import {benchmarks as mapLoopBenchmarks} from './benchmarks/map-loop.ts'; import {benchmarks as replicacheBenchmarks} from './benchmarks/replicache.ts'; -import {formatAsBenchmarkJS, formatAsReplicache} from './format.ts'; -import {createGithubActionBenchmarkJSONEntries} from './github-action-benchmark.ts'; -import { - connectSession, - runBenchmarkInApp, - selfTimeByFrame, -} from './hermes-trace.ts'; - -const execFile = promisify(execFileCb); const rootDir = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); -const allPlatforms = ['android', 'ios'] as const; -type Platform = (typeof allPlatforms)[number]; - -const allBackends = ['expo', 'op', 'mem'] as const; -type Backend = (typeof allBackends)[number]; - -type Format = 'benchmarkJS' | 'json' | 'replicache' | 'bmf'; - -/** Metro's default 8081 is often taken (Docker), so default one above it. */ -const DEFAULT_METRO_PORT = 8082; -const DEFAULT_CONTROL_PORT = 9099; -/** The Expo host app, checked in beside the harness it runs. */ -const DEFAULT_APP_DIR = path.join(rootDir, 'rn'); - -class UnknownValueError extends Error { - name = 'UNKNOWN_VALUE'; - value: string; - optionName: string; - - constructor(arg: string, optionName: string) { - super(`Unknown value ${arg}`); - this.value = arg; - this.optionName = '--' + optionName; - } -} - -function platform(arg: string): Platform | 'all' { - arg = arg.toLowerCase(); - if (!['all', ...allPlatforms].includes(arg)) { - throw new UnknownValueError(arg, 'platform'); - } - return arg as Platform | 'all'; -} - -function backend(arg: string): Backend | 'all' { - arg = arg.toLowerCase(); - if (!['all', ...allBackends].includes(arg)) { - throw new UnknownValueError(arg, 'backend'); - } - return arg as Backend | 'all'; -} - -function format(arg: string): Format { - if (!['benchmarkJS', 'json', 'replicache', 'bmf'].includes(arg)) { - throw new UnknownValueError(arg, 'format'); - } - return arg as Format; -} - -// --------------------------------------------------------------------------- -// Device drivers -// --------------------------------------------------------------------------- - -type DeviceDriver = { - readonly platform: Platform; - /** Boots a device if none is running; resolves to its name. */ - ensureBooted(requested: string | undefined): Promise; - /** Makes host ports reachable from the device. */ - forwardPorts(ports: number[]): Promise; - /** Args for `npx`, launching the app and Metro. */ - expoArgs(device: string, metroPort: number, release: boolean): string[]; - /** - * Retried until the app first contacts the control server. Exists because - * `expo run:ios` points the dev client at Metro with a deep link, and iOS - * puts a "Open in ...?" confirmation in front of that whenever some other app - * happens to be frontmost — which silently strands the run. - */ - nudge?(appDir: string, metroPort: number): Promise; -}; - -function adbPath(): string { - const home = - process.env.ANDROID_HOME ?? - process.env.ANDROID_SDK_ROOT ?? - path.join(os.homedir(), 'Library/Android/sdk'); - return path.join(home, 'platform-tools', 'adb'); -} - -const androidDriver: DeviceDriver = { - platform: 'android', - - async ensureBooted(requested) { - const avd = requested ?? 'medium_phone'; - const {stdout} = await execFile(adbPath(), ['devices']); - const booted = stdout - .split('\n') - .slice(1) - .filter(l => l.trim().endsWith('device')) - .map(l => l.split('\t')[0]); - if (booted.length === 0) { - throw new Error( - `No Android device. Start the emulator first:\n` + - ` $ANDROID_HOME/emulator/emulator -avd ${avd} &`, - ); - } - return avd; - }, - - async forwardPorts(ports) { - // The emulator's own localhost is not the host's, so every port the app - // talks to has to be reversed explicitly. - for (const p of ports) { - await execFile(adbPath(), ['reverse', `tcp:${p}`, `tcp:${p}`]); - } - }, - - expoArgs(device, metroPort, release) { - return [ - 'expo', - 'run:android', - '--device', - device, - '--port', - String(metroPort), - ...(release ? ['--variant', 'release'] : []), - ]; - }, -}; - -const iosDriver: DeviceDriver = { - platform: 'ios', - - async ensureBooted(requested) { - const {stdout} = await execFile('xcrun', [ - 'simctl', - 'list', - 'devices', - 'booted', - '-j', - ]); - const {devices} = JSON.parse(stdout) as { - devices: Record; - }; - const booted = Object.values(devices).flat(); - if (booted.length > 0) { - const match = requested - ? booted.find(d => d.name === requested) - : booted[0]; - if (match) { - return match.name; - } - } - const name = requested ?? 'iPhone 17 Pro'; - await execFile('xcrun', ['simctl', 'boot', name]); - await execFile('open', ['-a', 'Simulator']); - return name; - }, - - // The iOS simulator shares the host's network stack, so localhost already - // reaches us. Nothing to do. - forwardPorts: () => Promise.resolve(), - - expoArgs(device, metroPort, release) { - return [ - 'expo', - 'run:ios', - '--device', - device, - '--port', - String(metroPort), - ...(release ? ['--configuration', 'Release'] : []), - ]; - }, - - async nudge(appDir, metroPort) { - const appJson = JSON.parse( - await fs.readFile(path.join(appDir, 'app.json'), 'utf-8'), - ) as {expo: {ios: {bundleIdentifier: string}}}; - const id = appJson.expo.ios.bundleIdentifier; - // Foregrounding the app first means the deep link is delivered to the app - // that is already frontmost, which iOS does not gate behind a prompt. - await execFile('xcrun', ['simctl', 'launch', 'booted', id]); - await execFile('xcrun', [ - 'simctl', - 'openurl', - 'booted', - `${id}://expo-development-client/?url=${encodeURIComponent( - `http://localhost:${metroPort}`, - )}`, - ]); - }, -}; - -function driverFor(p: Platform): DeviceDriver { - return p === 'android' ? androidDriver : iosDriver; -} - -// --------------------------------------------------------------------------- -// Control server -// --------------------------------------------------------------------------- - -type QueueItem = {backend: Backend; name: string; group: string}; - -type Outcome = - | {item: QueueItem; result: BenchmarkResult} - | {item: QueueItem; error: string}; - -type ControlServer = { - readonly port: number; - /** Resolves when every queued benchmark has reported, rejects on error. */ - readonly done: Promise; - /** Resolves the first time the app talks to us. */ - readonly firstContact: Promise; - close(): Promise; -}; - -function startControlServer( - queue: QueueItem[], - port: number, - idleTimeoutMs: number, - onOutcome: (outcome: Outcome, index: number, total: number) => void, -): Promise { - const outcomes: Outcome[] = []; - let index = 0; - let settle!: (o: Outcome[]) => void; - let fail!: (e: unknown) => void; - const done = new Promise((res, rej) => { - settle = res; - fail = rej; - }); - - let noteContact!: () => void; - const firstContact = new Promise(res => { - noteContact = res; - }); - - let lastActivity = Date.now(); - const watchdog = setInterval(() => { - if (Date.now() - lastActivity > idleTimeoutMs) { - clearInterval(watchdog); - fail( - new Error( - `The app went quiet for ${Math.round(idleTimeoutMs / 1000)}s ` + - `(${index}/${queue.length} benchmarks done). ` + - `Re-run with --verbose to see the Expo/Metro output.`, - ), - ); - } - }, 5_000); - - const json = (res: http.ServerResponse, body: unknown) => { - const s = JSON.stringify(body); - res.writeHead(200, { - 'content-type': 'application/json', - 'content-length': Buffer.byteLength(s), - }); - res.end(s); - }; - - const server = http.createServer((req, res) => { - lastActivity = Date.now(); - noteContact(); - const url = new URL(req.url ?? '/', `http://localhost:${port}`); +await runRnBench({ + rootDir, + cliName: 'perf:rn', + globalName: '__replicachePerf', - switch (url.pathname) { - case '/ping': - json(res, {ok: true}); - return; - - case '/next': { - const item = queue[index]; - if (!item) { - json(res, {done: true}); - clearInterval(watchdog); - settle(outcomes); - return; - } - json(res, { - done: false, - ...item, - // 9.7 MB: served rather than bundled into the app. - tmcwUrl: `http://localhost:${port}/tmcw.json`, - index, - total: queue.length, - }); - return; - } - - case '/result': { - const chunks: Buffer[] = []; - req.on('data', c => chunks.push(c as Buffer)); - req.on('end', () => { - lastActivity = Date.now(); - const item = queue[index]; - if (!item) { - // A late or duplicate post, e.g. the app reloaded after we had - // already recorded its result. Nothing left to attribute it to. - res.writeHead(409, {'content-type': 'application/json'}); - res.end(JSON.stringify({error: 'no benchmark in flight'})); - return; - } - let body: {result: BenchmarkResult} | {error: string}; - try { - body = JSON.parse(Buffer.concat(chunks).toString()); - } catch (e) { - // Record it as a failed benchmark rather than taking the runner - // down with it, so the rest of the queue still runs. - body = {error: `malformed /result body: ${String(e)}`}; - } - const outcome: Outcome = - 'result' in body - ? {item, result: body.result} - : {item, error: body.error}; - outcomes.push(outcome); - onOutcome(outcome, index, queue.length); - index++; - json(res, {ok: true}); - }); - return; - } - - case '/tmcw.json': { - const file = path.join(rootDir, 'resources', 'tmcw.json'); - res.writeHead(200, {'content-type': 'application/json'}); - createReadStream(file).pipe(res); - return; - } - - default: - res.writeHead(404); - res.end(); - } - }); - - return new Promise((resolve, reject) => { - server.on('error', reject); - server.listen(port, () => - resolve({ - port, - done, - firstContact, - close: () => - new Promise(res => { - clearInterval(watchdog); - server.close(() => res()); - }), - }), - ); - }); -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- + // Must match the harness in rn.ts. + listBenchmarks: () => + [...replicacheBenchmarks(), ...mapLoopBenchmarks()].map(b => ({ + name: b.name, + group: b.group, + })), -const optionDefinitions = [ - { - name: 'platform', - type: platform, - defaultValue: 'android', - description: `Device platform: ${allPlatforms.join(', ')}, or all`, - }, - { - name: 'backend', - type: backend, - multiple: true, - defaultValue: ['expo'], - description: `Key/value backends: ${allBackends.join(', ')}, or all`, - }, - { - name: 'device', - type: String, - description: - 'AVD name (android, default medium_phone) or simulator name (ios, default the booted one)', - }, - { - name: 'run', - type: RegExp, - description: 'Run only those benchmarks matching the regular expression.', - }, - { - name: 'format', - alias: 'f', - type: format, - defaultValue: 'benchmarkJS', - description: - 'Format for text output, either benchmarkJS (default), json, replicache or bmf (Bencher Metrics Format)', - }, - { - name: 'list', - alias: 'l', - type: Boolean, - description: 'List available benchmarks', + variants: { + flag: 'backend', + values: ['expo', 'op', 'mem'], + describe: 'Key/value backends', }, - { - name: 'app', - type: String, - defaultValue: DEFAULT_APP_DIR, - description: - 'Path to the Expo host app (default: the rn/ directory beside this package)', - }, - { - name: 'port', - type: Number, - defaultValue: DEFAULT_CONTROL_PORT, - description: `Control server port (default ${DEFAULT_CONTROL_PORT})`, - }, - { - name: 'metro-port', - type: Number, - defaultValue: DEFAULT_METRO_PORT, - description: `Metro bundler port (default ${DEFAULT_METRO_PORT})`, - }, - { - name: 'idle-timeout', - type: Number, - defaultValue: 900, - description: - 'Seconds without a request from the app before giving up (default 900; the first native build is slow)', - }, - { - name: 'release', - type: Boolean, - defaultValue: false, - description: - 'Build and run the release variant (__DEV__ off, JS minified and precompiled to bytecode, no debugger). Note the app then has no DevSettings.reload, so benchmarks share one JS context, and --profile is unavailable because release builds ship no inspector.', - }, - { - name: 'profile', - type: String, - description: - 'Capture a Hermes CPU profile of one benchmark and write the Chrome trace to this path. Drives the app directly over CDP (no control server), rebuilds the bundle unminified for readable frames, and reports self time windowed to the region the bencher actually times. Requires a single platform/backend/benchmark.', - }, - { - name: 'verbose', - alias: 'v', - type: Boolean, - defaultValue: false, - description: 'Stream the Expo/Metro output', - }, - { - name: 'help', - alias: 'h', - type: Boolean, - description: 'Show this help message', - }, -]; - -type Options = { - 'platform': Platform | 'all'; - 'backend': (Backend | 'all')[]; - 'device'?: string; - 'run'?: RegExp; - 'format': Format; - 'list'?: boolean; - 'app': string; - 'port': number; - 'metro-port': number; - 'idle-timeout': number; - 'release'?: boolean; - 'profile'?: string; - 'verbose': boolean; - 'help'?: boolean; -}; - -function logLine(s: string, options: Options) { - if (options.format !== 'json' && options.format !== 'bmf') { - process.stdout.write(s + '\n'); - } -} - -/** - * The app is a plain Expo project outside the workspace; it consumes the bundle - * as an ordinary local file, so Metro never sees monorepo TypeScript. The - * sourcemap comes along (renamed) so on-device stack traces stay readable. - */ -async function appIdFor(appDir: string, p: Platform): Promise { - const {expo} = JSON.parse( - await fs.readFile(path.join(appDir, 'app.json'), 'utf-8'), - ) as {expo: {ios: {bundleIdentifier: string}; android: {package: string}}}; - return p === 'ios' ? expo.ios.bundleIdentifier : expo.android.package; -} - -async function installBundle(appDir: string): Promise { - const js = await fs.readFile(path.join(rootDir, 'out', 'rn.js'), 'utf-8'); - await fs.writeFile( - path.join(appDir, 'benchmarks.js'), - js.replace( - '//# sourceMappingURL=rn.js.map', - '//# sourceMappingURL=benchmarks.js.map', - ), - ); - await fs.copyFile( - path.join(rootDir, 'out', 'rn.js.map'), - path.join(appDir, 'benchmarks.js.map'), - ); - await fs.copyFile( - path.join(rootDir, 'out', 'rn.d.ts'), - path.join(appDir, 'benchmarks.d.ts'), - ); -} - -async function nudgeUntilContact( - driver: DeviceDriver, - appDir: string, - metroPort: number, - firstContact: Promise, -): Promise { - let contacted = false; - void firstContact.then(() => { - contacted = true; - }); - while (!contacted) { - await new Promise(r => setTimeout(r, 15_000)); - if (contacted) { - return; - } - // Expected to fail while the native build is still running. - await driver.nudge?.(appDir, metroPort).catch(() => {}); - } -} - -/** Builds, installs and launches the app, with Metro, in its own process group. */ -function spawnExpo( - driver: DeviceDriver, - device: string, - metroPort: number, - options: Options, -): ChildProcess { - return spawn( - 'npx', - driver.expoArgs(device, metroPort, options.release ?? false), - { - cwd: options.app, - env: { - ...process.env, - CI: '1', - EXPO_PUBLIC_PERF_PORT: String(options.port), - // CocoaPods refuses to run without a UTF-8 locale, and a non-login shell - // often has none. - LANG: process.env.LANG ?? 'en_US.UTF-8', - }, - stdio: options.verbose ? 'inherit' : 'ignore', - // Its own process group, so killing it takes Metro and gradle down too - // rather than leaving Metro holding the port. - detached: true, - }, - ); -} - -function killGroup(child: ChildProcess | undefined): void { - if (child?.pid === undefined) { - return; - } - try { - process.kill(-child.pid, 'SIGTERM'); - } catch { - // Already gone. - } -} - -/** - * Profiling mode. Unlike a normal run this does not use the control server at - * all: the app sits idle in manual mode and we drive one benchmark straight - * through `Runtime.evaluate`, so nothing but the benchmark runs inside the - * traced window. Requires the Expo app to publish `globalThis.__replicachePerf` - * (see its App.tsx). - */ -async function profileOnDevice( - p: Platform, - item: QueueItem, - device: string, - metroPort: number, - options: Options, -): Promise { - const session = await connectSession( - metroPort, - await appIdFor(options.app, p), - p === 'ios' ? device : 'sdk_gphone', - ); - try { - await session.waitForHarness(); - await session.evaluate( - `__replicachePerf.configure({backend: ${JSON.stringify( - item.backend, - )}, tmcwUrl: ${JSON.stringify( - `http://localhost:${options.port}/tmcw.json`, - )}})`, - ); - - await session.startTracing(); - const result = await runBenchmarkInApp( - session, - item.name, - item.group, - options['idle-timeout'] * 1000, - ); - const events = await session.stopTracing(); - - logLine(`${item.backend} ${result}`, options); - - await fs.writeFile(options.profile!, JSON.stringify(events)); - const {rows, inWindowUs, excludedUs, windows} = selfTimeByFrame( - events, - item.name, - ); - logLine( - `\nHermes CPU profile (${p}): ${windows} measured windows, ` + - `${(inWindowUs / 1000).toFixed(0)} ms in-window, ` + - `${(excludedUs / 1000).toFixed(0)} ms of setup/teardown excluded`, - options, - ); - for (const r of rows.slice(0, 25)) { - logLine( - `${r.pct.toFixed(1).padStart(6)}% ${(r.us / 1000) - .toFixed(1) - .padStart(8)} ms ${r.frame}`, - options, - ); - } - logLine(`\nfull trace written to ${options.profile}`, options); - } finally { - session.close(); - } -} - -async function runPlatform( - p: Platform, - queue: QueueItem[], - options: Options, -): Promise { - const driver = driverFor(p); - const metroPort = options['metro-port']; - const device = await driver.ensureBooted(options.device); - logLine(`Running ${queue.length} benchmarks on ${p} (${device})...`, options); + nextExtras: port => ({tmcwUrl: `http://localhost:${port}/tmcw.json`}), - await driver.forwardPorts([options.port, metroPort]); - - if (options.profile) { - // An empty queue: the app's first /next gets `done`, so it settles into - // idle instead of running anything, and we still serve /tmcw.json for the - // benchmarks that need the fixture. - const idle = await startControlServer( - [], - options.port, - options['idle-timeout'] * 1000, - () => {}, - ); - let expo: ChildProcess | undefined; - try { - expo = spawnExpo(driver, device, metroPort, options); - await profileOnDevice(p, queue[0], device, metroPort, options); - return []; - } finally { - killGroup(expo); - await idle.close(); - } - } - - const server = await startControlServer( - queue, - options.port, - options['idle-timeout'] * 1000, - (outcome, index, total) => { - if ('error' in outcome) { - return; - } - const label = `[${index + 1}/${total}] ${outcome.item.backend}`; - switch (options.format) { - case 'replicache': - logLine(`${label} ${formatAsReplicache(outcome.result)}`, options); - break; - case 'benchmarkJS': - logLine(`${label} ${formatAsBenchmarkJS(outcome.result)}`, options); - break; - default: - if (options.verbose) { - logLine(`${label} ${outcome.item.name}`, options); - } - } + extraRoutes: { + '/tmcw.json': (_req, res) => { + res.writeHead(200, {'content-type': 'application/json'}); + createReadStream(path.join(rootDir, 'resources', 'tmcw.json')).pipe(res); }, - ); - - let expo: ChildProcess | undefined; - try { - expo = spawnExpo(driver, device, metroPort, options); - const expoExited = new Promise((_, reject) => { - expo?.on('exit', code => { - // Metro staying up is normal; only a non-zero exit is a failure, and - // only before the run finished. - if (code !== 0 && code !== null) { - reject( - new Error( - `expo run:${p} exited with code ${code}. Re-run with --verbose.`, - ), - ); - } - }); - expo?.on('error', reject); - }); - - if (driver.nudge) { - void nudgeUntilContact( - driver, - options.app, - metroPort, - server.firstContact, - ); - } - - return await Promise.race([server.done, expoExited]); - } finally { - killGroup(expo); - await server.close(); - } -} - -async function main() { - const options = commandLineArgs(optionDefinitions) as Options; - - if (options.help) { - // oxlint-disable-next-line no-console - console.log( - commandLineUsage([ - {content: 'Usage: perf:rn [options...]'}, - {optionList: optionDefinitions}, - ]), - ); - process.exit(); - } - - // Must match the harness in rn.ts. - let names = [...replicacheBenchmarks(), ...mapLoopBenchmarks()].map(b => ({ - name: b.name, - group: b.group, - })); - if (options.run !== undefined) { - names = names.filter(({name}) => options.run!.test(name)); - } - - if (options.list) { - // oxlint-disable-next-line no-console - console.log( - 'Available benchmarks (group / name):\n' + - names - .map(({name, group}) => `${group} / ${name}`) - .sort() - .join('\n'), - ); - return; - } - - if (names.length === 0) { - // oxlint-disable-next-line no-console - console.error('No benchmarks matched --run.'); - process.exit(1); - } - - const backends: Backend[] = options.backend.includes('all') - ? [...allBackends] - : (options.backend as Backend[]); - const platforms: Platform[] = - options.platform === 'all' ? [...allPlatforms] : [options.platform]; - - if (options.profile) { - // Hermes frames are just `Ys`/`ie`/`dr` against the shipped bundle, so - // rebuild without minification before installing it. - logLine( - 'Rebuilding the RN bundle unminified for readable frames...', - options, - ); - await new Promise((resolve, reject) => { - const build = spawn('node', [path.join(rootDir, 'tool', 'build.ts')], { - cwd: rootDir, - env: {...process.env, PERF_RN_NO_MINIFY: '1'}, - stdio: options.verbose ? 'inherit' : 'ignore', - }); - build.on('exit', code => - code === 0 ? resolve() : reject(new Error(`build exited ${code}`)), - ); - build.on('error', reject); - }); - } - - await installBundle(options.app); - - const queue: QueueItem[] = backends.flatMap(backend => - names.map(({name, group}) => ({backend, name, group})), - ); - - if (options.release && options.port !== DEFAULT_CONTROL_PORT) { - // Release bundles are built by gradle/Xcode, which do not inherit this - // process's environment, so EXPO_PUBLIC_PERF_PORT never reaches the app and - // it falls back to the default port. - // oxlint-disable-next-line no-console - console.error( - `--release cannot use a custom --port (the app is stuck on ${DEFAULT_CONTROL_PORT}).`, - ); - process.exit(1); - } - - if (options.profile && options.release) { - // oxlint-disable-next-line no-console - console.error( - '--profile needs the debug variant: release builds ship no CDP inspector.', - ); - process.exit(1); - } - - if (options.profile && (queue.length !== 1 || platforms.length !== 1)) { - // oxlint-disable-next-line no-console - console.error( - `--profile needs exactly one benchmark on one platform with one backend; ` + - `got ${queue.length} benchmark(s) on ${platforms.length} platform(s). ` + - `Narrow it with --run, --backend and --platform.`, - ); - process.exit(1); - } - - const jsonEntries: unknown[] = []; - let bmf: BencherMetricsFormat = {}; - let failed = false; - let first = true; - - for (const p of platforms) { - if (!first) { - logLine('', options); - } - first = false; - - const outcomes = await runPlatform(p, [...queue], options); - - for (const outcome of outcomes) { - if ('error' in outcome) { - failed = true; - process.stderr.write( - `${p} / ${outcome.item.backend} / ${outcome.item.name}: ${outcome.error}\n`, - ); - continue; - } - // Keep platform and backend in the machine-readable names so a matrix run - // does not collapse into indistinguishable keys. - const result = { - ...outcome.result, - name: `${p} ${outcome.item.backend} ${outcome.result.name}`, - }; - switch (options.format) { - case 'json': - jsonEntries.push(...createGithubActionBenchmarkJSONEntries(result)); - break; - case 'bmf': - bmf = {...bmf, ...toBencherMetricFormat(result)}; - break; - } - } - } - - if (options.format === 'json') { - process.stdout.write(JSON.stringify(jsonEntries, null, 2) + '\n'); - } else if (options.format === 'bmf') { - process.stdout.write(JSON.stringify(bmf, null, 2) + '\n'); - } else { - logLine('Done!', options); - } - - if (failed) { - process.exit(1); - } -} + }, -main().catch(err => { - // oxlint-disable-next-line no-console - console.error(err); - process.exit(1); + configureExpr: (variant, port) => + `__replicachePerf.configure({backend: ${JSON.stringify( + variant, + )}, tmcwUrl: ${JSON.stringify(`http://localhost:${port}/tmcw.json`)}})`, }); diff --git a/packages/replicache-perf/src/perf.ts b/packages/replicache-perf/src/perf.ts index baeb05af64..e9ab1d7e8f 100644 --- a/packages/replicache-perf/src/perf.ts +++ b/packages/replicache-perf/src/perf.ts @@ -1,10 +1,10 @@ +import {makeHarness} from '../../../tools/rn-bench/src/harness.ts'; import {benchmarks as compareBenchmarks} from './benchmarks/compare-utf8.ts'; import {benchmarks as hashBenchmarks} from './benchmarks/hash.ts'; import {benchmarks as idbBenchmarks} from './benchmarks/idb.ts'; import {benchmarks as mapLoopBenchmarks} from './benchmarks/map-loop.ts'; import {benchmarks as replicacheBenchmarks} from './benchmarks/replicache.ts'; import {benchmarks as storageBenchmarks} from './benchmarks/storage.ts'; -import {makeHarness} from './harness.ts'; const harness = makeHarness([ ...replicacheBenchmarks(), diff --git a/packages/replicache-perf/src/rn.ts b/packages/replicache-perf/src/rn.ts index 0e6741d7c6..ac4b3d8717 100644 --- a/packages/replicache-perf/src/rn.ts +++ b/packages/replicache-perf/src/rn.ts @@ -1,3 +1,4 @@ +import {makeHarness} from '../../../tools/rn-bench/src/harness.ts'; /** * React Native entry point for the perf harness. * @@ -13,11 +14,13 @@ import type {StoreProvider} from '../../replicache/src/kv/store.ts'; import {benchmarks as mapLoopBenchmarks} from './benchmarks/map-loop.ts'; import {benchmarks as replicacheBenchmarks} from './benchmarks/replicache.ts'; import {setTmcwUrl} from './data.ts'; -import {makeHarness} from './harness.ts'; -export type {BenchmarkResult} from './benchmark.ts'; +export type {BenchmarkResult} from '../../../tools/rn-bench/src/benchmark.ts'; export {setTmcwUrl}; -export {formatAsBenchmarkJS, formatAsReplicache} from './format.ts'; +export { + formatAsBenchmarkJS, + formatAsReplicache, +} from '../../../tools/rn-bench/src/format.ts'; // `replicache` plus the pure-JS `map-loop` group, which is useful for // separating JS-engine cost from storage cost on a device. The rest are out: diff --git a/packages/replicache-perf/src/runner.ts b/packages/replicache-perf/src/runner.ts index 75fd050bc8..d67338b246 100644 --- a/packages/replicache-perf/src/runner.ts +++ b/packages/replicache-perf/src/runner.ts @@ -8,14 +8,17 @@ import commandLineUsage from 'command-line-usage'; import getPort from 'get-port'; import * as playwright from 'playwright'; import {createServer} from 'vite'; -import {assert} from '../../shared/src/asserts.ts'; -import {makeDefine} from '../../shared/src/build.ts'; import { type BencherMetricsFormat, toBencherMetricFormat, -} from './bencher-metric-format.ts'; -import {formatAsBenchmarkJS, formatAsReplicache} from './format.ts'; -import {createGithubActionBenchmarkJSONEntries} from './github-action-benchmark.ts'; +} from '../../../tools/rn-bench/src/bencher-metric-format.ts'; +import { + formatAsBenchmarkJS, + formatAsReplicache, +} from '../../../tools/rn-bench/src/format.ts'; +import {createGithubActionBenchmarkJSONEntries} from '../../../tools/rn-bench/src/github-action-benchmark.ts'; +import {assert} from '../../shared/src/asserts.ts'; +import {makeDefine} from '../../shared/src/build.ts'; type Format = 'benchmarkJS' | 'json' | 'replicache' | 'bmf'; diff --git a/packages/zql-benchmarks/package.json b/packages/zql-benchmarks/package.json index e376e0af27..e9bf1fd43f 100644 --- a/packages/zql-benchmarks/package.json +++ b/packages/zql-benchmarks/package.json @@ -21,14 +21,18 @@ "test-types": "vitest run --typecheck.only --no-browser.enabled", "test-types:watch": "vitest watch --typecheck.only --no-browser.enabled", "fmt": "oxfmt .", - "check-fmt": "oxfmt --check ." + "check-fmt": "oxfmt --check .", + "build:rn": "node tool/build-rn.ts", + "perf:rn": "pnpm run build:rn && node src/perf-rn.ts" }, "devDependencies": { "@types/tmp": "^0.2.6", + "esbuild": "0.27.7", "js-xxhash": "^4.0.0", "mitata": "^1.0.34", "otel": "workspace:*", "oxfmt": "^0.65.0", + "rn-bench": "workspace:*", "shared": "workspace:*", "typescript": "~7.0.2", "vitest": "^4.1.6", diff --git a/packages/zql-benchmarks/rn/.gitignore b/packages/zql-benchmarks/rn/.gitignore new file mode 100644 index 0000000000..240a327cda --- /dev/null +++ b/packages/zql-benchmarks/rn/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +.expo/ +android/ +ios/ +# Copied in by `pnpm --filter zql-benchmarks run perf:rn`; generated from +# src/rn.ts by tool/build-rn.ts. +benchmarks.js +benchmarks.js.map +benchmarks.d.ts diff --git a/packages/zql-benchmarks/rn/App.tsx b/packages/zql-benchmarks/rn/App.tsx new file mode 100644 index 0000000000..5325558446 --- /dev/null +++ b/packages/zql-benchmarks/rn/App.tsx @@ -0,0 +1,128 @@ +import {useEffect, useRef, useState} from 'react'; +import {DevSettings, ScrollView, StyleSheet, Text, View} from 'react-native'; +import { + configure, + formatAsReplicache, + runBenchmarkByNameAndGroup, + type BenchmarkResult, +} from './benchmarks.js'; + +/** + * Set by the runner (perf-rn.ts) when it spawns Metro. A release bundle is + * built by gradle/Xcode, which do not inherit the runner's environment, so it + * is absent there and we fall back to the runner's default port. + * + * `localhost` reaches the host directly on the iOS simulator, and on Android + * via the runner's `adb reverse`. + */ +const base = `http://localhost:${process.env.EXPO_PUBLIC_PERF_PORT ?? '9099'}`; + +/** + * Exposed so a CDP client can drive one benchmark via Runtime.evaluate and + * bracket a Hermes trace around exactly that call. See perf-rn.ts --profile. + */ +(globalThis as Record).__zqlPerf = { + configure, + formatAsReplicache, + runBenchmarkByNameAndGroup, +}; + +type Next = + | {done: true} + | { + done: false; + name: string; + group: string; + index: number; + total: number; + }; + +export default function App() { + const [status, setStatus] = useState('connecting…'); + const [lines, setLines] = useState([]); + const started = useRef(false); + + useEffect(() => { + if (started.current) { + return; + } + started.current = true; + + const log = (line: string) => { + // Also to the console so the Metro terminal is readable. + // oxlint-disable-next-line no-console + console.log(line); + setLines(prev => [...prev, line]); + }; + + void (async () => { + // Release builds have no dev-settings reload, so there we stay in one JS + // context and loop. Less isolation between benchmarks than a reload + // gives, but each benchmark rebuilds its own sources anyway. + const canReload = __DEV__ && typeof DevSettings?.reload === 'function'; + + for (;;) { + let next: Next; + try { + next = (await fetch(`${base}/next`).then(r => r.json())) as Next; + } catch { + setStatus(`no control server on ${base}`); + return; + } + if (next.done) { + setStatus('done'); + return; + } + + setStatus( + `[${next.index + 1}/${next.total}] ${next.group} · ${next.name}`, + ); + + let body: {result: BenchmarkResult} | {error: string}; + try { + const out = await runBenchmarkByNameAndGroup(next.name, next.group); + if (out && out[0] === 'result') { + body = {result: out[1]}; + log(formatAsReplicache(out[1])); + } else { + body = {error: String(out?.[1] ?? 'no result')}; + log(`${next.name}: ${body.error}`); + } + } catch (e) { + body = {error: (e as Error)?.stack ?? String(e)}; + log(`${next.name} THREW: ${body.error}`); + } + + await fetch(`${base}/result`, { + method: 'POST', + headers: {'content-type': 'application/json'}, + body: JSON.stringify(body), + }); + + if (canReload) { + DevSettings.reload(); + return; + } + } + })(); + }, []); + + return ( + + {status} + + {lines.map((l, i) => ( + + {l} + + ))} + + + ); +} + +const styles = StyleSheet.create({ + container: {flex: 1, paddingTop: 64, paddingHorizontal: 16}, + status: {fontSize: 14, marginBottom: 12}, + line: {fontFamily: 'Courier', fontSize: 11, marginVertical: 2}, +}); diff --git a/packages/zql-benchmarks/rn/README.md b/packages/zql-benchmarks/rn/README.md new file mode 100644 index 0000000000..f834c59321 --- /dev/null +++ b/packages/zql-benchmarks/rn/README.md @@ -0,0 +1,38 @@ +# zql-benchmarks-rn + +Host app for running the ZQL benchmarks on a React Native device. The runner, +device drivers and Hermes profiling live in `tools/rn-bench`; this is just the +app they drive. + +Like `packages/replicache-perf/rn`, it is **deliberately not a pnpm workspace +package** — `pnpm-workspace.yaml` globs a single path segment, so nesting it +here keeps it in git while leaving it out of the workspace. It needs a one-time +`pnpm install` in this directory, and the empty `pnpm-workspace.yaml` stops +`pnpm install` walking up and reinstalling the monorepo. + +Two root-level checks walk the tree by path rather than by workspace membership +and need this app excluded: `oxlint --type-aware` (via `oxlint.base.ts`) and +`syncpack` (via a version group in `.syncpackrc`). + +## Running + +From the monorepo root: + +```bash +pnpm --filter zql-benchmarks run perf:rn -- --platform android +pnpm --filter zql-benchmarks run perf:rn -- --run 'hydrate' --profile /tmp/zql.json +``` + +## How the benchmarks get here + +`src/rn.ts` imports the `.bench.ts` files that run without Node. They call +`bench`/`describe` from `shared/src/bench.ts`, which wraps mitata and Vitest; +the RN build rewrites that import to `tools/rn-bench/src/mitata-shim.ts`, so the +same files feed both the Vitest suite and the device. There is no second copy of +the benchmarks to keep in step, and the Node side reads its benchmark list from +the built bundle for the same reason. + +Note the reported unit: each figure is **N iterations** (a benchmark's +`max_samples`, or 100), not one operation, because mitata measures per-iteration +while this harness times a whole rep. Comparable across runs, not directly +comparable to mitata's per-op numbers. diff --git a/packages/zql-benchmarks/rn/app.json b/packages/zql-benchmarks/rn/app.json new file mode 100644 index 0000000000..7961c28561 --- /dev/null +++ b/packages/zql-benchmarks/rn/app.json @@ -0,0 +1,23 @@ +{ + "expo": { + "name": "zql-perf", + "slug": "zql-benchmarks-rn", + "version": "1.0.0", + "ios": { + "bundleIdentifier": "dev.rocicorp.zqlperfrn" + }, + "android": { + "package": "dev.rocicorp.zqlperfrn" + }, + "plugins": [ + [ + "expo-build-properties", + { + "android": { + "usesCleartextTraffic": true + } + } + ] + ] + } +} diff --git a/packages/zql-benchmarks/rn/index.ts b/packages/zql-benchmarks/rn/index.ts new file mode 100644 index 0000000000..63fb5a4b50 --- /dev/null +++ b/packages/zql-benchmarks/rn/index.ts @@ -0,0 +1,8 @@ +import {registerRootComponent} from 'expo'; + +import App from './App'; + +// registerRootComponent calls AppRegistry.registerComponent('main', () => App); +// It also ensures that whether you load the app in Expo Go or in a native build, +// the environment is set up appropriately +registerRootComponent(App); diff --git a/packages/zql-benchmarks/rn/package.json b/packages/zql-benchmarks/rn/package.json new file mode 100644 index 0000000000..6ec3007fa0 --- /dev/null +++ b/packages/zql-benchmarks/rn/package.json @@ -0,0 +1,21 @@ +{ + "name": "zql-benchmarks-rn", + "version": "1.0.0", + "private": true, + "main": "index.ts", + "scripts": { + "start": "expo start", + "android": "expo run:android", + "ios": "expo run:ios" + }, + "dependencies": { + "expo": "~57.0.0", + "expo-build-properties": "~57.0.17", + "react": "19.2.3", + "react-native": "0.86.0" + }, + "devDependencies": { + "@types/react": "~19.2.2", + "typescript": "~6.0.3" + } +} diff --git a/packages/zql-benchmarks/rn/pnpm-lock.yaml b/packages/zql-benchmarks/rn/pnpm-lock.yaml new file mode 100644 index 0000000000..2bc174e158 --- /dev/null +++ b/packages/zql-benchmarks/rn/pnpm-lock.yaml @@ -0,0 +1,4651 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + expo: + specifier: ~57.0.0 + version: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-build-properties: + specifier: ~57.0.17 + version: 57.0.17(expo@57.0.20) + react: + specifier: 19.2.3 + version: 19.2.3 + react-native: + specifier: 0.86.0 + version: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + devDependencies: + '@types/react': + specifier: ~19.2.2 + version: 19.2.18 + typescript: + specifier: ~6.0.3 + version: 6.0.3 + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.29.7': + resolution: {integrity: sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.29.7': + resolution: {integrity: sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.29.7': + resolution: {integrity: sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-proposal-decorators@7.29.7': + resolution: {integrity: sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-export-default-from@7.29.7': + resolution: {integrity: sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.29.7': + resolution: {integrity: sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-dynamic-import@7.8.3': + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-export-default-from@7.29.7': + resolution: {integrity: sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-flow@7.29.7': + resolution: {integrity: sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.29.7': + resolution: {integrity: sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.29.7': + resolution: {integrity: sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.29.7': + resolution: {integrity: sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.29.7': + resolution: {integrity: sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.29.7': + resolution: {integrity: sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.29.7': + resolution: {integrity: sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.29.7': + resolution: {integrity: sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.29.7': + resolution: {integrity: sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-flow-strip-types@7.29.7': + resolution: {integrity: sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.29.7': + resolution: {integrity: sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.29.7': + resolution: {integrity: sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.29.7': + resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7': + resolution: {integrity: sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7': + resolution: {integrity: sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.29.7': + resolution: {integrity: sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.29.7': + resolution: {integrity: sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.29.7': + resolution: {integrity: sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.29.7': + resolution: {integrity: sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.29.7': + resolution: {integrity: sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.29.7': + resolution: {integrity: sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.29.7': + resolution: {integrity: sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.29.7': + resolution: {integrity: sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.29.7': + resolution: {integrity: sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.29.7': + resolution: {integrity: sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-runtime@7.29.7': + resolution: {integrity: sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.29.7': + resolution: {integrity: sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.29.7': + resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@expo/cli@57.0.22': + resolution: {integrity: sha512-MdToR0ZZ1yjfkqbP1jcA7viS9duMPGq/C+qQ/21dtq+en583Ghp+O11BP/wujHa9ZGRtXimzuKPZDpPresVE0g==} + hasBin: true + peerDependencies: + expo: '*' + expo-router: '*' + react-native: '*' + peerDependenciesMeta: + expo-router: + optional: true + react-native: + optional: true + + '@expo/code-signing-certificates@0.0.6': + resolution: {integrity: sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==} + + '@expo/config-plugins@57.0.9': + resolution: {integrity: sha512-hHgfL1avkCdEvDSw7IwlKwRYYNgcxzbNNMIk6W6lTkJpY0MajinAfeJUS0J+wPCsjUfGbVqOJM+XhPaO5ulUxg==} + + '@expo/config-types@57.0.2': + resolution: {integrity: sha512-ewW08OonrcRIsRKIlFvvcmmafE5zemb1ocu3HkNwtVPyRtj2w42pZCAkMIROYpcVBaPnc3mDT9UZDzwXWC3i6g==} + + '@expo/config@57.0.9': + resolution: {integrity: sha512-dmzlKraIFxa7wLwV6K7WzI8jp6QZpW6Mc5mGjLimJUFjzh4uQdYaT3m3plEutM5yxBoBEwqzks7l+I/ljCbxAQ==} + + '@expo/devcert@1.2.1': + resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==} + + '@expo/devtools@57.0.1': + resolution: {integrity: sha512-GyUf+wFNkbttaX0jR7MZa9bm77U0IrLg6d2AjpxdyoXw/w4abHoXG0oFufwLMgP9zLTd5+Ct4X/ffNUTnlzZgg==} + peerDependencies: + react: '*' + react-native: '*' + peerDependenciesMeta: + react: + optional: true + react-native: + optional: true + + '@expo/dom-webview@57.0.1': + resolution: {integrity: sha512-lAKsME4SAq+8sf56oN0DX5TBYyruupoRxbWbD2xf9RnKY8y6x8eb9LCE5pxSN0qyWdqnp+0wmyWzDkKboThKAw==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + '@expo/env@2.4.3': + resolution: {integrity: sha512-M1NXeZCA1mkMkYOyIe7PlyRX0/jqFtMoJgyblnlq/vpCRfmueFT7RnGSQG8uEFDF5WHOFGijAQ3fogPh3/n5Ng==} + engines: {node: '>=20.12.0'} + + '@expo/expo-modules-macros-plugin@0.6.1': + resolution: {integrity: sha512-cpsLZE4rqkc1Y3eZTkxB98jrqY1YXgetmtxFt8q89jBRmk3quRuk1BZo+VcnCSObZardjg99r1k5xijEMONFGA==} + + '@expo/fingerprint@0.20.12': + resolution: {integrity: sha512-FIR5fkZYeFaLSowmjgyB6RPKl8AXeE8HuCBHHvyxK8UhOjpPfCAmzT4E7v2yub4qqDafKnfcOFCYxvpUEu+01w==} + hasBin: true + + '@expo/image-utils@0.11.5': + resolution: {integrity: sha512-KPQBTpmpAfy/Vu9y4wPW808/qtZxjYmyJg8cm2QCPAupp+qEWA3b5zmk0ulOwQ9OgeHxuCPgUqWgkwHFo7UsrQ==} + + '@expo/inline-modules@0.1.7': + resolution: {integrity: sha512-Bz/khd1gIJqDkje7t5ejD5e9jFbm4xEJzWSwRKadRo6gruepbw1xJ3Eb+e58OS8fY1ZNQYjzZbspnuph67sN0g==} + + '@expo/json-file@11.0.1': + resolution: {integrity: sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ==} + + '@expo/local-build-cache-provider@57.0.8': + resolution: {integrity: sha512-SEdE0pAQrr90bRh3MNR0ZuwoIBw390cYdFgbn7Vk0Mtm9EHaBfP7kYj+2hXnXZJgOEm3/JMtfoD3rV7rWA9FGg==} + + '@expo/log-box@57.0.4': + resolution: {integrity: sha512-IxwS9s1L2muj8mj8AQSuiy7u8OFJdc02NRFo2me/Tj6DiaeG5SREqmpBE4rQpR2cadqSg5jl8Qab8Cjie616dg==} + peerDependencies: + '@expo/dom-webview': ^57.0.1 + expo: '*' + react: '*' + react-native: '*' + + '@expo/metro-config@57.0.12': + resolution: {integrity: sha512-S62Lrq35HZqBFD55423pmWb8PjaiR/W02zQC1uECBmw1vTN8WZaFz4TJ0i21EeJzwfebMb9MLxL8JZ102Z6VbA==} + peerDependencies: + expo: '*' + peerDependenciesMeta: + expo: + optional: true + + '@expo/metro-file-map@57.0.2': + resolution: {integrity: sha512-tb50nSIWwKpRufSkGuivOK0FbUxv1Uwqptb0SzFTk0bkmYmcy3gIwnCbOHTfmrQDVndRhPEiu2SYgh9Z0PCvGg==} + + '@expo/metro@56.0.2': + resolution: {integrity: sha512-Ld5AeYMCCDa8bLeWhfuLbZFFjlV3f6ORqyPz2glGh6RltIngMuLf9BTC2yvHFjkKuGxL5SynijmA8xmNNWn5iA==} + + '@expo/osascript@2.7.1': + resolution: {integrity: sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g==} + engines: {node: '>=12'} + + '@expo/package-manager@1.13.1': + resolution: {integrity: sha512-y/K+CaYYpZpNGZhSX4HyLT/vyIunFjNfyoxNysPBCefeLKI/VCx6f9LNPzrxayr3rCYO5bl9O8H+HRQK265Nkg==} + + '@expo/plist@0.8.1': + resolution: {integrity: sha512-3gTReGIUm0oRaMClsAJYxBnVPCl6fVpsl8HS+DTVxDhW4GyVyxg9E/Znm3BvcHtUJ51RJJI14pC1wvrNilCRHw==} + + '@expo/prebuild-config@57.0.15': + resolution: {integrity: sha512-xTbWHroj0PDmlbqvmU+zF9ZZxveJkiuyiPoeRJYRGruFHebRAWnoTdw5S7d/UCzDBI8ropGGu9g2eb2nMxtvAw==} + + '@expo/require-utils@57.0.5': + resolution: {integrity: sha512-kTAXj9lDFEIPMsbAOGCGbjBbMF0oi7CqkYM79KOX0DDD9wSwXmlKL1z2h8OwsrBf7mbOo2DjlRvZu4BEjrIxGw==} + peerDependencies: + typescript: ^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@expo/router-server@57.0.9': + resolution: {integrity: sha512-/PxRQozFesIyCJZOAtrQE8XcmcojNiL5ctPMQnbE4ojC2EJPu0zc7c0Y4PuXsoRxrZxm8Usw76/lCVrXcfTZ2w==} + peerDependencies: + '@expo/metro-runtime': ^57.0.15 + expo: '*' + expo-constants: ^57.0.17 + expo-font: ^57.0.3 + expo-router: '*' + expo-server: ^57.0.3 + react: '*' + react-dom: '*' + react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1 + peerDependenciesMeta: + '@expo/metro-runtime': + optional: true + expo-router: + optional: true + react-dom: + optional: true + react-server-dom-webpack: + optional: true + + '@expo/schema-utils@57.0.2': + resolution: {integrity: sha512-fMu/jyN0l1Wzv7XkeWR4IYCx1M8ryui3FdBNGrWwbRgJ7EhxXxK8E2jxP2W3pbgUwUY0V3hG8+GyfCZwny+Lxw==} + + '@expo/sdk-runtime-versions@1.0.0': + resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} + + '@expo/spawn-async@1.8.0': + resolution: {integrity: sha512-eb9xxd/LbuEGSdua4NumCu/McVB9EM+F/JxB9pWgnERw4HQ9XyTNH1KapG6oqLWR8TuRK2LQfzJlmNi94CVobw==} + engines: {node: '>=12'} + + '@expo/sudo-prompt@9.3.2': + resolution: {integrity: sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==} + + '@expo/ws-tunnel@2.0.0': + resolution: {integrity: sha512-j+JfTRdCk820J9dU0sA2SqshQIKFOMo7ED84w9MJFcebfbNQgsLztEY/SABDkGnjatrW4xGqnUhVRxSBVyCkXw==} + peerDependencies: + ws: ^8.0.0 + + '@expo/xcpretty@4.4.4': + resolution: {integrity: sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw==} + hasBin: true + + '@isaacs/ttlcache@1.4.1': + resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} + engines: {node: '>=12'} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@react-native/assets-registry@0.86.0': + resolution: {integrity: sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/babel-plugin-codegen@0.86.3': + resolution: {integrity: sha512-O6Xza4JBGPIU8J7YbKTyBoYL4thpy8jMW/oaLDWdAyOwYHKIjK47pAL5HUEbOe2bWz2PEKjbYRF2ApkJv1ottQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/codegen@0.86.0': + resolution: {integrity: sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + + '@react-native/codegen@0.86.3': + resolution: {integrity: sha512-Ux4jHi0fh+bdtVEcL0gaPLbY56V+SvFUDl/8sRAE1jdb4k+o7fT/4Nc29yz4X+qfjstkSqObQTMBGhdzxH9JvA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + + '@react-native/community-cli-plugin@0.86.0': + resolution: {integrity: sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@react-native-community/cli': '*' + '@react-native/metro-config': 0.86.0 + peerDependenciesMeta: + '@react-native-community/cli': + optional: true + '@react-native/metro-config': + optional: true + + '@react-native/debugger-frontend@0.86.0': + resolution: {integrity: sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/debugger-frontend@0.86.3': + resolution: {integrity: sha512-TQmeofQ0PcuylhhlleOeuzHYZfbrgm3gayXzowqUEzgRisTm1D40/J3ggqs7XkQi5HP5ZA3n8dHmKL9vIzPcsw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/debugger-shell@0.86.0': + resolution: {integrity: sha512-Y0zEkZzLz8ou6o/VLml1A31X/rMgc6DRjwxwzPMa94qRTMY070WeBCNTITQo4kKTBAUgbxh07oXPQqp0Tpja8w==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/debugger-shell@0.86.3': + resolution: {integrity: sha512-O4ds+J7xZfxkbih9T+cAGegBdvKSPKYJm/lDgC9CpEjFMkmzWTpVLU3Qsv9sqZuo58z+sGhIcJfPsCFFWHpqbQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/dev-middleware@0.86.0': + resolution: {integrity: sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/dev-middleware@0.86.3': + resolution: {integrity: sha512-LiEPTqTg/63bYUnrPyHLfjTDCNhA/+CUqI1+DsA9tYyewtSbULd5awsva6SgE10I+2iMhgKXS3ymkhU/kSCrGA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/gradle-plugin@0.86.0': + resolution: {integrity: sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/js-polyfills@0.86.0': + resolution: {integrity: sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/normalize-colors@0.86.0': + resolution: {integrity: sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g==} + + '@react-native/normalize-colors@0.86.3': + resolution: {integrity: sha512-Cv3CDkprb67GrzuaS9BGbBJC/6G4lIw3nyKOHRKTqTTum4bn37y5+R0Z04L8mcbQN85eEohNrRwb7IOM4j6uvg==} + + '@react-native/virtualized-lists@0.86.0': + resolution: {integrity: sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@types/react': ^19.2.0 + react: '*' + react-native: 0.86.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@sinclair/typebox@0.27.12': + resolution: {integrity: sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==} + + '@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/node@26.4.1': + resolution: {integrity: sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==} + + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} + + '@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==} + + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + + '@xmldom/xmldom@0.8.15': + resolution: {integrity: sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==} + engines: {node: '>=10.0.0'} + + '@xmldom/xmldom@0.9.12': + resolution: {integrity: sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==} + engines: {node: '>=14.6'} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + agent-cli-detector@0.1.7: + resolution: {integrity: sha512-d8OWDVdZMgjhLUT9ZPgSv/BdFFF9pVuscC0JdUSz3bjwE15gcp6u/o0/JooM2yyAWC49KThFhXlgTXRa9B7yng==} + engines: {node: '>=18.18'} + hasBin: true + + anser@1.4.10: + resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + 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'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-react-compiler@1.0.0: + resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} + + babel-plugin-react-native-web@0.21.2: + resolution: {integrity: sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==} + + babel-plugin-syntax-hermes-parser@0.36.0: + resolution: {integrity: sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==} + + babel-plugin-syntax-hermes-parser@0.36.1: + resolution: {integrity: sha512-ycduwJbvdvIMmVvlAZqGggS+pm5Eu4Bk9pcV9Sm2Z4PJNRVsKkv0g7vHj+LeuC1gHTeF67sJXFOq61IlqCa2hA==} + + babel-plugin-transform-flow-enums@0.0.2: + resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} + + babel-preset-expo@57.0.10: + resolution: {integrity: sha512-08bt6bqMZFoQuWO9gkt0EXotbqKHimELFGa1LlKJvY89iKsi4S0FJSFBFJ4pn9NZvofet48HIjYDQ0SZJ7bNrg==} + peerDependencies: + '@babel/runtime': ^7.20.0 + expo: '*' + expo-widgets: ^57.0.16 + react-refresh: '>=0.14.0 <1.0.0' + peerDependenciesMeta: + '@babel/runtime': + optional: true + expo: + optional: true + expo-widgets: + optional: true + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.11.21: + resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + + bplist-creator@0.1.0: + resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==} + + bplist-parser@0.3.1: + resolution: {integrity: sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==} + engines: {node: '>= 5.10.0'} + + bplist-parser@0.3.2: + resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} + engines: {node: '>= 5.10.0'} + + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.9: + resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chrome-launcher@0.15.2: + resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} + engines: {node: '>=12.13.0'} + hasBin: true + + chromium-edge-launcher@0.3.0: + resolution: {integrity: sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==} + + ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cli-cursor@2.1.0: + resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} + engines: {node: '>=4'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + 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@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-js-compat@3.50.0: + resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} + engines: {node: '>=6.4.0'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dnssd-advertise@1.1.6: + resolution: {integrity: sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.422: + resolution: {integrity: sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + expo-asset@57.0.16: + resolution: {integrity: sha512-IBRfQdW3iFT+GOBERMZLZM1MUNyrjMgMskuD0elVZ1ae44858UFlTJkHO3f8vi+u5zuv7O7KofsiN8NMG/uWzw==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-build-properties@57.0.17: + resolution: {integrity: sha512-+qA5we2h1Gpnd24RGCR1amfSx7Mtf8glZ0jA5bbe1Nfdw9NdAIl+HPUqU7xyOUDKD0JWsHh9ap6yJG/jTs0tvA==} + peerDependencies: + expo: '*' + + expo-constants@57.0.17: + resolution: {integrity: sha512-cPWYBKN1SEbg2lXg2f8VkePJqGZrJPLvVdQDCTfbFu9sQHO1M31Y1zILReUuGnmt/VKeUz40ze579vR00xGu/A==} + peerDependencies: + expo: '*' + react-native: '*' + + expo-file-system@57.0.6: + resolution: {integrity: sha512-pm8PMYEW6BnVOCBJ7df9FcDmQtE1tqImuYphlfYe1ipQRLYtdCayRczJcbKIsnB3mqEyp4gC2gWmMbsAsAOjVg==} + peerDependencies: + expo: '*' + react-native: '*' + + expo-font@57.0.3: + resolution: {integrity: sha512-kiVUnc2A8vAvO2FfDJTsQa5BwmY+PAkof/1wRb5MOkcX1jtiaSTwz9gCUAyscMBFLundZDmZrLy1P7LZVC+NvA==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-keep-awake@57.0.1: + resolution: {integrity: sha512-28lkFImeXTS+bhAjuCFV7w7tW5bXg27BJVrxv+nC/nyYa86qEa0oFeHwqol6ha5k4pdVDQgBF09GM4A1k76Ssg==} + peerDependencies: + expo: '*' + react: '*' + + expo-modules-autolinking@57.0.12: + resolution: {integrity: sha512-Q8KAlq37nLKsQ+HsS9NpQVpd5jCgqtu694TDUNHBUBpV9ViD82mRBh8Uug/h68RG9xnLS+kuL4nYaCuFRghHjg==} + hasBin: true + + expo-modules-core@57.0.16: + resolution: {integrity: sha512-vPbxDOdvG6Ucpws5ucdbQa+nNmdxKONS1fTg92sy0p4TnHX8hBiExtbrtUA/5+1t3FRCUuiy29/1nJEGdt3E4A==} + peerDependencies: + react: '*' + react-native: '*' + react-native-worklets: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 + peerDependenciesMeta: + react-native-worklets: + optional: true + + expo-modules-jsi@57.0.8: + resolution: {integrity: sha512-MFwvKP0FXLkEET27KsKvOh/L7Dpu2d00UgzAlmKozJgtI23WTKSyKPxu+EOSs9peSgkHzKI0vQOhWjOseGBMkQ==} + peerDependencies: + react-native: '*' + + expo-server@57.0.3: + resolution: {integrity: sha512-aK+LdKzauHSGmsOStZtyxdzv0zWssCkxTw3m4QuOhfDSJsZaMRTd9O41d8ixU/QfELTbaJ0oRNcF7JFV/7O9YQ==} + engines: {node: '>=20.16.0'} + + expo@57.0.20: + resolution: {integrity: sha512-H57GWLLpNPrltsrjvx6ROsMecoFQza7h9OzujUjBsaY+aDL+bj3GsZaH3T5v5B96R7txuidUwjUh5q5hi3qttg==} + hasBin: true + peerDependencies: + '@expo/dom-webview': '*' + '@expo/metro-runtime': '*' + react: '*' + react-dom: '*' + react-native: '*' + react-native-web: '*' + react-native-webview: '*' + peerDependenciesMeta: + '@expo/dom-webview': + optional: true + '@expo/metro-runtime': + optional: true + react-dom: + optional: true + react-native-web: + optional: true + react-native-webview: + optional: true + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + fb-dotslash@0.5.8: + resolution: {integrity: sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==} + engines: {node: '>=20'} + hasBin: true + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetch-nodeshim@0.4.10: + resolution: {integrity: sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + + flow-enums-runtime@0.0.6: + resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} + + fontfaceobserver@2.3.0: + resolution: {integrity: sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + getenv@2.0.0: + resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} + engines: {node: '>=6'} + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hermes-compiler@250829098.0.14: + resolution: {integrity: sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA==} + + hermes-estree@0.35.0: + resolution: {integrity: sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==} + + hermes-estree@0.36.0: + resolution: {integrity: sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==} + + hermes-estree@0.36.1: + resolution: {integrity: sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==} + + hermes-parser@0.35.0: + resolution: {integrity: sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==} + + hermes-parser@0.36.0: + resolution: {integrity: sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==} + + hermes-parser@0.36.1: + resolution: {integrity: sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==} + + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jimp-compact@0.16.1: + resolution: {integrity: sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + jsc-safe-url@0.2.4: + resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + lan-network@0.2.1: + resolution: {integrity: sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A==} + hasBin: true + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + lighthouse-logger@1.4.2: + resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + log-symbols@2.2.0: + resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} + engines: {node: '>=4'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + metro-babel-transformer@0.84.5: + resolution: {integrity: sha512-2WbHILKMiJUzfdjmGOQOqU1bWi9//gqiclc/tkk/AIsrrVw3efhZ1uhkOwMTxUEPOzqoo091H0olLmVZH5FHGQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-babel-transformer@0.84.6: + resolution: {integrity: sha512-B1ozl6KxFHbQjXZ2U8WiTPWXylqiD3V4EXyV5r0fqETOrqIrJlBvbVTD82UxIN+B3Vpe4l8G1tb15ocd2RyOQA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-cache-key@0.84.5: + resolution: {integrity: sha512-3dPB2TnvGjjf0/9O7AXVQURKXuQNauTZE7WpTGTlR017Gh/B5y0m/2wcqxfveUguHSpu89KhVxCAlr2k/H7uhQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-cache-key@0.84.6: + resolution: {integrity: sha512-6tyXt1BZ/3U183XV48oif7Nm65TE+pTo0Vs31u4FxMgqiYQR/SiSr3RoXn3pmy4pd280ZmLmct0x3jZpu0pV4A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-cache@0.84.5: + resolution: {integrity: sha512-WHS0n2OxQqtwEjSeQFPePNrMvEFhmQcUQM9cRJMHByWoi/GMWFBEWOf7hVkAM/0KRutAXNbDlSu/cZB6CyxgQQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-cache@0.84.6: + resolution: {integrity: sha512-KBJVpb02oKNO+jlWQ1Aax7cd11YP0MClDaMgx6C4MhyZ9g95J/dC1Bo6sCzXaU9L9h/w5cQfT5zDkGW5sOj99A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-config@0.84.5: + resolution: {integrity: sha512-zie+uN6oohscowi2S7ByU+wUw6CrT4ZxW9uAbONOObSxx86RGmnIAmjXHLkfmcdYoY7jzOPEbqcI6oeVmqyBQA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-config@0.84.6: + resolution: {integrity: sha512-cD2mLEofcuV26kxSV4hfV1Pbuh6igdUhYQriO8ZVeJ7HyZfq4/wqHV0gSR7fEO8NUp9odr60/Kzm5YakmghWwQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-core@0.84.5: + resolution: {integrity: sha512-xwm605hCi5Y6eJTTb8ZWo6pkUcoBEIyiQOfkZh5GwtDwUrP9SNhTQZhzJHrBCwwxlf3Ptl/pxWJgQ1rsNYMnrA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-core@0.84.6: + resolution: {integrity: sha512-ZqYskM8+f3PFMosBDJd8DLqRvT1ltRGhWL/ZfTpBvf4SoadO30VVrdhAoHFVGz3G1CJpWa3fgZH7nnnX/ybllA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-file-map@0.84.5: + resolution: {integrity: sha512-mlm/JL8toSbSc2akpKIGmzvrVRSCgZ5vkbycI34oMLoOnLGuLyC8WTyVJ6P0hZG/usDaGwZSl/s9BCRriqjGJA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-file-map@0.84.6: + resolution: {integrity: sha512-ov9VywWBsHPt54Zc7/DxWvqjTwxuZvhfeyHg8ZAMGKtf2PcH1wS3EnoBH7In53Rspk/eXVWAO5+avxunPn081w==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-minify-terser@0.84.5: + resolution: {integrity: sha512-BJoFwCEDsYnagPqarayInv2+diCDNDdLlaof/p6s9w4gh+gc9HXYM+pDvsKGKKUumpZswNF3Z/ftTMqKl/5IBg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-minify-terser@0.84.6: + resolution: {integrity: sha512-SqhB/Kxrw0XfyW0k8mEscqX/CQopm6Fp3EpdSIPzvKfGgQp9bTMahg9PAdVsXUmIWwErbmFw1VqB+VFBOKksUw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-resolver@0.84.5: + resolution: {integrity: sha512-VSSnepg1k6LyCwtb6eirWdAWlpKwBG8Rdtsr1mU38rMelFyWgh3/QuMSiZIZAIjwg/fsa8GhW5/FO54CAUPCEA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-resolver@0.84.6: + resolution: {integrity: sha512-PplpGc/OCLxgKeLNsLQO/VHsYDIvkcGGr3zNT7UanHCGRqhxKv245o+naV8cRI4/pcLqyc+9j+J0AJbT3Kp5Sg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-runtime@0.84.5: + resolution: {integrity: sha512-U1m2+d1Pr+JO2/iVXBB2OfXXityz7tqwIorxfrT15IEgaHvpJBq/OHiqnOWPKJbUl3JcxjcdviZZOKk85oK4Qg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-runtime@0.84.6: + resolution: {integrity: sha512-47EYO/DOai0PA3GoAyDpyXOgHNMwbl/Z9dQlQineDLHkpD42xOwMrIx1crT0+n6F+aGoEyxZmeFwfMzaIqVqKQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-source-map@0.84.5: + resolution: {integrity: sha512-2BtV5L9uPc49F13Gn5wiP6bX/EncqzqTIk2VL/0F/96Vo0YEOjluT/qktQjFODfqGFsucwnh5mPEAl/2jVEfeg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-source-map@0.84.6: + resolution: {integrity: sha512-4p/EplRbgNxhdqA6kAj1AmIq9qqYXYSAjgxuZ73rYA+p037xDDrxK8CyDKNfbn1k6aMuVt8LV0fpt7NHUTKiZw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-symbolicate@0.84.5: + resolution: {integrity: sha512-rQ40zYDAkaWBN9yvjUuAD0ZpzBMZSoKyGYXnb5JrfbKjun7fTvfoLHL3KXFYenBTYZkQtlp4cKSCv/1utxFyOw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + + metro-symbolicate@0.84.6: + resolution: {integrity: sha512-wJFyyF5ysbVyoYVWGL6GwVMiNnGllwDU2gF096376fl0fc8IFUlADSyNlswj31K9og2sOilXe/FdyWFPczPUYg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + + metro-transform-plugins@0.84.5: + resolution: {integrity: sha512-+InaSVGaOyt0DyRo4Y/zIdPI6CZwnbNho5LAL23tgmuGwv7fyfkF7kKfPjZcfxXBcoYdTLLFnCfCH/dHSiCqNg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-transform-plugins@0.84.6: + resolution: {integrity: sha512-pROPMFaj25Y9+3LlLqpBRz/7B/2YPnfEQz/z3juYxkh3FGQ+3c/Qh4E+khPSTdwTw0M6LitLYHMqaHMbQmuBJw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-transform-worker@0.84.5: + resolution: {integrity: sha512-ui1Z8x4s5RL36gMmKLaMMO7O9NNDHNdthEZSCDQHAau3JcAsTaFOK6I+2q4I/kW5u8hSEjJk9L45TXSVJw6g1A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-transform-worker@0.84.6: + resolution: {integrity: sha512-xLTVrOENaHR9DiuhnHS+zcle0rKsCy2CpXSIx7xD+zhJs+qVRNUyV7Z1zIt/0P/d0cSGVx846R+dIjHEudhcww==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro@0.84.5: + resolution: {integrity: sha512-r1liLkyFZMVSEMNjU1CJU5pRzs3NdkxHqXS60O25c0rCIqAR+cGk7rPydw/g0WAIKVXojIBIF45yYBPagJGcgw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + + metro@0.84.6: + resolution: {integrity: sha512-ty/tx/imE5Ph2gvPU788Ckim6RAC2tyZxLz2hWRgC9U7/fGKV7qY/slADy0qHPEw9cpUgc4BfmV3oUsYwm7oeg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@1.2.0: + resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} + engines: {node: '>=4'} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multitars@1.0.2: + resolution: {integrity: sha512-6GwVw5eLi9sThdtlS4PKwC7yRLaf45pYhIEzKBHdKxi+YOXGKFX8acIniH+Uh/+k9mS2lQOupTccjoe5r0/1IQ==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + + node-forge@1.4.0: + resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} + engines: {node: '>= 6.13.0'} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.54: + resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==} + engines: {node: '>=18'} + + npm-package-arg@11.0.3: + resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} + engines: {node: ^16.14.0 || >=18.0.0} + + nullthrows@1.1.1: + resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + + ob1@0.84.5: + resolution: {integrity: sha512-aH9RkoZc7w/90HBamFxTw8ZLFr05wXS+iOnvmrgo53Ep8Pyrm5FieQSaPIVROkfFVQISeD/zo92fes26TOwe+A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + ob1@0.84.6: + resolution: {integrity: sha512-+s+6zjd0X68hfAcn92NsEPXnSYOF3O7exw/Fj4UfRTB+UL+Tdjdkzu5/4WquXlzqgvNm8FWuuW6/0Yd8S2rYxw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + onetime@2.0.1: + resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} + engines: {node: '>=4'} + + open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + + ora@3.4.0: + resolution: {integrity: sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==} + engines: {node: '>=6'} + + parse-png@2.1.0: + resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} + engines: {node: '>=10'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + 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.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + plist@3.1.1: + resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} + engines: {node: '>=10.4.0'} + + pngjs@3.4.0: + resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} + engines: {node: '>=4.0.0'} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + proc-log@4.2.0: + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + promise@8.3.0: + resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + react-devtools-core@6.1.5: + resolution: {integrity: sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-native@0.86.0: + resolution: {integrity: sha512-17ALh/dd6AO4pgOVmOO5Axll5PbErEo3XFyLokyzW6usyi+OShIEPwUW26wLPlhVifgSOIfECCH0WN+0IqtJ1w==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + peerDependencies: + '@react-native/jest-preset': 0.86.0 + '@types/react': ^19.1.1 + react: ^19.2.3 + peerDependenciesMeta: + '@react-native/jest-preset': + optional: true + '@types/react': + optional: true + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + engines: {node: '>=0.10.0'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.2: + resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} + hasBin: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-workspace-root@2.0.1: + resolution: {integrity: sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@2.0.0: + resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} + engines: {node: '>=4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + sandbox-cli-detector@0.2.0: + resolution: {integrity: sha512-4lyHX0ZU0AZKwjgZ1InxZAa3PNpyEb8rOQ+Zss1ReYmhNzW0Q+h1zE5nvniXN0HaAWZaZE1zgVNEirb0R7LmNg==} + engines: {node: '>=18.18'} + hasBin: true + + sax@1.6.1: + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} + engines: {node: '>=11.0.0'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serialize-error@2.1.0: + resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} + engines: {node: '>=0.10.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + 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'} + + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + simple-plist@1.3.1: + resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slugify@1.6.9: + resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} + engines: {node: '>=8.0.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} + engines: {node: '>=6'} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stream-buffers@2.2.0: + resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} + engines: {node: '>= 0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@5.2.0: + resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + engines: {node: '>=6'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + structured-headers@0.4.1: + resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + 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'} + + supports-hyperlinks@2.3.0: + resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + terminal-link@2.1.1: + resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} + engines: {node: '>=8'} + + terser@5.51.2: + resolution: {integrity: sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==} + engines: {node: '>=10'} + hasBin: true + + throat@5.0.0: + resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + toqr@0.1.1: + resolution: {integrity: sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.3.2: + resolution: {integrity: sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@7.0.3: + resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vlq@1.0.1: + resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + + whatwg-url-minimum@0.1.2: + resolution: {integrity: sha512-XPEm0XFQWNVG292lII1PrRRJl3sItrs7CettZ4ncYxuDVpLyy+NwlGyut2hXI0JswcJUxeCH+CyOJK0ZzAXD6A==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + ws@7.5.13: + resolution: {integrity: sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + 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 + + xcode@3.0.1: + resolution: {integrity: sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==} + engines: {node: '>=10.0.0'} + + xml2js@0.6.0: + resolution: {integrity: sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +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/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.9 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.8 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-wrap-function': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helper-wrap-function@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-decorators': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-export-default-from@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-decorators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-export-default-from@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) + + '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-display-name@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-development@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@expo/cli@57.0.22(@expo/dom-webview@57.0.1)(expo-constants@57.0.17(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)))(expo-font@57.0.3(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': + dependencies: + '@expo/code-signing-certificates': 0.0.6 + '@expo/config': 57.0.9(typescript@6.0.3) + '@expo/config-plugins': 57.0.9(typescript@6.0.3) + '@expo/devcert': 1.2.1 + '@expo/env': 2.4.3 + '@expo/image-utils': 0.11.5(typescript@6.0.3) + '@expo/inline-modules': 0.1.7(typescript@6.0.3) + '@expo/json-file': 11.0.1 + '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@expo/metro': 56.0.2 + '@expo/metro-config': 57.0.12(expo@57.0.20)(typescript@6.0.3) + '@expo/metro-file-map': 57.0.2 + '@expo/osascript': 2.7.1 + '@expo/package-manager': 1.13.1 + '@expo/plist': 0.8.1 + '@expo/prebuild-config': 57.0.15(typescript@6.0.3) + '@expo/require-utils': 57.0.5(typescript@6.0.3) + '@expo/router-server': 57.0.9(expo-constants@57.0.17(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)))(expo-font@57.0.3(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo-server@57.0.3)(expo@57.0.20)(react@19.2.3) + '@expo/schema-utils': 57.0.2 + '@expo/spawn-async': 1.8.0 + '@expo/ws-tunnel': 2.0.0(ws@8.21.3) + '@expo/xcpretty': 4.4.4 + '@react-native/dev-middleware': 0.86.3 + accepts: 1.3.8 + agent-cli-detector: 0.1.7 + arg: 5.0.2 + bplist-creator: 0.1.0 + bplist-parser: 0.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + compression: 1.8.1 + connect: 3.7.0 + debug: 4.4.3 + dnssd-advertise: 1.1.6 + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-server: 57.0.3 + fetch-nodeshim: 0.4.10 + getenv: 2.0.0 + glob: 13.0.6 + lan-network: 0.2.1 + multitars: 1.0.2 + node-forge: 1.4.0 + npm-package-arg: 11.0.3 + ora: 3.4.0 + picomatch: 4.0.7 + pretty-format: 29.7.0 + progress: 2.0.3 + prompts: 2.4.2 + resolve-from: 5.0.0 + sandbox-cli-detector: 0.2.0 + semver: 7.8.5 + send: 0.19.2 + slugify: 1.6.9 + stacktrace-parser: 0.1.11 + structured-headers: 0.4.1 + terminal-link: 2.1.1 + toqr: 0.1.1 + wrap-ansi: 7.0.0 + ws: 8.21.3 + zod: 3.25.76 + optionalDependencies: + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + transitivePeerDependencies: + - '@expo/dom-webview' + - '@expo/metro-runtime' + - bufferutil + - expo-constants + - expo-font + - react + - react-dom + - react-server-dom-webpack + - supports-color + - typescript + - utf-8-validate + + '@expo/code-signing-certificates@0.0.6': + dependencies: + node-forge: 1.4.0 + + '@expo/config-plugins@57.0.9(typescript@6.0.3)': + dependencies: + '@expo/config-types': 57.0.2 + '@expo/json-file': 11.0.1 + '@expo/plist': 0.8.1 + '@expo/require-utils': 57.0.5(typescript@6.0.3) + '@expo/sdk-runtime-versions': 1.0.0 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + glob: 13.0.6 + semver: 7.8.5 + slugify: 1.6.9 + xcode: 3.0.1 + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/config-types@57.0.2': {} + + '@expo/config@57.0.9(typescript@6.0.3)': + dependencies: + '@expo/config-plugins': 57.0.9(typescript@6.0.3) + '@expo/config-types': 57.0.2 + '@expo/json-file': 11.0.1 + '@expo/require-utils': 57.0.5(typescript@6.0.3) + deepmerge: 4.3.1 + getenv: 2.0.0 + glob: 13.0.6 + resolve-workspace-root: 2.0.1 + semver: 7.8.5 + slugify: 1.6.9 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/devcert@1.2.1': + dependencies: + '@expo/sudo-prompt': 9.3.2 + debug: 3.2.7 + transitivePeerDependencies: + - supports-color + + '@expo/devtools@57.0.1(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': + dependencies: + chalk: 4.1.2 + optionalDependencies: + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + + '@expo/dom-webview@57.0.1(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': + dependencies: + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + + '@expo/env@2.4.3': + dependencies: + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + transitivePeerDependencies: + - supports-color + + '@expo/expo-modules-macros-plugin@0.6.1': {} + + '@expo/fingerprint@0.20.12': + dependencies: + '@expo/env': 2.4.3 + '@expo/spawn-async': 1.8.0 + arg: 5.0.2 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + glob: 13.0.6 + ignore: 5.3.2 + minimatch: 10.2.6 + resolve-from: 5.0.0 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + + '@expo/image-utils@0.11.5(typescript@6.0.3)': + dependencies: + '@expo/require-utils': 57.0.5(typescript@6.0.3) + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + getenv: 2.0.0 + jimp-compact: 0.16.1 + parse-png: 2.1.0 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/inline-modules@0.1.7(typescript@6.0.3)': + dependencies: + '@expo/config-plugins': 57.0.9(typescript@6.0.3) + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/json-file@11.0.1': + dependencies: + '@babel/code-frame': 7.29.7 + json5: 2.2.3 + + '@expo/local-build-cache-provider@57.0.8(typescript@6.0.3)': + dependencies: + '@expo/config': 57.0.9(typescript@6.0.3) + chalk: 4.1.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/log-box@57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': + dependencies: + '@expo/dom-webview': 57.0.1(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + anser: 1.4.10 + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + stacktrace-parser: 0.1.11 + + '@expo/metro-config@57.0.12(expo@57.0.20)(typescript@6.0.3)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@expo/config': 57.0.9(typescript@6.0.3) + '@expo/env': 2.4.3 + '@expo/json-file': 11.0.1 + '@expo/metro': 56.0.2 + '@expo/require-utils': 57.0.5(typescript@6.0.3) + '@expo/spawn-async': 1.8.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.6.0 + browserslist: 4.28.9 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + glob: 13.0.6 + hermes-parser: 0.36.1 + jsc-safe-url: 0.2.4 + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + resolve-from: 5.0.0 + optionalDependencies: + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + transitivePeerDependencies: + - bufferutil + - supports-color + - typescript + - utf-8-validate + + '@expo/metro-file-map@57.0.2': + dependencies: + debug: 4.4.3 + fb-watchman: 2.0.2 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + '@expo/metro@56.0.2': + dependencies: + metro: 0.84.5 + metro-babel-transformer: 0.84.5 + metro-cache: 0.84.5 + metro-cache-key: 0.84.5 + metro-config: 0.84.5 + metro-core: 0.84.5 + metro-file-map: 0.84.5 + metro-minify-terser: 0.84.5 + metro-resolver: 0.84.5 + metro-runtime: 0.84.5 + metro-source-map: 0.84.5 + metro-symbolicate: 0.84.5 + metro-transform-plugins: 0.84.5 + metro-transform-worker: 0.84.5 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@expo/osascript@2.7.1': + dependencies: + '@expo/spawn-async': 1.8.0 + + '@expo/package-manager@1.13.1': + dependencies: + '@expo/json-file': 11.0.1 + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + npm-package-arg: 11.0.3 + ora: 3.4.0 + resolve-workspace-root: 2.0.1 + + '@expo/plist@0.8.1': + dependencies: + '@xmldom/xmldom': 0.8.15 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + '@expo/prebuild-config@57.0.15(typescript@6.0.3)': + dependencies: + '@expo/config': 57.0.9(typescript@6.0.3) + '@expo/config-plugins': 57.0.9(typescript@6.0.3) + '@expo/config-types': 57.0.2 + '@expo/image-utils': 0.11.5(typescript@6.0.3) + '@expo/json-file': 11.0.1 + '@react-native/normalize-colors': 0.86.3 + debug: 4.4.3 + expo-modules-autolinking: 57.0.12(typescript@6.0.3) + resolve-from: 5.0.0 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/require-utils@57.0.5(typescript@6.0.3)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@expo/router-server@57.0.9(expo-constants@57.0.17(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)))(expo-font@57.0.3(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo-server@57.0.3)(expo@57.0.20)(react@19.2.3)': + dependencies: + debug: 4.4.3 + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-constants: 57.0.17(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)) + expo-font: 57.0.3(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + expo-server: 57.0.3 + react: 19.2.3 + transitivePeerDependencies: + - supports-color + + '@expo/schema-utils@57.0.2': {} + + '@expo/sdk-runtime-versions@1.0.0': {} + + '@expo/spawn-async@1.8.0': + dependencies: + cross-spawn: 7.0.6 + + '@expo/sudo-prompt@9.3.2': {} + + '@expo/ws-tunnel@2.0.0(ws@8.21.3)': + dependencies: + ws: 8.21.3 + + '@expo/xcpretty@4.4.4': + dependencies: + '@babel/code-frame': 7.29.7 + chalk: 4.1.2 + js-yaml: 4.3.2 + + '@isaacs/ttlcache@1.4.1': {} + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.12 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 26.4.1 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@react-native/assets-registry@0.86.0': {} + + '@react-native/babel-plugin-codegen@0.86.3(@babel/core@7.29.7)': + dependencies: + '@babel/traverse': 7.29.8 + '@react-native/codegen': 0.86.3(@babel/core@7.29.7) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@react-native/codegen@0.86.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.8 + hermes-parser: 0.36.0 + invariant: 2.2.4 + nullthrows: 1.1.1 + tinyglobby: 0.2.17 + yargs: 17.7.3 + + '@react-native/codegen@0.86.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.8 + hermes-parser: 0.36.0 + invariant: 2.2.4 + nullthrows: 1.1.1 + tinyglobby: 0.2.17 + yargs: 17.7.3 + + '@react-native/community-cli-plugin@0.86.0': + dependencies: + '@react-native/dev-middleware': 0.86.0 + debug: 4.4.3 + invariant: 2.2.4 + metro: 0.84.6 + metro-config: 0.84.6 + metro-core: 0.84.6 + semver: 7.8.5 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/debugger-frontend@0.86.0': {} + + '@react-native/debugger-frontend@0.86.3': {} + + '@react-native/debugger-shell@0.86.0': + dependencies: + cross-spawn: 7.0.6 + debug: 4.4.3 + fb-dotslash: 0.5.8 + transitivePeerDependencies: + - supports-color + + '@react-native/debugger-shell@0.86.3': + dependencies: + cross-spawn: 7.0.6 + debug: 4.4.3 + fb-dotslash: 0.5.8 + transitivePeerDependencies: + - supports-color + + '@react-native/dev-middleware@0.86.0': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.86.0 + '@react-native/debugger-shell': 0.86.0 + chrome-launcher: 0.15.2 + chromium-edge-launcher: 0.3.0 + connect: 3.7.0 + debug: 4.4.3 + invariant: 2.2.4 + nullthrows: 1.1.1 + open: 7.4.2 + serve-static: 1.16.3 + ws: 7.5.13 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/dev-middleware@0.86.3': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.86.3 + '@react-native/debugger-shell': 0.86.3 + chrome-launcher: 0.15.2 + chromium-edge-launcher: 0.3.0 + connect: 3.7.0 + debug: 4.4.3 + invariant: 2.2.4 + nullthrows: 1.1.1 + open: 7.4.2 + serve-static: 1.16.3 + ws: 7.5.13 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/gradle-plugin@0.86.0': {} + + '@react-native/js-polyfills@0.86.0': {} + + '@react-native/normalize-colors@0.86.0': {} + + '@react-native/normalize-colors@0.86.3': {} + + '@react-native/virtualized-lists@0.86.0(@types/react@19.2.18)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)': + dependencies: + invariant: 2.2.4 + nullthrows: 1.1.1 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.18 + + '@sinclair/typebox@0.27.12': {} + + '@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/node@26.4.1': + dependencies: + undici-types: 8.3.0 + + '@types/react@19.2.18': + dependencies: + csstype: 3.2.3 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@ungap/structured-clone@1.4.0': {} + + '@xmldom/xmldom@0.8.15': {} + + '@xmldom/xmldom@0.9.12': {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + + acorn@8.18.0: {} + + agent-base@7.1.4: {} + + agent-cli-detector@0.1.7: {} + + anser@1.4.10: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@4.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + arg@5.0.2: {} + + argparse@2.0.1: {} + + asap@2.0.6: {} + + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + core-js-compat: 3.50.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + babel-plugin-react-compiler@1.0.0: + dependencies: + '@babel/types': 7.29.8 + + babel-plugin-react-native-web@0.21.2: {} + + babel-plugin-syntax-hermes-parser@0.36.0: + dependencies: + hermes-parser: 0.36.0 + + babel-plugin-syntax-hermes-parser@0.36.1: + dependencies: + hermes-parser: 0.36.1 + + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.7): + dependencies: + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - '@babel/core' + + babel-preset-expo@57.0.10(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.20)(react-refresh@0.14.2): + dependencies: + '@babel/generator': 7.29.8 + '@babel/helper-module-imports': 7.29.7 + '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-export-default-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-pure-annotations': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@react-native/babel-plugin-codegen': 0.86.3(@babel/core@7.29.7) + babel-plugin-react-compiler: 1.0.0 + babel-plugin-react-native-web: 0.21.2 + babel-plugin-syntax-hermes-parser: 0.36.1 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) + debug: 4.4.3 + react-refresh: 0.14.2 + optionalDependencies: + '@babel/runtime': 7.29.7 + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + balanced-match@4.0.4: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.11.21: {} + + big-integer@1.6.52: {} + + bplist-creator@0.1.0: + dependencies: + stream-buffers: 2.2.0 + + bplist-parser@0.3.1: + dependencies: + big-integer: 1.6.52 + + bplist-parser@0.3.2: + dependencies: + big-integer: 1.6.52 + + brace-expansion@5.0.9: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.9: + dependencies: + baseline-browser-mapping: 2.11.21 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.422 + node-releases: 2.0.54 + update-browserslist-db: 1.3.2(browserslist@4.28.9) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + bytes@3.1.2: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001810: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chrome-launcher@0.15.2: + dependencies: + '@types/node': 26.4.1 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + transitivePeerDependencies: + - supports-color + + chromium-edge-launcher@0.3.0: + dependencies: + '@types/node': 26.4.1 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + mkdirp: 1.0.4 + transitivePeerDependencies: + - supports-color + + ci-info@2.0.0: {} + + ci-info@3.9.0: {} + + cli-cursor@2.1.0: + dependencies: + restore-cursor: 2.0.0 + + cli-spinners@2.9.2: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + commander@12.1.0: {} + + commander@2.20.3: {} + + commander@7.2.0: {} + + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.8.1: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + + content-type@2.1.0: {} + + convert-source-map@2.0.0: {} + + core-js-compat@3.50.0: + dependencies: + browserslist: 4.28.9 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-libc@2.1.2: {} + + dnssd-advertise@1.1.6: {} + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.422: {} + + emoji-regex@8.0.0: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + es-errors@1.3.0: {} + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + + expo-asset@57.0.16(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3): + dependencies: + '@expo/image-utils': 0.11.5(typescript@6.0.3) + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-constants: 57.0.17(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + transitivePeerDependencies: + - supports-color + - typescript + + expo-build-properties@57.0.17(expo@57.0.20): + dependencies: + '@expo/schema-utils': 57.0.2 + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + resolve-from: 5.0.0 + semver: 7.8.5 + + expo-constants@57.0.17(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)): + dependencies: + '@expo/env': 2.4.3 + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + transitivePeerDependencies: + - supports-color + + expo-file-system@57.0.6(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)): + dependencies: + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + + expo-font@57.0.3(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + dependencies: + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + fontfaceobserver: 2.3.0 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + + expo-keep-awake@57.0.1(expo@57.0.20)(react@19.2.3): + dependencies: + expo: 57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react: 19.2.3 + + expo-modules-autolinking@57.0.12(typescript@6.0.3): + dependencies: + '@expo/require-utils': 57.0.5(typescript@6.0.3) + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + commander: 7.2.0 + transitivePeerDependencies: + - supports-color + - typescript + + expo-modules-core@57.0.16(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3): + dependencies: + '@expo/expo-modules-macros-plugin': 0.6.1 + expo-modules-jsi: 57.0.8(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)) + invariant: 2.2.4 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + + expo-modules-jsi@57.0.8(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)): + dependencies: + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + + expo-server@57.0.3: {} + + expo@57.0.20(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3): + dependencies: + '@babel/runtime': 7.29.7 + '@expo/cli': 57.0.22(@expo/dom-webview@57.0.1)(expo-constants@57.0.17(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)))(expo-font@57.0.3(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@expo/config': 57.0.9(typescript@6.0.3) + '@expo/config-plugins': 57.0.9(typescript@6.0.3) + '@expo/devtools': 57.0.1(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@expo/fingerprint': 0.20.12 + '@expo/local-build-cache-provider': 57.0.8(typescript@6.0.3) + '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + '@expo/metro': 56.0.2 + '@expo/metro-config': 57.0.12(expo@57.0.20)(typescript@6.0.3) + '@ungap/structured-clone': 1.4.0 + babel-preset-expo: 57.0.10(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.20)(react-refresh@0.14.2) + expo-asset: 57.0.16(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-constants: 57.0.17(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)) + expo-file-system: 57.0.6(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3)) + expo-font: 57.0.3(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + expo-keep-awake: 57.0.1(expo@57.0.20)(react@19.2.3) + expo-modules-autolinking: 57.0.12(typescript@6.0.3) + expo-modules-core: 57.0.16(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + pretty-format: 29.7.0 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3) + react-refresh: 0.14.2 + whatwg-url-minimum: 0.1.2 + optionalDependencies: + '@expo/dom-webview': 57.0.1(expo@57.0.20)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + transitivePeerDependencies: + - '@babel/core' + - bufferutil + - expo-router + - expo-widgets + - react-native-worklets + - react-server-dom-webpack + - supports-color + - typescript + - utf-8-validate + + exponential-backoff@3.1.3: {} + + fb-dotslash@0.5.8: {} + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fetch-nodeshim@0.4.10: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.1.2: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + flow-enums-runtime@0.0.6: {} + + fontfaceobserver@2.3.0: {} + + fresh@0.5.2: {} + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + getenv@2.0.0: {} + + glob@13.0.6: + dependencies: + minimatch: 10.2.6 + minipass: 7.1.3 + path-scurry: 2.0.2 + + graceful-fs@4.2.11: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hermes-compiler@250829098.0.14: {} + + hermes-estree@0.35.0: {} + + hermes-estree@0.36.0: {} + + hermes-estree@0.36.1: {} + + hermes-parser@0.35.0: + dependencies: + hermes-estree: 0.35.0 + + hermes-parser@0.36.0: + dependencies: + hermes-estree: 0.36.0 + + hermes-parser@0.36.1: + dependencies: + hermes-estree: 0.36.1 + + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ignore@5.3.2: {} + + inherits@2.0.4: {} + + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-docker@2.2.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-number@7.0.0: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + + jest-get-type@29.6.3: {} + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 26.4.1 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.2 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-worker@29.7.0: + dependencies: + '@types/node': 26.4.1 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jimp-compact@0.16.1: {} + + js-tokens@4.0.0: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + jsc-safe-url@0.2.4: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + kleur@3.0.3: {} + + lan-network@0.2.1: {} + + leven@3.1.0: {} + + lighthouse-logger@1.4.2: + dependencies: + debug: 2.6.9 + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lodash.debounce@4.0.8: {} + + lodash.throttle@4.1.1: {} + + log-symbols@2.2.0: + dependencies: + chalk: 2.4.2 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@11.5.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + marky@1.3.0: {} + + memoize-one@5.2.1: {} + + merge-stream@2.0.0: {} + + metro-babel-transformer@0.84.5: + dependencies: + '@babel/core': 7.29.7 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.35.0 + metro-cache-key: 0.84.5 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-babel-transformer@0.84.6: + dependencies: + '@babel/core': 7.29.7 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.35.0 + metro-cache-key: 0.84.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-cache-key@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-cache-key@0.84.6: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-cache@0.84.5: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.84.5 + transitivePeerDependencies: + - supports-color + + metro-cache@0.84.6: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.84.6 + transitivePeerDependencies: + - supports-color + + metro-config@0.84.5: + dependencies: + connect: 3.7.0 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.84.5 + metro-cache: 0.84.5 + metro-core: 0.84.5 + metro-runtime: 0.84.5 + yaml: 2.9.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-config@0.84.6: + dependencies: + connect: 3.7.0 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.84.6 + metro-cache: 0.84.6 + metro-core: 0.84.6 + metro-runtime: 0.84.6 + yaml: 2.9.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-core@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.84.5 + + metro-core@0.84.6: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.84.6 + + metro-file-map@0.84.5: + dependencies: + debug: 4.4.3 + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + nullthrows: 1.1.1 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + metro-file-map@0.84.6: + dependencies: + debug: 4.4.3 + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + nullthrows: 1.1.1 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + metro-minify-terser@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.51.2 + + metro-minify-terser@0.84.6: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.51.2 + + metro-resolver@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-resolver@0.84.6: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-runtime@0.84.5: + dependencies: + '@babel/runtime': 7.29.7 + flow-enums-runtime: 0.0.6 + + metro-runtime@0.84.6: + dependencies: + '@babel/runtime': 7.29.7 + flow-enums-runtime: 0.0.6 + + metro-source-map@0.84.5: + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.84.5 + nullthrows: 1.1.1 + ob1: 0.84.5 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-source-map@0.84.6: + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.84.6 + nullthrows: 1.1.1 + ob1: 0.84.6 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-symbolicate@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.84.5 + nullthrows: 1.1.1 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-symbolicate@0.84.6: + dependencies: + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.84.6 + nullthrows: 1.1.1 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-transform-plugins@0.84.5: + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-transform-plugins@0.84.6: + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-transform-worker@0.84.5: + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + flow-enums-runtime: 0.0.6 + metro: 0.84.5 + metro-babel-transformer: 0.84.5 + metro-cache: 0.84.5 + metro-cache-key: 0.84.5 + metro-minify-terser: 0.84.5 + metro-source-map: 0.84.5 + metro-transform-plugins: 0.84.5 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-transform-worker@0.84.6: + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + flow-enums-runtime: 0.0.6 + metro: 0.84.6 + metro-babel-transformer: 0.84.6 + metro-cache: 0.84.6 + metro-cache-key: 0.84.6 + metro-minify-terser: 0.84.6 + metro-source-map: 0.84.6 + metro-transform-plugins: 0.84.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro@0.84.5: + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + accepts: 2.0.0 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 4.4.3 + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.35.0 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.84.5 + metro-cache: 0.84.5 + metro-cache-key: 0.84.5 + metro-config: 0.84.5 + metro-core: 0.84.5 + metro-file-map: 0.84.5 + metro-resolver: 0.84.5 + metro-runtime: 0.84.5 + metro-source-map: 0.84.5 + metro-symbolicate: 0.84.5 + metro-transform-plugins: 0.84.5 + metro-transform-worker: 0.84.5 + mime-types: 3.0.2 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + throat: 5.0.0 + ws: 7.5.13 + yargs: 17.7.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro@0.84.6: + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + accepts: 2.0.0 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 4.4.3 + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.35.0 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.84.6 + metro-cache: 0.84.6 + metro-cache-key: 0.84.6 + metro-config: 0.84.6 + metro-core: 0.84.6 + metro-file-map: 0.84.6 + metro-resolver: 0.84.6 + metro-runtime: 0.84.6 + metro-source-map: 0.84.6 + metro-symbolicate: 0.84.6 + metro-transform-plugins: 0.84.6 + metro-transform-worker: 0.84.6 + mime-types: 3.0.2 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + throat: 5.0.0 + ws: 7.5.13 + yargs: 17.7.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@1.6.0: {} + + mimic-fn@1.2.0: {} + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.9 + + minipass@7.1.3: {} + + mkdirp@1.0.4: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + multitars@1.0.2: {} + + nanoid@3.3.18: {} + + negotiator@0.6.3: {} + + negotiator@0.6.4: {} + + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + + node-forge@1.4.0: {} + + node-int64@0.4.0: {} + + node-releases@2.0.54: {} + + npm-package-arg@11.0.3: + dependencies: + hosted-git-info: 7.0.2 + proc-log: 4.2.0 + semver: 7.8.5 + validate-npm-package-name: 5.0.1 + + nullthrows@1.1.1: {} + + ob1@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + + ob1@0.84.6: + dependencies: + flow-enums-runtime: 0.0.6 + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + onetime@2.0.1: + dependencies: + mimic-fn: 1.2.0 + + open@7.4.2: + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + + ora@3.4.0: + dependencies: + chalk: 2.4.2 + cli-cursor: 2.1.0 + cli-spinners: 2.9.2 + log-symbols: 2.2.0 + strip-ansi: 5.2.0 + wcwidth: 1.0.1 + + parse-png@2.1.0: + dependencies: + pngjs: 3.4.0 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.7: {} + + plist@3.1.1: + dependencies: + '@xmldom/xmldom': 0.9.12 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + pngjs@3.4.0: {} + + postcss@8.5.28: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + proc-log@4.2.0: {} + + progress@2.0.3: {} + + promise@8.3.0: + dependencies: + asap: 2.0.6 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + range-parser@1.2.1: {} + + react-devtools-core@6.1.5: + dependencies: + shell-quote: 1.10.0 + ws: 7.5.13 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + react-is@18.3.1: {} + + react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3): + dependencies: + '@react-native/assets-registry': 0.86.0 + '@react-native/codegen': 0.86.0(@babel/core@7.29.7) + '@react-native/community-cli-plugin': 0.86.0 + '@react-native/gradle-plugin': 0.86.0 + '@react-native/js-polyfills': 0.86.0 + '@react-native/normalize-colors': 0.86.0 + '@react-native/virtualized-lists': 0.86.0(@types/react@19.2.18)(react-native@0.86.0(@babel/core@7.29.7)(@types/react@19.2.18)(react@19.2.3))(react@19.2.3) + abort-controller: 3.0.0 + anser: 1.4.10 + ansi-regex: 5.0.1 + babel-plugin-syntax-hermes-parser: 0.36.0 + base64-js: 1.5.1 + commander: 12.1.0 + flow-enums-runtime: 0.0.6 + hermes-compiler: 250829098.0.14 + invariant: 2.2.4 + memoize-one: 5.2.1 + metro-runtime: 0.84.6 + metro-source-map: 0.84.6 + nullthrows: 1.1.1 + pretty-format: 29.7.0 + promise: 8.3.0 + react: 19.2.3 + react-devtools-core: 6.1.5 + react-refresh: 0.14.2 + regenerator-runtime: 0.13.11 + scheduler: 0.27.0 + semver: 7.8.5 + stacktrace-parser: 0.1.11 + tinyglobby: 0.2.17 + whatwg-fetch: 3.6.20 + ws: 7.5.13 + yargs: 17.7.3 + optionalDependencies: + '@types/react': 19.2.18 + transitivePeerDependencies: + - '@babel/core' + - '@react-native-community/cli' + - '@react-native/metro-config' + - bufferutil + - supports-color + - utf-8-validate + + react-refresh@0.14.2: {} + + react@19.2.3: {} + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.13.11: {} + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.2 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.2: + dependencies: + jsesc: 3.1.0 + + require-directory@2.1.1: {} + + resolve-from@5.0.0: {} + + resolve-workspace-root@2.0.1: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@2.0.0: + dependencies: + onetime: 2.0.1 + signal-exit: 3.0.7 + + safe-buffer@5.2.1: {} + + sandbox-cli-detector@0.2.0: {} + + sax@1.6.1: {} + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.5: {} + + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serialize-error@2.1.0: {} + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.10.0: {} + + signal-exit@3.0.7: {} + + simple-plist@1.3.1: + dependencies: + bplist-creator: 0.1.0 + bplist-parser: 0.3.1 + plist: 3.1.1 + + sisteransi@1.0.5: {} + + slugify@1.6.9: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + stackframe@1.3.4: {} + + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + + statuses@1.5.0: {} + + statuses@2.0.2: {} + + stream-buffers@2.2.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@5.2.0: + dependencies: + ansi-regex: 4.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + structured-headers@0.4.1: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@2.3.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + terminal-link@2.1.1: + dependencies: + ansi-escapes: 4.3.2 + supports-hyperlinks: 2.3.0 + + terser@5.51.2: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.18.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + throat@5.0.0: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + toqr@0.1.1: {} + + type-fest@0.21.3: {} + + type-fest@0.7.1: {} + + typescript@6.0.3: {} + + undici-types@8.3.0: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.3.2(browserslist@4.28.9): + dependencies: + browserslist: 4.28.9 + escalade: 3.2.0 + picocolors: 1.1.1 + + utils-merge@1.0.1: {} + + uuid@7.0.3: {} + + validate-npm-package-name@5.0.1: {} + + vary@1.1.2: {} + + vlq@1.0.1: {} + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + whatwg-fetch@3.6.20: {} + + whatwg-url-minimum@0.1.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + ws@7.5.13: {} + + ws@8.21.3: {} + + xcode@3.0.1: + dependencies: + simple-plist: 1.3.1 + uuid: 7.0.3 + + xml2js@0.6.0: + dependencies: + sax: 1.6.1 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + + xmlbuilder@15.1.1: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml@2.9.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.3: + 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 + + zod@3.25.76: {} diff --git a/packages/zql-benchmarks/rn/pnpm-workspace.yaml b/packages/zql-benchmarks/rn/pnpm-workspace.yaml new file mode 100644 index 0000000000..59e9464e7c --- /dev/null +++ b/packages/zql-benchmarks/rn/pnpm-workspace.yaml @@ -0,0 +1,8 @@ +# Intentionally empty, and intentionally present. +# +# This app is nested inside the mono pnpm workspace but is not a member of it +# (pnpm-workspace.yaml at the repo root globs a single path segment). Without +# this file, `pnpm install` run from here walks up, reinstalls the entire +# monorepo, and installs none of this app's dependencies. Declaring a workspace +# root here stops that walk so the install stays local. +packages: [] diff --git a/packages/zql-benchmarks/rn/tsconfig.json b/packages/zql-benchmarks/rn/tsconfig.json new file mode 100644 index 0000000000..b9567f6052 --- /dev/null +++ b/packages/zql-benchmarks/rn/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true + } +} diff --git a/packages/zql-benchmarks/src/perf-rn.ts b/packages/zql-benchmarks/src/perf-rn.ts new file mode 100644 index 0000000000..ace551ae89 --- /dev/null +++ b/packages/zql-benchmarks/src/perf-rn.ts @@ -0,0 +1,33 @@ +/** + * Runs the ZQL benchmarks on a React Native device. + * + * Everything generic — device drivers, the HTTP control server, launching Expo, + * Hermes profiling — lives in `tools/rn-bench`. There is nothing to configure + * per run here: these benchmarks are pure in-memory IVM, so unlike the + * Replicache harness there is no storage backend to select and no fixture to + * serve. + */ +import * as path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {runRnBench} from '../../../tools/rn-bench/src/runner.ts'; + +const rootDir = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); + +await runRnBench({ + rootDir, + cliName: 'perf:rn', + buildScript: 'tool/build-rn.ts', + globalName: '__zqlPerf', + + // Read from the built bundle rather than re-imported here, so the list can + // never drift from what the device actually runs. The bundle is plain ESM + // with no React Native imports, so Node can load it directly. + listBenchmarks: async () => { + const bundle = (await import(path.join(rootDir, 'out', 'rn.js'))) as { + benchmarks: {name: string; group: string}[]; + }; + return bundle.benchmarks.map(({name, group}) => ({name, group})); + }, + + configureExpr: () => '__zqlPerf.configure()', +}); diff --git a/packages/zql-benchmarks/src/rn.ts b/packages/zql-benchmarks/src/rn.ts new file mode 100644 index 0000000000..3cf415a09a --- /dev/null +++ b/packages/zql-benchmarks/src/rn.ts @@ -0,0 +1,48 @@ +/** + * React Native entry point for the ZQL benchmarks. + * + * Importing a `.bench.ts` module registers its benchmarks as a side effect — + * they call `bench`/`describe` from `shared/src/bench.ts`, which the RN build + * rewrites to `tools/rn-bench/src/mitata-shim.ts` (see tool/build-rn.ts). So + * the same files feed both the Vitest/mitata suite and the device harness, and + * there is no second copy to keep in step. + * + * Only the modules that run without Node are listed. The rest of the suite + * needs PostgreSQL (`chinook-*`, `planner-*`, `zbugs`), `zero-cache`, or + * ZQLite's native `@rocicorp/zero-sqlite3`, none of which exist on a phone. + */ +import {formatAsReplicache} from '../../../tools/rn-bench/src/format.ts'; +import {makeHarness} from '../../../tools/rn-bench/src/harness.ts'; +import {collectedBenchmarks} from '../../../tools/rn-bench/src/mitata-shim.ts'; + +// Side-effecting imports, in the order their groups should appear. +import './ivm-memory.bench.ts'; +import './array-view-relationships.bench.ts'; +import './array-view-transaction.bench.ts'; +import './cold-boot-hydration.bench.ts'; +import './memory-ivm-deopt.bench.ts'; +import './debug-row-vended.bench.ts'; +import './query-hash.bench.ts'; + +export type {BenchmarkResult} from '../../../tools/rn-bench/src/benchmark.ts'; +export { + formatAsBenchmarkJS, + formatAsReplicache, +} from '../../../tools/rn-bench/src/format.ts'; + +const harness = makeHarness(collectedBenchmarks()); + +export const {benchmarks, findBenchmarks, runBenchmarkByNameAndGroup} = harness; + +/** + * Present for symmetry with the Replicache harness, which uses it to pick a + * key/value backend. These benchmarks run entirely in memory and have nothing + * to configure, but the host app and the profiler both call it. + */ +export function configure(_opts?: unknown): void { + // Nothing to configure. +} + +// Referenced so the formatter is not dropped from the bundle when the app only +// reaches it through the profiler's injected expression. +export const __formatter = formatAsReplicache; diff --git a/packages/zql-benchmarks/tool/build-rn.ts b/packages/zql-benchmarks/tool/build-rn.ts new file mode 100644 index 0000000000..afec27edb2 --- /dev/null +++ b/packages/zql-benchmarks/tool/build-rn.ts @@ -0,0 +1,110 @@ +// @ts-check + +/** + * Bundles the ZQL benchmarks into a single self-contained file the Expo app in + * `rn/` imports. Metro never resolves monorepo TypeScript; it only sees the + * output of this build. + */ +import * as path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import * as esbuild from 'esbuild'; +import {makeDefine, sharedOptions} from '../../shared/src/build.ts'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.join(dirname, '..'); +const shimPath = path.join( + rootDir, + '..', + '..', + 'tools', + 'rn-bench', + 'src', + 'mitata-shim.ts', +); + +/** + * Two rewrites make the existing `.bench.ts` files runnable on a device: + * + * 1. `shared/src/bench.ts` wraps mitata and Vitest, neither of which runs on + * Hermes, so it is replaced by the shim. + * 2. Each `.bench.ts` module gets a preamble naming itself, so benchmarks from + * different files land in different groups. Several files declare a suite + * called `push`, which would otherwise collide. + */ +const benchShimPlugin: esbuild.Plugin = { + name: 'rn-bench-shim', + setup(build) { + build.onResolve({filter: /shared\/src\/bench\.ts$/}, () => ({ + path: shimPath, + })); + + build.onLoad({filter: /\.bench\.ts$/}, async args => { + const source = await import('node:fs/promises').then(fs => + fs.readFile(args.path, 'utf-8'), + ); + const name = path.basename(args.path).replace(/\.bench\.ts$/, ''); + return { + contents: + `import {__setBenchFile as __sbf} from ${JSON.stringify(shimPath)};\n` + + `__sbf(${JSON.stringify(name)});\n` + + source, + loader: 'ts', + resolveDir: path.dirname(args.path), + }; + }); + }, +}; + +async function buildRN(): Promise { + // Minified by default to match what a real app would ship, but Hermes CPU + // profiles are unreadable without function names. + const minify = process.env.PERF_RN_NO_MINIFY !== '1'; + await esbuild.build({ + ...sharedOptions(minify), + external: ['node:*'], + format: 'esm', + platform: 'neutral', + // 'neutral' resolves no main fields at all, so any dependency that ships + // only a legacy entry (rather than an `exports` map esbuild can follow) + // fails to resolve outright. Same order replicache-perf uses: what Metro + // would pick, with `module` ahead of `main` so an ESM build wins where + // Metro would have fallen back to CommonJS. + mainFields: ['react-native', 'browser', 'module', 'main'], + splitting: false, + plugins: [benchShimPlugin], + define: { + ...makeDefine('release'), + 'import.meta.env': 'undefined', + // Hermes cannot parse import.meta at all. + 'import.meta.url': '""', + }, + outfile: path.join(rootDir, 'out', 'rn.js'), + entryPoints: [path.join(rootDir, 'src', 'rn.ts')], + }); +} + +/** + * The declaration for the bundle, emitted beside it so the host app's copy is + * build output rather than a file kept in step by hand. + */ +async function buildRNTypes(): Promise { + const fs = await import('node:fs/promises'); + await fs.writeFile( + path.join(rootDir, 'out', 'rn.d.ts'), + `// Generated by tool/build-rn.ts from src/rn.ts. Do not edit. + +/** Opaque to the host app: it formats results and posts them back. */ +export type BenchmarkResult = {readonly __benchmarkResult: unique symbol}; + +export declare function configure(opts?: unknown): void; +export declare function formatAsReplicache(result: BenchmarkResult): string; +export declare function runBenchmarkByNameAndGroup( + name: string, + group: string, +): Promise<['result', BenchmarkResult] | ['error', unknown] | undefined>; +`, + ); +} + +await buildRN(); +await buildRNTypes(); diff --git a/packages/zql-benchmarks/tsconfig.json b/packages/zql-benchmarks/tsconfig.json index 3c94740c94..01d4288e06 100644 --- a/packages/zql-benchmarks/tsconfig.json +++ b/packages/zql-benchmarks/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "rootDir": "..", + "rootDir": "../..", "outDir": "dist" }, "include": ["src/**/*.ts"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a8602c35d..393e2d483f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -683,6 +683,9 @@ importers: replicache: specifier: workspace:* version: link:../replicache + rn-bench: + specifier: workspace:* + version: link:../../tools/rn-bench shared: specifier: workspace:* version: link:../shared @@ -1635,6 +1638,9 @@ importers: '@types/tmp': specifier: ^0.2.6 version: 0.2.6 + esbuild: + specifier: 0.27.7 + version: 0.27.7 js-xxhash: specifier: ^4.0.0 version: 4.0.0 @@ -1647,6 +1653,9 @@ importers: oxfmt: specifier: ^0.65.0 version: 0.65.0 + rn-bench: + specifier: workspace:* + version: link:../../tools/rn-bench shared: specifier: workspace:* version: link:../shared @@ -1924,6 +1933,37 @@ importers: specifier: workspace:* version: link:../../packages/shared + tools/rn-bench: + dependencies: + '@types/command-line-args': + specifier: ^5.2.3 + version: 5.2.3 + '@types/command-line-usage': + specifier: ^5.0.4 + version: 5.0.4 + '@types/node': + specifier: ^22.10.5 + version: 22.19.19 + '@types/ws': + specifier: ^8.5.12 + version: 8.18.1 + command-line-args: + specifier: ^6.0.1 + version: 6.0.2 + command-line-usage: + specifier: ^7.0.3 + version: 7.0.4 + typescript: + specifier: ~7.0.2 + version: 7.0.2 + ws: + specifier: ^8.18.1 + version: 8.20.1 + devDependencies: + oxfmt: + specifier: ^0.65.0 + version: 0.65.0 + tools/sqlite-io-yield-simulator: dependencies: '@rocicorp/lock': diff --git a/tools/rn-bench/hermes-eval.mjs b/tools/rn-bench/hermes-eval.mjs new file mode 100644 index 0000000000..3b83a59746 --- /dev/null +++ b/tools/rn-bench/hermes-eval.mjs @@ -0,0 +1,98 @@ +/* oxlint-disable no-console -- a CLI whose output is the point */ +/** + * Evaluates a JavaScript file in a running React Native app's Hermes runtime + * and prints what it returns. + * + * A micro-benchmark loop that needs a rebuild, reinstall and relaunch per + * variant is too slow to think with. This reuses the CDP connection the + * profiler uses, so a candidate implementation can be measured on the real + * engine in seconds. It is how the compare-utf8 first-code-unit probe was + * found to be a pessimization on Hermes (rocicorp/compare-utf8#11): four + * alternatives were measured this way, and the one that looked most promising + * on paper was three times slower. + * + * Needs Metro running and the app open, e.g. + * + * cd packages/zql-benchmarks/rn && npx expo start --port 8082 + * adb reverse tcp:8082 tcp:8082 + * adb shell am start -n dev.rocicorp.zqlperfrn/.MainActivity + * + * then, from this directory (so `ws` resolves): + * + * node hermes-eval.mjs /tmp/probe.js [device-title-substring] + * + * The file is evaluated as a single expression, so wrap it in an IIFE and + * return a string — CDP returns values by value, so keep the result small. + */ +import {readFileSync} from 'node:fs'; +import WebSocket from 'ws'; + +const HOST = '127.0.0.1'; +const PORT = Number(process.env.METRO_PORT ?? 8082); +const deviceMatch = process.argv[3] ?? 'sdk_gphone'; + +const list = await fetch(`http://${HOST}:${PORT}/json/list`).then(r => + r.json(), +); +const target = list.find(t => (t.title ?? '').includes(deviceMatch)); +if (!target) { + throw new Error( + `No CDP target matching ${deviceMatch}. Targets: ${list + .map(t => t.title) + .join(' | ')}`, + ); +} + +// The proxy checks Origin, and rejects `localhost` in favour of its own host. +const ws = new WebSocket( + target.webSocketDebuggerUrl.replace('localhost', HOST), + { + headers: {Origin: `http://${HOST}:${PORT}`}, + perMessageDeflate: false, + }, +); +await new Promise((resolve, reject) => { + ws.once('open', resolve); + ws.once('error', reject); +}); + +let id = 0; +const pending = new Map(); +ws.on('message', data => { + const msg = JSON.parse(String(data)); + if (msg.id && pending.has(msg.id)) { + const {resolve, reject} = pending.get(msg.id); + pending.delete(msg.id); + msg.error + ? reject(new Error(JSON.stringify(msg.error))) + : resolve(msg.result); + } +}); + +function send(method, params = {}) { + const myId = ++id; + ws.send(JSON.stringify({id: myId, method, params})); + return new Promise((resolve, reject) => { + pending.set(myId, {resolve, reject}); + setTimeout(() => { + if (pending.delete(myId)) { + reject(new Error(`${method} timed out`)); + } + }, 120_000); + }); +} + +const result = await send('Runtime.evaluate', { + expression: readFileSync(process.argv[2], 'utf-8'), + returnByValue: true, +}); +if (result.exceptionDetails) { + console.error(JSON.stringify(result.exceptionDetails).slice(0, 800)); + process.exit(1); +} +console.log( + typeof result.result.value === 'string' + ? result.result.value + : JSON.stringify(result.result.value, null, 1), +); +process.exit(0); diff --git a/tools/rn-bench/package.json b/tools/rn-bench/package.json new file mode 100644 index 0000000000..2879eaafec --- /dev/null +++ b/tools/rn-bench/package.json @@ -0,0 +1,29 @@ +{ + "name": "rn-bench", + "version": "0.0.0", + "private": true, + "description": "Shared harness for running benchmarks on React Native devices", + "type": "module", + "scripts": { + "format": "oxfmt .", + "check-format": "oxfmt --check .", + "check-types": "tsc", + "lint": "oxlint --quiet --config ../../oxlint.config.ts src/", + "fmt": "oxfmt .", + "check-fmt": "oxfmt --check ." + }, + "dependencies": { + "@types/command-line-args": "^5.2.3", + "@types/command-line-usage": "^5.0.4", + "@types/node": "^22.10.5", + "@types/ws": "^8.5.12", + "command-line-args": "^6.0.1", + "command-line-usage": "^7.0.3", + "typescript": "~7.0.2", + "ws": "^8.18.1" + }, + "devDependencies": { + "oxfmt": "^0.65.0" + }, + "packageManager": "pnpm@11.11.0" +} diff --git a/packages/replicache-perf/src/bencher-metric-format.ts b/tools/rn-bench/src/bencher-metric-format.ts similarity index 72% rename from packages/replicache-perf/src/bencher-metric-format.ts rename to tools/rn-bench/src/bencher-metric-format.ts index 1b757e4156..2cf8768f3e 100644 --- a/packages/replicache-perf/src/bencher-metric-format.ts +++ b/tools/rn-bench/src/bencher-metric-format.ts @@ -16,7 +16,10 @@ export function toBencherMetricFormat( // https://bencher.dev/docs/reference/bencher-metric-format/#bencher-metric-format-bmf-json-schema return { [result.name]: { - throughput: { + // `latency`, not `throughput`: the value is a time in milliseconds, so + // lower is better. Reported as throughput, Bencher reads the direction + // backwards and every regression shows up as an improvement. + latency: { value: result.runTimesStatistics.meanMs, ['lower_value']: Math.min(...result.sortedRunTimesMs), ['upper_value']: Math.max(...result.sortedRunTimesMs), diff --git a/packages/replicache-perf/src/benchmark.ts b/tools/rn-bench/src/benchmark.ts similarity index 82% rename from packages/replicache-perf/src/benchmark.ts rename to tools/rn-bench/src/benchmark.ts index 2cd9c1c93e..b9764115e7 100644 --- a/packages/replicache-perf/src/benchmark.ts +++ b/tools/rn-bench/src/benchmark.ts @@ -28,6 +28,7 @@ export async function runBenchmark( const minTime = 500; const maxTotalTime = 5000; const times: number[] = []; + /** Total measured time so far; the loop below runs until it hits minTime. */ let sum = 0; if (benchmark.skip && (await benchmark.skip())) { @@ -88,19 +89,25 @@ export async function runBenchmark( } times.sort((a, b) => a - b); - // Remove two slowest. Treat them as JIT warmup. - times.splice(0, 2); + // Remove the two slowest, treating them as JIT warmup. `times` is sorted + // ascending, so that is the tail -- `splice(0, 2)` dropped the two *fastest* + // instead, which can only bias every statistic upward. + times.splice(-2); const calcPercentile = (percentile: number): number => times[Math.floor((runCount * percentile) / 100)]; const runCount = times.length; const medianMs = calcPercentile(50); + // Sum the samples that survived the discard above. `sum` accumulated every + // run, so dividing it by the post-discard `runCount` reported a mean that + // was not the mean of anything -- it came out above p95 on every benchmark. + const retainedSum = times.reduce((a, b) => a + b, 0); return { name: benchmark.name, group: benchmark.group, byteSize: benchmark.byteSize, sortedRunTimesMs: times, runTimesStatistics: { - meanMs: sum / runCount, + meanMs: retainedSum / runCount, medianMs, p75Ms: calcPercentile(75), p90Ms: calcPercentile(90), diff --git a/packages/replicache-perf/src/format.ts b/tools/rn-bench/src/format.ts similarity index 100% rename from packages/replicache-perf/src/format.ts rename to tools/rn-bench/src/format.ts diff --git a/packages/replicache-perf/src/github-action-benchmark.ts b/tools/rn-bench/src/github-action-benchmark.ts similarity index 100% rename from packages/replicache-perf/src/github-action-benchmark.ts rename to tools/rn-bench/src/github-action-benchmark.ts diff --git a/packages/replicache-perf/src/harness.ts b/tools/rn-bench/src/harness.ts similarity index 100% rename from packages/replicache-perf/src/harness.ts rename to tools/rn-bench/src/harness.ts diff --git a/packages/replicache-perf/src/hermes-trace.ts b/tools/rn-bench/src/hermes-trace.ts similarity index 97% rename from packages/replicache-perf/src/hermes-trace.ts rename to tools/rn-bench/src/hermes-trace.ts index 9137d1140e..1500098e55 100644 --- a/packages/replicache-perf/src/hermes-trace.ts +++ b/tools/rn-bench/src/hermes-trace.ts @@ -111,6 +111,8 @@ export async function connectSession( metroPort: number, appId: string, deviceMatch: string, + /** The global the host app publishes its harness on. */ + globalName: string, ): Promise { const url = await targetWebSocketUrl(metroPort, appId, deviceMatch); const ws = new WebSocket(url, { @@ -199,14 +201,14 @@ export async function connectSession( const deadline = Date.now() + 60_000; for (;;) { const t = await evaluate( - 'typeof globalThis.__replicachePerf', + `typeof globalThis.${globalName}`, ).catch(() => 'undefined'); if (t === 'object') { return; } if (Date.now() > deadline) { throw new Error( - 'The app never published globalThis.__replicachePerf. It is set in ' + + `The app never published globalThis.${globalName}. It is set in ` + "the Expo app's App.tsx; make sure Metro rebundled after any edit " + '(a CI=1 Metro does not watch for changes).', ); @@ -244,14 +246,15 @@ export async function runBenchmarkInApp( name: string, group: string, timeoutMs: number, + globalName: string, ): Promise { await session.evaluate(`(globalThis.__perfDone = false, globalThis.__perfOut = null, - __replicachePerf.runBenchmarkByNameAndGroup(${JSON.stringify( + ${globalName}.runBenchmarkByNameAndGroup(${JSON.stringify( name, )}, ${JSON.stringify(group)}) .then(r => { globalThis.__perfOut = r && r[0] === 'result' - ? __replicachePerf.formatAsReplicache(r[1]) + ? ${globalName}.formatAsReplicache(r[1]) : 'ERROR ' + String(r && r[1]); }) .catch(e => { globalThis.__perfOut = 'THREW ' + String((e && e.message) || e); }) .then(() => { globalThis.__perfDone = true; }), 'started')`); diff --git a/tools/rn-bench/src/mitata-shim.ts b/tools/rn-bench/src/mitata-shim.ts new file mode 100644 index 0000000000..d09ee29a27 --- /dev/null +++ b/tools/rn-bench/src/mitata-shim.ts @@ -0,0 +1,151 @@ +/** + * A React Native stand-in for `packages/shared/src/bench.ts`. + * + * That module wraps mitata + Vitest, neither of which runs on Hermes. This + * exposes the same three names — `bench`, `describe`, `use` — on top of the + * {@link Benchmark} shape the device harness understands, so a package's + * existing `.bench.ts` files can be compiled for a device unchanged. The RN + * esbuild build swaps this in with an `onResolve` hook; see + * `zqlBenchPlugin` in zql-benchmarks/tool/build-rn.ts. + * + * ## How a mitata benchmark maps onto a harness benchmark + * + * mitata measures one inner iteration; the harness times a whole `run()` and + * samples that 9-21 times. Each harness rep therefore runs the benchmark body + * `max_samples` times (100 when unspecified) inside the timed window, so a + * reported figure is "N iterations", not "one operation" — fixed per benchmark + * and comparable across runs, but not comparable to mitata's per-op numbers. + * Both the plain and the generator form loop, so every benchmark in a run is + * on the same scale. + * + * mitata's generator form — + * + * ```js + * bench('push', function* () { + * const view = setup(); // once + * yield () => { body(); }; // measured + * teardown(view); // after + * }); + * ``` + * + * — keeps that structure: the setup and teardown run once per harness rep, + * outside the timed window, and only the yielded function is timed. Running + * teardown every rep also bounds the growth that `max_samples` exists to cap + * upstream, which matters far more on a phone than on a desktop. + */ +import type {Benchmark} from './benchmark.ts'; + +/** The subset of mitata's options the benchmarks in this repo actually pass. */ +export type MeasureOptions = { + max_samples?: number | undefined; + min_samples?: number | undefined; + min_cpu_time?: number | undefined; +}; + +type Body = () => unknown; +type MeasureFn = (() => unknown) | (() => Generator); + +/** + * Iterations per harness rep when a benchmark does not say. Small enough that + * a slow benchmark stays responsive, large enough that a microsecond-scale one + * is not swamped by `performance.now()` resolution. + */ +const DEFAULT_ITERATIONS = 100; + +const collected: Benchmark[] = []; +const describeStack: string[] = []; +let currentFile = 'bench'; + +/** Prevents a benchmark body's result being optimised away (mitata's `use`). */ +let sink: unknown; +export function use(value: T): T { + sink = value; + return value; +} +/** Keeps `sink` observably live. */ +export function readSink(): unknown { + return sink; +} + +/** + * Set by the build's `onLoad` hook at the top of each `.bench.ts` module, so + * benchmarks from different files land in different groups even when they use + * the same `describe` names (several files declare a `push` suite). + */ +export function __setBenchFile(name: string): void { + currentFile = name; + describeStack.length = 0; +} + +export function describe(name: string, fn: () => void): void { + describeStack.push(name); + try { + fn(); + } finally { + describeStack.pop(); + } +} + +function isGenerator(v: unknown): v is Generator { + return ( + typeof v === 'object' && + v !== null && + typeof (v as Generator).next === 'function' + ); +} + +export function bench( + name: string, + fn: MeasureFn, + opts?: MeasureOptions, +): void { + const iterations = opts?.max_samples ?? DEFAULT_ITERATIONS; + const fullName = [...describeStack, name].join(' / '); + + collected.push({ + name: fullName, + group: currentFile, + async run(bencher) { + const produced = fn(); + + if (!isGenerator(produced)) { + // Plain function form. The call above was only a probe to tell the two + // forms apart, and a synchronous body has already run to completion by + // the time we get here -- so let it settle outside the timed region and + // then measure a full `iterations` runs. Counting the probe as the + // first iteration instead would leave a synchronous benchmark + // reporting `iterations - 1` executions while the generator form + // reports `iterations`. + await produced; + bencher.reset(); + for (let i = 0; i < iterations; i++) { + await (fn as () => unknown)(); + } + bencher.stop(); + return; + } + + // Generator form: setup ran up to the `yield`; time only the body. + const first = produced.next(); + const body = first.value; + if (typeof body !== 'function') { + throw new Error( + `Benchmark "${fullName}" yielded ${typeof body}, expected a function`, + ); + } + bencher.reset(); + for (let i = 0; i < iterations; i++) { + await body(); + } + bencher.stop(); + // Runs whatever follows the `yield` — typically the cleanup that keeps + // a push benchmark's source from growing without bound. + produced.next(); + }, + }); +} + +/** Every benchmark registered by the `.bench.ts` modules imported so far. */ +export function collectedBenchmarks(): Benchmark[] { + return collected; +} diff --git a/tools/rn-bench/src/runner.ts b/tools/rn-bench/src/runner.ts new file mode 100644 index 0000000000..2a07c3cb6e --- /dev/null +++ b/tools/rn-bench/src/runner.ts @@ -0,0 +1,1060 @@ +/** + * Runs the replicache benchmarks on a React Native device. + * + * The web runner (runner.ts) serves the harness from Vite and drives it with + * Playwright. There is no Playwright for React Native, so the roles are + * inverted: this process serves a small HTTP control API and the Expo app in + * `replicache-perf-rn` pulls one benchmark at a time from it, posts the result + * back, and reloads its JS context between benchmarks the way runner.ts calls + * `page.reload()`. + * + * The only platform-specific parts are booting the device, making the host + * reachable and launching the app; those live behind {@link DeviceDriver}. + */ +import { + execFile as execFileCb, + spawn, + type ChildProcess, +} from 'node:child_process'; +import * as fs from 'node:fs/promises'; +import * as http from 'node:http'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import process from 'node:process'; +import {promisify} from 'node:util'; +import commandLineArgs from 'command-line-args'; +import commandLineUsage from 'command-line-usage'; +import { + type BencherMetricsFormat, + toBencherMetricFormat, +} from './bencher-metric-format.ts'; +import type {BenchmarkResult} from './benchmark.ts'; +import {formatAsBenchmarkJS, formatAsReplicache} from './format.ts'; +import {createGithubActionBenchmarkJSONEntries} from './github-action-benchmark.ts'; +import { + connectSession, + runBenchmarkInApp, + selfTimeByFrame, +} from './hermes-trace.ts'; + +const execFile = promisify(execFileCb); + +const allPlatforms = ['android', 'ios'] as const; +type Platform = (typeof allPlatforms)[number]; + +type Format = 'benchmarkJS' | 'json' | 'replicache' | 'bmf'; + +/** + * Everything a package must supply to run its own benchmarks on a device. The + * rest of this file — device drivers, the control server, Expo launching, + * Hermes profiling — is the same for every package. + */ +export type RnBenchConfig = { + /** Package root. `/out/rn.js` is installed into the app and + * `/tool/build.ts` rebuilds it unminified for profiling. */ + rootDir: string; + /** Name shown in `--help`, e.g. `perf:rn`. */ + cliName: string; + /** + * Script that rebuilds the RN bundle, relative to `rootDir`. Profiling reruns + * it with PERF_RN_NO_MINIFY=1, without which every Hermes frame is a mangled + * one-or-two-letter name. + */ + buildScript?: string | undefined; + /** The global the host app publishes its harness on, e.g. `__zqlPerf`. */ + globalName: string; + /** + * Benchmarks available for `--list` and the queue. Must match what the RN + * bundle registers — importing the built bundle is the surest way to keep the + * two in step. + */ + listBenchmarks: () => + | {name: string; group: string}[] + | Promise<{name: string; group: string}[]>; + /** + * An optional second axis to run every benchmark across — Replicache uses it + * for its key/value backends. Omitted for a single-variant suite. + */ + variants?: + | { + /** CLI flag name, e.g. `backend`. */ + flag: string; + values: readonly string[]; + describe: string; + } + | undefined; + /** Extra fields merged into each `/next` payload. */ + nextExtras?: ((controlPort: number) => Record) | undefined; + /** Extra routes on the control server, e.g. a large fixture to stream. */ + extraRoutes?: + | Record< + string, + (req: http.IncomingMessage, res: http.ServerResponse) => void + > + | undefined; + /** Expression evaluated in the app to configure it before a profiled run. */ + configureExpr?: + | ((variant: string | undefined, controlPort: number) => string) + | undefined; +}; + +/** Metro's default 8081 is often taken (Docker), so default one above it. */ +const DEFAULT_METRO_PORT = 8082; +const DEFAULT_CONTROL_PORT = 9099; +/** The Expo host app, checked in beside the harness it runs. */ + +class UnknownValueError extends Error { + name = 'UNKNOWN_VALUE'; + value: string; + optionName: string; + + constructor(arg: string, optionName: string) { + super(`Unknown value ${arg}`); + this.value = arg; + this.optionName = '--' + optionName; + } +} + +function platform(arg: string): Platform | 'all' { + arg = arg.toLowerCase(); + if (!['all', ...allPlatforms].includes(arg)) { + throw new UnknownValueError(arg, 'platform'); + } + return arg as Platform | 'all'; +} + +function format(arg: string): Format { + if (!['benchmarkJS', 'json', 'replicache', 'bmf'].includes(arg)) { + throw new UnknownValueError(arg, 'format'); + } + return arg as Format; +} + +// --------------------------------------------------------------------------- +// Device drivers +// --------------------------------------------------------------------------- + +type DeviceDriver = { + readonly platform: Platform; + /** Boots a device if none is running; resolves to its name. */ + ensureBooted(requested: string | undefined): Promise; + /** Makes host ports reachable from the device. */ + forwardPorts(ports: number[]): Promise; + /** Args for `npx`, launching the app and Metro. */ + expoArgs(device: string, metroPort: number, release: boolean): string[]; + /** + * Retried until the app first contacts the control server. Exists because + * `expo run:ios` points the dev client at Metro with a deep link, and iOS + * puts a "Open in ...?" confirmation in front of that whenever some other app + * happens to be frontmost — which silently strands the run. + */ + nudge?(appDir: string, metroPort: number): Promise; +}; + +function adbPath(): string { + const home = + process.env.ANDROID_HOME ?? + process.env.ANDROID_SDK_ROOT ?? + path.join(os.homedir(), 'Library/Android/sdk'); + return path.join(home, 'platform-tools', 'adb'); +} + +const androidDriver: DeviceDriver = { + platform: 'android', + + async ensureBooted(requested) { + const avd = requested ?? 'medium_phone'; + const {stdout} = await execFile(adbPath(), ['devices']); + const booted = stdout + .split('\n') + .slice(1) + .filter(l => l.trim().endsWith('device')) + .map(l => l.split('\t')[0]); + if (booted.length === 0) { + throw new Error( + `No Android device. Start the emulator first:\n` + + ` $ANDROID_HOME/emulator/emulator -avd ${avd} &`, + ); + } + return avd; + }, + + async forwardPorts(ports) { + // The emulator's own localhost is not the host's, so every port the app + // talks to has to be reversed explicitly. + for (const p of ports) { + await execFile(adbPath(), ['reverse', `tcp:${p}`, `tcp:${p}`]); + } + }, + + expoArgs(device, metroPort, release) { + return [ + 'expo', + 'run:android', + '--device', + device, + '--port', + String(metroPort), + ...(release ? ['--variant', 'release'] : []), + ]; + }, +}; + +const iosDriver: DeviceDriver = { + platform: 'ios', + + async ensureBooted(requested) { + const {stdout} = await execFile('xcrun', [ + 'simctl', + 'list', + 'devices', + 'booted', + '-j', + ]); + const {devices} = JSON.parse(stdout) as { + devices: Record; + }; + const booted = Object.values(devices).flat(); + if (booted.length > 0) { + const match = requested + ? booted.find(d => d.name === requested) + : booted[0]; + if (match) { + return match.name; + } + } + const name = requested ?? 'iPhone 17 Pro'; + await execFile('xcrun', ['simctl', 'boot', name]); + await execFile('open', ['-a', 'Simulator']); + return name; + }, + + // The iOS simulator shares the host's network stack, so localhost already + // reaches us. Nothing to do. + forwardPorts: () => Promise.resolve(), + + expoArgs(device, metroPort, release) { + return [ + 'expo', + 'run:ios', + '--device', + device, + '--port', + String(metroPort), + ...(release ? ['--configuration', 'Release'] : []), + ]; + }, + + async nudge(appDir, metroPort) { + const appJson = JSON.parse( + await fs.readFile(path.join(appDir, 'app.json'), 'utf-8'), + ) as {expo: {ios: {bundleIdentifier: string}}}; + const id = appJson.expo.ios.bundleIdentifier; + // Foregrounding the app first means the deep link is delivered to the app + // that is already frontmost, which iOS does not gate behind a prompt. + await execFile('xcrun', ['simctl', 'launch', 'booted', id]); + await execFile('xcrun', [ + 'simctl', + 'openurl', + 'booted', + `${id}://expo-development-client/?url=${encodeURIComponent( + `http://localhost:${metroPort}`, + )}`, + ]); + }, +}; + +function driverFor(p: Platform): DeviceDriver { + return p === 'android' ? androidDriver : iosDriver; +} + +// --------------------------------------------------------------------------- +// Control server +// --------------------------------------------------------------------------- + +type QueueItem = {variant: string | undefined; name: string; group: string}; + +type Outcome = + | {item: QueueItem; result: BenchmarkResult} + | {item: QueueItem; error: string}; + +type ControlServer = { + readonly port: number; + /** Resolves when every queued benchmark has reported, rejects on error. */ + readonly done: Promise; + /** Resolves the first time the app talks to us. */ + readonly firstContact: Promise; + close(): Promise; +}; + +function startControlServer( + config: RnBenchConfig, + queue: QueueItem[], + port: number, + idleTimeoutMs: number, + onOutcome: (outcome: Outcome, index: number, total: number) => void, +): Promise { + const outcomes: Outcome[] = []; + let index = 0; + let settle!: (o: Outcome[]) => void; + let fail!: (e: unknown) => void; + const done = new Promise((res, rej) => { + settle = res; + fail = rej; + }); + + let noteContact!: () => void; + const firstContact = new Promise(res => { + noteContact = res; + }); + + let lastActivity = Date.now(); + const watchdog = setInterval(() => { + if (Date.now() - lastActivity > idleTimeoutMs) { + clearInterval(watchdog); + fail( + new Error( + `The app went quiet for ${Math.round(idleTimeoutMs / 1000)}s ` + + `(${index}/${queue.length} benchmarks done). ` + + `Re-run with --verbose to see the Expo/Metro output.`, + ), + ); + } + }, 5_000); + + const json = (res: http.ServerResponse, body: unknown) => { + const s = JSON.stringify(body); + res.writeHead(200, { + 'content-type': 'application/json', + 'content-length': Buffer.byteLength(s), + }); + res.end(s); + }; + + const server = http.createServer((req, res) => { + lastActivity = Date.now(); + noteContact(); + const url = new URL(req.url ?? '/', `http://localhost:${port}`); + + switch (url.pathname) { + case '/ping': + json(res, {ok: true}); + return; + + case '/next': { + const item = queue[index]; + if (!item) { + json(res, {done: true}); + clearInterval(watchdog); + settle(outcomes); + return; + } + const {variant, ...rest} = item; + json(res, { + done: false, + ...rest, + // The variant travels under the config's own flag name, so an app + // reading `next.backend` keeps working. + ...(config.variants && variant !== undefined + ? {[config.variants.flag]: variant} + : {}), + ...(config.nextExtras?.(port) ?? {}), + index, + total: queue.length, + }); + return; + } + + case '/result': { + const chunks: Buffer[] = []; + req.on('data', c => chunks.push(c as Buffer)); + req.on('end', () => { + lastActivity = Date.now(); + const item = queue[index]; + if (!item) { + // A late or duplicate post, e.g. the app reloaded after we had + // already recorded its result. Nothing left to attribute it to. + res.writeHead(409, {'content-type': 'application/json'}); + res.end(JSON.stringify({error: 'no benchmark in flight'})); + return; + } + let body: {result: BenchmarkResult} | {error: string}; + try { + body = JSON.parse(Buffer.concat(chunks).toString()); + } catch (e) { + // Record it as a failed benchmark rather than taking the runner + // down with it, so the rest of the queue still runs. + body = {error: `malformed /result body: ${String(e)}`}; + } + const outcome: Outcome = + 'result' in body + ? {item, result: body.result} + : {item, error: body.error}; + outcomes.push(outcome); + onOutcome(outcome, index, queue.length); + index++; + json(res, {ok: true}); + }); + return; + } + + default: { + const extra = config.extraRoutes?.[url.pathname]; + if (extra) { + extra(req, res); + return; + } + res.writeHead(404); + res.end(); + } + } + }); + + return new Promise((resolve, reject) => { + server.on('error', reject); + server.listen(port, () => + resolve({ + port, + done, + firstContact, + close: () => + new Promise(res => { + clearInterval(watchdog); + server.close(() => res()); + }), + }), + ); + }); +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +function makeOptionDefinitions(config: RnBenchConfig) { + const {variants} = config; + return [ + { + name: 'platform', + type: platform, + defaultValue: 'android', + description: `Device platform: ${allPlatforms.join(', ')}, or all`, + }, + ...(variants + ? [ + { + name: variants.flag, + type: (arg: string) => { + const a = arg.toLowerCase(); + if (!['all', ...variants.values].includes(a)) { + throw new UnknownValueError(a, variants.flag); + } + return a; + }, + multiple: true, + defaultValue: [variants.values[0]], + description: `${variants.describe}: ${variants.values.join(', ')}, or all`, + }, + ] + : []), + { + name: 'device', + type: String, + description: + 'AVD name (android, default medium_phone) or simulator name (ios, default the booted one)', + }, + { + name: 'run', + type: RegExp, + description: 'Run only those benchmarks matching the regular expression.', + }, + { + name: 'format', + alias: 'f', + type: format, + defaultValue: 'benchmarkJS', + description: + 'Format for text output, either benchmarkJS (default), json, replicache or bmf (Bencher Metrics Format)', + }, + { + name: 'list', + alias: 'l', + type: Boolean, + description: 'List available benchmarks', + }, + { + name: 'app', + type: String, + defaultValue: path.join(config.rootDir, 'rn'), + description: + 'Path to the Expo host app (default: the rn/ directory beside this package)', + }, + { + name: 'port', + type: Number, + defaultValue: DEFAULT_CONTROL_PORT, + description: `Control server port (default ${DEFAULT_CONTROL_PORT})`, + }, + { + name: 'metro-port', + type: Number, + defaultValue: DEFAULT_METRO_PORT, + description: `Metro bundler port (default ${DEFAULT_METRO_PORT})`, + }, + { + name: 'idle-timeout', + type: Number, + defaultValue: 900, + description: + 'Seconds without a request from the app before giving up (default 900; the first native build is slow)', + }, + { + name: 'repeat', + type: Number, + defaultValue: 1, + description: + 'Run the whole queue N times and report the median per benchmark plus the spread. A single run on an emulator is worth about +/-3%, so a smaller difference than that cannot be resolved without this.', + }, + { + name: 'release', + type: Boolean, + defaultValue: false, + description: + 'Build and run the release variant (__DEV__ off, JS minified and precompiled to bytecode, no debugger). Note the app then has no DevSettings.reload, so benchmarks share one JS context, and --profile is unavailable because release builds ship no inspector.', + }, + { + name: 'profile', + type: String, + description: + 'Capture a Hermes CPU profile of one benchmark and write the Chrome trace to this path. Drives the app directly over CDP (no control server), rebuilds the bundle unminified for readable frames, and reports self time windowed to the region the bencher actually times. Requires a single platform/backend/benchmark.', + }, + { + name: 'verbose', + alias: 'v', + type: Boolean, + defaultValue: false, + description: 'Stream the Expo/Metro output', + }, + { + name: 'help', + alias: 'h', + type: Boolean, + description: 'Show this help message', + }, + ]; +} + +type Options = { + 'platform': Platform | 'all'; + /** Present only when the config declares a variant axis. */ + [variantFlag: string]: unknown; + 'device'?: string | undefined; + 'run'?: RegExp | undefined; + 'format': Format; + 'list'?: boolean | undefined; + 'app': string; + 'port': number; + 'metro-port': number; + 'idle-timeout': number; + 'repeat': number; + 'release'?: boolean | undefined; + 'profile'?: string | undefined; + 'verbose': boolean; + 'help'?: boolean | undefined; +}; + +function logLine(s: string, options: Options) { + if (options.format !== 'json' && options.format !== 'bmf') { + process.stdout.write(s + '\n'); + } +} + +/** + * The app is a plain Expo project outside the workspace; it consumes the bundle + * as an ordinary local file, so Metro never sees monorepo TypeScript. The + * sourcemap comes along (renamed) so on-device stack traces stay readable. + */ +async function appIdFor(appDir: string, p: Platform): Promise { + const {expo} = JSON.parse( + await fs.readFile(path.join(appDir, 'app.json'), 'utf-8'), + ) as {expo: {ios: {bundleIdentifier: string}; android: {package: string}}}; + return p === 'ios' ? expo.ios.bundleIdentifier : expo.android.package; +} + +async function installBundle(appDir: string, rootDir: string): Promise { + const js = await fs.readFile(path.join(rootDir, 'out', 'rn.js'), 'utf-8'); + await fs.writeFile( + path.join(appDir, 'benchmarks.js'), + js.replace( + '//# sourceMappingURL=rn.js.map', + '//# sourceMappingURL=benchmarks.js.map', + ), + ); + await fs.copyFile( + path.join(rootDir, 'out', 'rn.js.map'), + path.join(appDir, 'benchmarks.js.map'), + ); + await fs.copyFile( + path.join(rootDir, 'out', 'rn.d.ts'), + path.join(appDir, 'benchmarks.d.ts'), + ); +} + +async function nudgeUntilContact( + driver: DeviceDriver, + appDir: string, + metroPort: number, + firstContact: Promise, +): Promise { + let contacted = false; + void firstContact.then(() => { + contacted = true; + }); + while (!contacted) { + await new Promise(r => setTimeout(r, 15_000)); + if (contacted) { + return; + } + // Expected to fail while the native build is still running. + await driver.nudge?.(appDir, metroPort).catch(() => {}); + } +} + +/** Builds, installs and launches the app, with Metro, in its own process group. */ +function spawnExpo( + driver: DeviceDriver, + device: string, + metroPort: number, + options: Options, +): ChildProcess { + return spawn( + 'npx', + driver.expoArgs(device, metroPort, options.release ?? false), + { + cwd: options.app, + env: { + ...process.env, + CI: '1', + EXPO_PUBLIC_PERF_PORT: String(options.port), + // CocoaPods refuses to run without a UTF-8 locale, and a non-login shell + // often has none. + LANG: process.env.LANG ?? 'en_US.UTF-8', + }, + stdio: options.verbose ? 'inherit' : 'ignore', + // Its own process group, so killing it takes Metro and gradle down too + // rather than leaving Metro holding the port. + detached: true, + }, + ); +} + +function killGroup(child: ChildProcess | undefined): void { + if (child?.pid === undefined) { + return; + } + try { + process.kill(-child.pid, 'SIGTERM'); + } catch { + // Already gone. + } +} + +/** + * Profiling mode. Unlike a normal run this does not use the control server at + * all: the app sits idle in manual mode and we drive one benchmark straight + * through `Runtime.evaluate`, so nothing but the benchmark runs inside the + * traced window. Requires the Expo app to publish the configured global (see + * its App.tsx). + */ +async function profileOnDevice( + config: RnBenchConfig, + p: Platform, + item: QueueItem, + device: string, + metroPort: number, + options: Options, +): Promise { + const session = await connectSession( + metroPort, + await appIdFor(options.app, p), + p === 'ios' ? device : 'sdk_gphone', + config.globalName, + ); + try { + await session.waitForHarness(); + const configureExpr = config.configureExpr?.(item.variant, options.port); + if (configureExpr) { + await session.evaluate(configureExpr); + } + + await session.startTracing(); + const result = await runBenchmarkInApp( + session, + item.name, + item.group, + options['idle-timeout'] * 1000, + config.globalName, + ); + const events = await session.stopTracing(); + + logLine(`${item.variant ? item.variant + ' ' : ''}${result}`, options); + + await fs.writeFile(options.profile!, JSON.stringify(events)); + const {rows, inWindowUs, excludedUs, windows} = selfTimeByFrame( + events, + item.name, + ); + logLine( + `\nHermes CPU profile (${p}): ${windows} measured windows, ` + + `${(inWindowUs / 1000).toFixed(0)} ms in-window, ` + + `${(excludedUs / 1000).toFixed(0)} ms of setup/teardown excluded`, + options, + ); + for (const r of rows.slice(0, 25)) { + logLine( + `${r.pct.toFixed(1).padStart(6)}% ${(r.us / 1000) + .toFixed(1) + .padStart(8)} ms ${r.frame}`, + options, + ); + } + logLine(`\nfull trace written to ${options.profile}`, options); + } finally { + session.close(); + } +} + +/** + * Collapses N runs of the same queue into one outcome per benchmark, taking the + * median run so a single unlucky one cannot dominate, and reporting the spread + * so it is obvious when a difference is smaller than the noise. + * + * An error in any repeat is kept: a benchmark that fails intermittently is a + * real result, not something to average away. + */ +function summarizeRepeats(repeats: Outcome[][], options: Options): Outcome[] { + const byKey = new Map(); + for (const run of repeats) { + for (const o of run) { + const key = `${o.item.variant ?? ''}\u0000${o.item.group}\u0000${o.item.name}`; + (byKey.get(key) ?? byKey.set(key, []).get(key)!).push(o); + } + } + + const summarized: Outcome[] = []; + for (const group of byKey.values()) { + const failure = group.find(o => 'error' in o); + if (failure) { + summarized.push(failure); + continue; + } + const ok = group as {item: QueueItem; result: BenchmarkResult}[]; + const sorted = ok.toSorted( + (a, b) => + a.result.runTimesStatistics.medianMs - + b.result.runTimesStatistics.medianMs, + ); + const median = sorted[sorted.length >> 1]; + const lo = sorted[0].result.runTimesStatistics.medianMs; + const hi = sorted.at(-1)!.result.runTimesStatistics.medianMs; + const mid = median.result.runTimesStatistics.medianMs; + logLine( + ` ${median.item.name}: median ${mid.toFixed(2)} ms over ${ + ok.length + } runs, spread ${lo.toFixed(2)}-${hi.toFixed(2)} ms (${( + (100 * (hi - lo)) / + (mid || 1) + ).toFixed(1)}%)`, + options, + ); + summarized.push(median); + } + return summarized; +} + +async function runPlatform( + config: RnBenchConfig, + p: Platform, + queue: QueueItem[], + options: Options, +): Promise { + const driver = driverFor(p); + const metroPort = options['metro-port']; + + const device = await driver.ensureBooted(options.device); + logLine(`Running ${queue.length} benchmarks on ${p} (${device})...`, options); + + await driver.forwardPorts([options.port, metroPort]); + + if (options.profile) { + // An empty queue: the app's first /next gets `done`, so it settles into + // idle instead of running anything, while any extra routes (a large + // fixture, say) stay served. + const idle = await startControlServer( + config, + [], + options.port, + options['idle-timeout'] * 1000, + () => {}, + ); + let expo: ChildProcess | undefined; + try { + expo = spawnExpo(driver, device, metroPort, options); + await profileOnDevice(config, p, queue[0], device, metroPort, options); + return []; + } finally { + killGroup(expo); + await idle.close(); + } + } + + const server = await startControlServer( + config, + queue, + options.port, + options['idle-timeout'] * 1000, + (outcome, index, total) => { + if ('error' in outcome) { + return; + } + const label = `[${index + 1}/${total}]${ + outcome.item.variant ? ' ' + outcome.item.variant : '' + }`; + switch (options.format) { + case 'replicache': + logLine(`${label} ${formatAsReplicache(outcome.result)}`, options); + break; + case 'benchmarkJS': + logLine(`${label} ${formatAsBenchmarkJS(outcome.result)}`, options); + break; + default: + if (options.verbose) { + logLine(`${label} ${outcome.item.name}`, options); + } + } + }, + ); + + let expo: ChildProcess | undefined; + try { + expo = spawnExpo(driver, device, metroPort, options); + const expoExited = new Promise((_, reject) => { + expo?.on('exit', code => { + // Metro staying up is normal; only a non-zero exit is a failure, and + // only before the run finished. + if (code !== 0 && code !== null) { + reject( + new Error( + `expo run:${p} exited with code ${code}. Re-run with --verbose.`, + ), + ); + } + }); + expo?.on('error', reject); + }); + + if (driver.nudge) { + void nudgeUntilContact( + driver, + options.app, + metroPort, + server.firstContact, + ); + } + + return await Promise.race([server.done, expoExited]); + } finally { + killGroup(expo); + await server.close(); + } +} + +/** + * Runs a package's benchmarks on a device. The package supplies its benchmark + * list, its Expo app and the global its harness publishes; everything else — + * device drivers, the control server, Expo launching, Hermes profiling — is + * shared. + */ +export async function runRnBench(config: RnBenchConfig): Promise { + const optionDefinitions = makeOptionDefinitions(config); + const options = commandLineArgs(optionDefinitions) as Options; + + if (options.help) { + // oxlint-disable-next-line no-console + console.log( + commandLineUsage([ + {content: `Usage: ${config.cliName} [options...]`}, + {optionList: optionDefinitions}, + ]), + ); + process.exit(); + } + + let names = await config.listBenchmarks(); + if (options.run !== undefined) { + names = names.filter(({name}) => options.run!.test(name)); + } + + if (options.list) { + // oxlint-disable-next-line no-console + console.log( + 'Available benchmarks (group / name):\n' + + names + .map(({name, group}) => `${group} / ${name}`) + .sort() + .join('\n'), + ); + return; + } + + if (names.length === 0) { + // oxlint-disable-next-line no-console + console.error('No benchmarks matched --run.'); + process.exit(1); + } + + const {variants} = config; + const selected = variants + ? ((options[variants.flag] as string[] | undefined) ?? [variants.values[0]]) + : []; + const variantValues: (string | undefined)[] = variants + ? selected.includes('all') + ? [...variants.values] + : selected + : [undefined]; + const platforms: Platform[] = + options.platform === 'all' ? [...allPlatforms] : [options.platform]; + + // Always rebuild. Measuring a stale bundle silently reports the previous + // revision's numbers, which looks exactly like "the change did nothing" -- + // and a null result is the one outcome nobody re-checks. + logLine( + options.profile + ? // Hermes frames are just `Ys`/`ie`/`dr` against the shipped bundle, so + // rebuild without minification before installing it. + 'Rebuilding the RN bundle unminified for readable frames...' + : 'Rebuilding the RN bundle...', + options, + ); + await new Promise((resolve, reject) => { + const build = spawn( + 'node', + [path.join(config.rootDir, config.buildScript ?? 'tool/build.ts')], + { + cwd: config.rootDir, + env: options.profile + ? {...process.env, PERF_RN_NO_MINIFY: '1'} + : process.env, + stdio: options.verbose ? 'inherit' : 'ignore', + }, + ); + build.on('exit', code => + code === 0 ? resolve() : reject(new Error(`build exited ${code}`)), + ); + build.on('error', reject); + }); + + await installBundle(options.app, config.rootDir); + + const queue: QueueItem[] = variantValues.flatMap(variant => + names.map(({name, group}) => ({variant, name, group})), + ); + + if (options.release && options.port !== DEFAULT_CONTROL_PORT) { + // Release bundles are built by gradle/Xcode, which do not inherit this + // process's environment, so EXPO_PUBLIC_PERF_PORT never reaches the app and + // it falls back to the default port. + // oxlint-disable-next-line no-console + console.error( + `--release cannot use a custom --port (the app is stuck on ${DEFAULT_CONTROL_PORT}).`, + ); + process.exit(1); + } + + if (options.profile && options.repeat > 1) { + // oxlint-disable-next-line no-console + console.error( + '--profile captures one trace; --repeat has nothing to average.', + ); + process.exit(1); + } + + if (options.profile && options.release) { + // oxlint-disable-next-line no-console + console.error( + '--profile needs the debug variant: release builds ship no CDP inspector.', + ); + process.exit(1); + } + + if (options.profile && (queue.length !== 1 || platforms.length !== 1)) { + // oxlint-disable-next-line no-console + console.error( + `--profile needs exactly one benchmark on one platform` + + (variants ? ` with one --${variants.flag}` : '') + + `; got ${queue.length} benchmark(s) on ${platforms.length} platform(s).`, + ); + process.exit(1); + } + + const jsonEntries: unknown[] = []; + let bmf: BencherMetricsFormat = {}; + let failed = false; + let first = true; + + for (const p of platforms) { + if (!first) { + logLine('', options); + } + first = false; + + const repeats: Outcome[][] = []; + for (let rep = 0; rep < options.repeat; rep++) { + if (options.repeat > 1) { + logLine(`\n-- repeat ${rep + 1}/${options.repeat} --`, options); + } + repeats.push(await runPlatform(config, p, [...queue], options)); + } + const outcomes = + options.repeat > 1 ? summarizeRepeats(repeats, options) : repeats[0]; + + for (const outcome of outcomes) { + if ('error' in outcome) { + failed = true; + process.stderr.write( + `${p}${outcome.item.variant ? ' / ' + outcome.item.variant : ''} / ` + + `${outcome.item.name}: ${outcome.error}\n`, + ); + continue; + } + // Keep platform and variant in the machine-readable names so a matrix run + // does not collapse into indistinguishable keys. + const result = { + ...outcome.result, + name: `${p} ${ + outcome.item.variant ? outcome.item.variant + ' ' : '' + }${outcome.result.name}`, + }; + switch (options.format) { + case 'json': + jsonEntries.push(...createGithubActionBenchmarkJSONEntries(result)); + break; + case 'bmf': + bmf = {...bmf, ...toBencherMetricFormat(result)}; + break; + } + } + } + + if (options.format === 'json') { + process.stdout.write(JSON.stringify(jsonEntries, null, 2) + '\n'); + } else if (options.format === 'bmf') { + process.stdout.write(JSON.stringify(bmf, null, 2) + '\n'); + } else { + logLine('Done!', options); + } + + if (failed) { + process.exit(1); + } +} diff --git a/tools/rn-bench/tsconfig.json b/tools/rn-bench/tsconfig.json new file mode 100644 index 0000000000..a5cb75c562 --- /dev/null +++ b/tools/rn-bench/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts"] +}