From a0ce1cdadf95176a27e712c4933b21288a23761b Mon Sep 17 00:00:00 2001 From: Tunglies Date: Sat, 14 Nov 2026 06:11:35 +0000 Subject: [PATCH] chore(perf): extract replay tooling into a standalone harness (#8645) * chore(perf): use the external measurement harness Keep real Traffic, Logs and Connections scenes and the isolated native entry beside product APIs. Invoke the standalone runner with an explicit project path and remove bundled measurement orchestration. Support the optional owned virtual display and read-only isolation snapshots without changing ordinary product startup or networking. * docs: scope agent guidance to the active task Keep behavior and ownership guidance with the relevant task routes, including the external performance adapter workflow. Preserve focused self-review for routine changes and independent review where required. * docs(perf): link the standalone harness repository Link the harness repository and operating guide, and document first-time checkout setup while preserving explicit paths for routine invocation. * fix(perf): account for external harness dependencies in Knip * chore(perf): keep tool dependencies and guidance outside the product * docs(perf): keep product guidance focused on the adapter --- AGENTS.md | 2 + knip.json | 2 +- package.json | 11 +- pnpm-lock.yaml | 1732 +--------------------------------- renovate.json | 7 +- scripts/perf/AGENTS.md | 28 + scripts/perf/GUIDE.md | 159 +--- scripts/perf/adapter.ts | 57 ++ scripts/perf/app.rs | 248 ++++- scripts/perf/build.mjs | 107 --- scripts/perf/compare.mjs | 41 - scripts/perf/entry.tsx | 75 +- scripts/perf/isolation.json | 13 + scripts/perf/minimal.ts | 21 + scripts/perf/replay.ts | 37 + scripts/perf/report.mjs | 443 --------- scripts/perf/report.test.mjs | 340 ------- scripts/perf/run.mjs | 674 ------------- scripts/perf/sample.c | 20 - scripts/perf/scenes.tsx | 81 ++ scripts/perf/traffic.tsx | 52 + scripts/perf/tsconfig.json | 2 +- 22 files changed, 580 insertions(+), 3572 deletions(-) create mode 100644 scripts/perf/AGENTS.md create mode 100644 scripts/perf/adapter.ts delete mode 100644 scripts/perf/build.mjs delete mode 100644 scripts/perf/compare.mjs create mode 100644 scripts/perf/isolation.json create mode 100644 scripts/perf/minimal.ts create mode 100644 scripts/perf/replay.ts delete mode 100644 scripts/perf/report.mjs delete mode 100644 scripts/perf/report.test.mjs delete mode 100644 scripts/perf/run.mjs delete mode 100644 scripts/perf/sample.c create mode 100644 scripts/perf/scenes.tsx create mode 100644 scripts/perf/traffic.tsx diff --git a/AGENTS.md b/AGENTS.md index 55bb606b0..6211b6c63 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,6 +68,8 @@ the ownership evidence the AI-slop review evaluates (see - For code review and verification of review fixes, follow [REVIEW.md](REVIEW.md). Review is read-only unless changes are authorized; findings do not expand that authorization to unrelated fixes, commits, pushes, or GitHub updates. +- For macOS frontend performance or WebView memory investigations, follow + [scripts/perf/AGENTS.md](scripts/perf/AGENTS.md). - When handing work between agents or review rounds, use the [AI context sharing guidance](REVIEW.md#ai-context-sharing). - Read the relevant sections and linked module rules, expanding only when the diff --git a/knip.json b/knip.json index 6ea552114..d01f3cb74 100644 --- a/knip.json +++ b/knip.json @@ -14,7 +14,7 @@ "duplicates", "cycles" ], - "ignoreBinaries": ["rustc", "cc"], + "ignoreBinaries": ["rustc"], "ignoreDependencies": ["lint-staged"], "ignoreIssues": { "src/types/generated/**": ["exports", "types"], diff --git a/package.json b/package.json index f9a080068..fa53db6e5 100644 --- a/package.json +++ b/package.json @@ -36,11 +36,7 @@ "test": "vitest run", "test:watch": "vitest", "knip:check": "knip", - "knip:report": "knip --no-exit-code", - "perf:build": "node scripts/perf/build.mjs", - "perf:run": "node scripts/perf/run.mjs", - "perf:compare": "node scripts/perf/compare.mjs", - "perf:test": "node --test scripts/perf/report.test.mjs" + "knip:report": "knip --no-exit-code" }, "dependencies": { "@dnd-kit/collision": "^0.5.0", @@ -102,7 +98,6 @@ "@types/validator": "^13.15.10", "@vitejs/plugin-legacy": "^8.2.3", "@vitejs/plugin-react": "^6.1.1", - "@wdio/tauri-service": "1.5.1", "adm-zip": "^0.6.1", "axios": "^1.20.0", "cli-color": "^2.0.4", @@ -131,9 +126,7 @@ "typescript-eslint": "^8.70.0", "vite": "^8.3.0", "vite-plugin-svgr": "^5.2.0", - "vitest": "^5.0.1", - "webdriverio": "10.2.0", - "ws": "8.22.2" + "vitest": "^5.0.1" }, "lint-staged": { "*.{ts,tsx,js,mjs}": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac6c4d6b8..10c182e43 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -338,9 +338,6 @@ importers: '@vitejs/plugin-react': specifier: ^6.1.1 version: 6.1.1(vite@8.3.3(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) - '@wdio/tauri-service': - specifier: 1.5.1 - version: 1.5.1(expect-webdriverio@6.1.0)(supports-color@7.2.0)(webdriverio@10.2.0(supports-color@7.2.0)(yauzl@2.10.0)) adm-zip: specifier: ^0.6.1 version: 0.6.1 @@ -428,12 +425,6 @@ importers: vitest: specifier: ^5.0.1 version: 5.0.1(@types/node@26.6.5)(vite@8.3.3(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1)) - webdriverio: - specifier: 10.2.0 - version: 10.2.0(supports-color@7.2.0)(yauzl@2.10.0) - ws: - specifier: 8.22.2 - version: 8.22.2 packages: @@ -1381,33 +1372,6 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} - '@isaacs/nonrecursive-glob-match@1.1.0': - resolution: {integrity: sha512-GPm/Dbvk5RLmhRnxXIa7nTpK0xtl4E0wj0MslsuNiJ1FIROU+2+9lgUC/dgF7Ey4QCWhfEsgxilMRMCdHPfDfw==} - - '@jest/diff-sequences@30.5.0': - resolution: {integrity: sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/expect-utils@30.5.2': - resolution: {integrity: sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/get-type@30.5.0': - resolution: {integrity: sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/pattern@30.5.0': - resolution: {integrity: sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/schemas@30.5.0': - resolution: {integrity: sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/types@30.5.1': - resolution: {integrity: sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1911,22 +1875,6 @@ packages: '@preact/signals-core@1.14.4': resolution: {integrity: sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA==} - '@promptbook/utils@0.69.5': - resolution: {integrity: sha512-xm5Ti/Hp3o4xHrsK9Yy3MS6KbDxYbq485hDsFvxqaNA7equHLPdo8H8faTitTeb14QCDfLW4iwCxdVYu5sn6YQ==} - - '@puppeteer/browsers@3.2.3': - resolution: {integrity: sha512-2Bt3m6dDAJqmZehn0wiXSYyQmuhyiHHZ7FjvrVaS1W2RoFGV6eCK71UvKYkNJjkwC1Q9nobjhvTMhLQztFVINA==} - engines: {node: '>=22.12.0'} - hasBin: true - peerDependencies: - proxy-agent: '>=8.0.1' - yauzl: ^2.10.0 || ^3.4.0 - peerDependenciesMeta: - proxy-agent: - optional: true - yauzl: - optional: true - '@remix-run/route-pattern@0.22.1': resolution: {integrity: sha512-czdGJWh09Lapvcqt7Vb09KlrU2PhRJ8xQaI+AItTuD9aDJ5MAgxnS38lnys6Ll+6RJEqPiaUqTwIQhVLwFvv+w==} @@ -2038,9 +1986,6 @@ packages: rollup: optional: true - '@sinclair/typebox@0.34.52': - resolution: {integrity: sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==} - '@svgr/babel-plugin-add-jsx-attribute@8.0.0': resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} engines: {node: '>=14'} @@ -2285,15 +2230,6 @@ packages: '@types/hast@3.0.5': resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+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/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -2309,12 +2245,6 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@20.19.43': - resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} - - '@types/node@22.20.5': - resolution: {integrity: sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==} - '@types/node@26.6.5': resolution: {integrity: sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==} @@ -2337,15 +2267,6 @@ packages: '@types/react@19.3.0': resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} - '@types/sinonjs__fake-timers@15.0.1': - resolution: {integrity: sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==} - - '@types/split2@4.2.3': - resolution: {integrity: sha512-59OXIlfUsi2k++H6CHgUQKEb2HKRokUA39HY1i1dS8/AIcqVjtAAFdf8u+HxTWK/4FUHMJQlKSZ4I6irCBJ1Zw==} - - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -2358,15 +2279,6 @@ packages: '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@17.0.35': - resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.70.0': resolution: {integrity: sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2583,103 +2495,12 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.11': - resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} - - '@vitest/snapshot@4.1.11': - resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} - '@vitest/spy@5.0.1': resolution: {integrity: sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==} - '@vitest/utils@4.1.11': - resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} - '@vscode/l10n@0.0.18': resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} - '@wdio/config@10.2.0': - resolution: {integrity: sha512-dCsMVpgmyL8fLRQVcxVyYXBG/pgtJeY897+LUxU/Vw+JvJOV2iTycHn5xUD5EehbNsstfxgJwkLCVunhJcbXdg==} - engines: {node: '>=22.23.4 <23 || >=24.22.1 <25 || >=26.12.1'} - - '@wdio/globals@9.31.3': - resolution: {integrity: sha512-ZzmRKUlwUBahNoNxHbAzst+9dNInEjLQc7Z3c86OhDRxAf3Ivw8k2rc56UCbyYiZnkd1dOpuA91zFiTrUNNORw==} - engines: {node: '>=18.20.0'} - peerDependencies: - expect-webdriverio: ^6.0.9 - webdriverio: ^9.28.0 - - '@wdio/logger@10.2.0': - resolution: {integrity: sha512-C7qpFSA+Zuu/6xbx+ALoEeOiyIWlD02/Llb8ufjypCOpe9rAy77JgAHeQlKYWg7aitRQRt7aG2oeix0mNhTk5A==} - engines: {node: '>=22.23.4 <23 || >=24.22.1 <25 || >=26.12.1'} - - '@wdio/logger@9.29.1': - resolution: {integrity: sha512-0ZAEIo6PNyMIJPlOGkIgyOJUjcd0pC8/QHlVAAe1c91/IcjZ1X+k0yidXHaboJdN7dq1XPUacmhRdtua0U5EZg==} - engines: {node: '>=18.20.0'} - - '@wdio/native-core@1.3.1': - resolution: {integrity: sha512-fdGk2lEr2vS+bClJXuZEnsG30cLzQv+B9W+dAt/Yte/fE33s/vDBn7hHwFx8aZLTOS0pPExErUxYiZEMXX+04g==} - engines: {node: '>=18.20.0'} - - '@wdio/native-spy@1.4.1': - resolution: {integrity: sha512-KGOmgaicHqDkqj+s2bxXsRsb27IzW6carlyTu1xcrJI34yUbC0v14F8mbhuZcewW+mt2xhmC17yITwAbNa8PKw==} - engines: {node: '>=18.20.0'} - - '@wdio/native-types@2.6.1': - resolution: {integrity: sha512-Rm5chX+MrRhVgJYcYdcX/yyTZaLjxvi0w/fnSgPaDT16gMIntXlk22K2x3hwEdaUonOKepZyhh4jr9ZgRz7UaQ==} - engines: {node: '>=18.20.0'} - - '@wdio/native-utils@2.8.1': - resolution: {integrity: sha512-QbmZjGkLUC2bmUJSzyqsHM11HND1D7tkxHFssUV1Kmx43+oPAeHHVR0NSZu2hMzSNrDZMBAmNYycR+3X8rqWcA==} - engines: {node: '>=18.20.0'} - peerDependencies: - tsx: ^4.22.4 - peerDependenciesMeta: - tsx: - optional: true - - '@wdio/protocols@10.2.0': - resolution: {integrity: sha512-HuSsvpv7hV8hbhFdlMraz7c+Bpu9O4FiKg2XdztZupj59wx0c7n7YPnZRGEY5yW3/wRt9PfqWkBI0EO81Y+Zqw==} - engines: {node: '>=22.23.4 <23 || >=24.22.1 <25 || >=26.12.1'} - - '@wdio/repl@10.2.0': - resolution: {integrity: sha512-bVx6C9c1LgAzOyWevg796NwghExw28oPLp0NlfYGc0gErxO8wtpvLJGwCYLXQ/V372b2en5H4OZ05QXoGmXOWw==} - engines: {node: '>=22.23.4 <23 || >=24.22.1 <25 || >=26.12.1'} - - '@wdio/reporter@9.31.2': - resolution: {integrity: sha512-3cKE6vCOsro/Ep9dGTIbo3FBn3WNvJ++BIn32lFd9m19pFdjuwlVgLtPXHb9jWNCJs740h5AIUcVkcscqUYhAA==} - engines: {node: '>=18.20.0'} - - '@wdio/spec-reporter@9.31.2': - resolution: {integrity: sha512-lNLLPUk4y1Yp5pWwBFF+tjZIy7bOv0TPTk4HDWV4LnnHnkc24abI0nN+kb2y5/JV8ZgwLCCVKUFRdRUxfKsAaQ==} - engines: {node: '>=18.20.0'} - - '@wdio/tauri-service@1.5.1': - resolution: {integrity: sha512-nfzJU2x7bxvIXtYo+yvrX4Xwuba8h5hXOlJ0IyHanGg0ELavY/2UfyxygJDTcSGO+pCQMdC9JJEyMvaS50AtZQ==} - engines: {node: '>=18.20.0'} - peerDependencies: - webdriverio: ^9.0.0 - - '@wdio/types@10.2.0': - resolution: {integrity: sha512-a9qwqHzf0t2ctrXs+qUEhobH/VK0PmeoErCBWdN6DRxhTP8YiXQco4wjTj1z7H4k5xwZYxMT0KC0QzN/e4B73Q==} - engines: {node: '>=22.23.4 <23 || >=24.22.1 <25 || >=26.12.1'} - - '@wdio/types@9.31.2': - resolution: {integrity: sha512-lQKaiQDCJJ6VC1K/cKzaAxCpwB9pL1U0VbmX2uGksMxO4EDbNuLvIxWryuUDJ7OaSYTjxILTKYFErdbT/xEikg==} - engines: {node: '>=18.20.0'} - - '@wdio/utils@10.2.0': - resolution: {integrity: sha512-C4ok/WatCBV3gHmIYGjj4Aa43tQTD53eTFs3okpKxuc1wmFy/1zDFOYLDjeTq4xSFygoRBI+ZMyc9LBEiok3ng==} - engines: {node: '>=22.23.4 <23 || >=24.22.1 <25 || >=26.12.1'} - - '@zip.js/zip.js@2.24.0': - resolution: {integrity: sha512-WlaVKzdqBK+RF3bdiXZWbdCnvGB6PdO4IHC1mn/2Yfl3bASgqUNegnU3+PNg61iecwoPfUi5BgLUPqBukXpyWw==} - engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'} - - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2711,58 +2532,19 @@ packages: ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.3.0: - resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - archiver@8.0.0: - resolution: {integrity: sha512-fV1orZfsnPn9BaSByR/qE67rJCLJEy2Ox5bq7nJh+jquWaNh6Sfec75kJ2T6PtdGUbPQlrVoSVCEOa5SdiTQ1g==} - engines: {node: '>=18'} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} axios@1.20.0: resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==} - b4a@1.9.0: - resolution: {integrity: sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==} - peerDependencies: - react-native-b4a: '*' - peerDependenciesMeta: - react-native-b4a: - optional: true - babel-plugin-macros@3.1.0: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} @@ -2789,46 +2571,6 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - bare-events@2.9.2: - resolution: {integrity: sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==} - peerDependencies: - bare-abort-controller: '*' - peerDependenciesMeta: - bare-abort-controller: - optional: true - - bare-fs@4.8.1: - resolution: {integrity: sha512-N1nnXdHZAOSstz0XiHikGS4HGMH4CnSwhqWdGQQMqqdvp4Jybm9sE3R1WVnpWVd4SFkc8ryPDBLViNLwiEqECg==} - engines: {bare: '>=1.28.0'} - peerDependencies: - bare-buffer: '*' - peerDependenciesMeta: - bare-buffer: - optional: true - - bare-path@3.1.2: - resolution: {integrity: sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==} - - bare-stream@2.13.4: - resolution: {integrity: sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==} - peerDependencies: - bare-abort-controller: '*' - bare-buffer: '*' - bare-events: '*' - peerDependenciesMeta: - bare-abort-controller: - optional: true - bare-buffer: - optional: true - bare-events: - optional: true - - bare-url@2.5.4: - resolution: {integrity: sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.11.25: resolution: {integrity: sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==} engines: {node: '>=6.0.0'} @@ -2837,9 +2579,6 @@ packages: before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@5.0.12: resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} engines: {node: 20 || >=22} @@ -2856,19 +2595,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - - buffer-crc32@1.0.0: - resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} - engines: {node: '>=8.0.0'} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - cacheable@2.5.0: resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==} @@ -2894,18 +2623,6 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - chalk@6.0.1: - resolution: {integrity: sha512-/Ce6KNm3vIbWdMlNna6RVIZ/ICQxnJxCicet5LBKK9ZffBkqzDw0xh9EiKSljdRtiIQ1S1z4YgcscUUGzNCWrA==} - engines: {node: '>=22'} - character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -2918,13 +2635,6 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - cheerio-select@2.1.0: - resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} - - cheerio@1.2.0: - resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} - engines: {node: '>=20.18.1'} - chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -2933,10 +2643,6 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} - ci-info@4.4.0: - resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} - engines: {node: '>=8'} - cidr-block@2.3.0: resolution: {integrity: sha512-U5NE6QJKNOhIsVrI6sicbwGtEmEORbrz9B5Mbl+Mi05sz1Ic4egoLGV4DkJp56VWva61WxOf+vzIxTKEmRazqg==} engines: {node: '>=16'} @@ -2948,25 +2654,10 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - cliui@9.0.1: - resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} - engines: {node: '>=20'} - - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -2981,10 +2672,6 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} - comment-parser@1.4.9: resolution: {integrity: sha512-+2AvZKjJaNq9GQljm3tr0utwrig5BL+jgJGvz5rDpGKNIp+ojcRXWUwBbh/sGIi1QCprR6PsKFakub+w1v+4hQ==} engines: {node: '>= 12.0.0'} @@ -2992,10 +2679,6 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} - compress-commons@7.0.1: - resolution: {integrity: sha512-g0S8KAD8qf4+V//pr3BfB1aBnARLXNz2Gx+jmHU0LEriUuoQUOPOulVquHKTJ8+EAIIO7fhseNDr9wK5Q9FKBQ==} - engines: {node: '>=18'} - content-type@3.1.1: resolution: {integrity: sha512-GW4qUsfFo59d0HbUibDlWv5wPz+vAAcaTWbKIuKCf0JkC7wWkSyf8f13IpXn5JkeMlB2P8iTSSIjwXljorg2vA==} engines: {node: '>=22'} @@ -3016,9 +2699,6 @@ packages: core-js@3.50.0: resolution: {integrity: sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==} - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -3032,15 +2712,6 @@ packages: typescript: optional: true - crc-32@1.2.2: - resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} - engines: {node: '>=0.8'} - hasBin: true - - crc32-stream@7.0.1: - resolution: {integrity: sha512-IBWsY8xznyQrcHn8h4bC8/4ErNke5elzgG8GcqF4RFPw6aHkWWRc7Tgw6upjaTX/CT/yQgqYENkxYsTYN+hW2g==} - engines: {node: '>=18'} - cross-env@10.1.0: resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} engines: {node: '>=20'} @@ -3050,19 +2721,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - css-select@5.2.2: - resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} - - css-shorthand-properties@1.1.2: - resolution: {integrity: sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==} - - css-value@0.0.1: - resolution: {integrity: sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==} - - css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} - csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -3082,31 +2740,12 @@ packages: supports-color: optional: true - decamelize@6.0.1: - resolution: {integrity: sha512-G7Cqgaelq68XHJNGlZ7lrNQyhZGsFqpwtGFexqUv4IQdjKoSYF7ipZ9UuTJZUSQXFj/XaoBLuEVIVqr8EJngEQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - decamelize@7.0.0: - resolution: {integrity: sha512-uT/0KzZIx9u4b5vW3HLWtSYwjLTuY2QFVY3xNmfXXUmTqnfgzfT8DEA2gY93go5sXEjnbqP66KcURdVxm0rDXA==} - engines: {node: '>=22'} - decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - deepmerge-ts@8.0.2: - resolution: {integrity: sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==} - engines: {node: '>=16.9.0'} - - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -3122,29 +2761,12 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - diff@8.0.4: - resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} - engines: {node: '>=0.3.1'} - dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - dompurify@3.4.15: resolution: {integrity: sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==} - domutils@3.2.2: - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -3152,25 +2774,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - easy-table@1.2.0: - resolution: {integrity: sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==} - - edgedriver@8.0.0: - resolution: {integrity: sha512-h4twAKTFILvpjrxfeN1gslvnggztA6Cgb4iHQdLzEq3TivaHHlf6qEETpCgU7+iw9JIWjcveeckrDlbcY28a/A==} - engines: {node: '>=22.19.0'} - hasBin: true - peerDependencies: - '@wdio/logger': ^9.29.1 || ^10.0.0 - electron-to-chromium@1.5.444: resolution: {integrity: sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==} - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - - encoding-sniffer@0.2.1: - resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -3179,10 +2785,6 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} - entities@7.0.1: - resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} - engines: {node: '>=0.12'} - error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -3228,10 +2830,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -3418,33 +3016,10 @@ packages: event-target-bus@1.1.0: resolution: {integrity: sha512-24JX34nM0qRcsj7KvtLbVpMcX8ZQYmb0+0a6viaV2GJtVFg5UGyJyDLITkBipJIG0fSscNskI2PxdhTBIMeg8g==} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - - events-universal@1.0.1: - resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - expect-type@1.4.0: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} - expect-webdriverio@6.1.0: - resolution: {integrity: sha512-RXgaXoH1U8IiAHPelkMhZSnWUT0HfIhkjKK8nBkIpnXRO+zJ8dRafoyZhVy5mu/LtoyIr9juRWRwN284OSNM6Q==} - engines: {node: '>=20'} - peerDependencies: - '@wdio/globals': ^9.0.0 - '@wdio/logger': ^9.0.0 - webdriverio: ^9.28.0 - - expect@30.5.2: - resolution: {integrity: sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} @@ -3454,9 +3029,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - fast-fnv1a@1.0.0: resolution: {integrity: sha512-Y7iaFmz24pNJX+nqXgDuyHYFTfV9XPWJRfqp0hlG0Xuz1vKnelwLwvmKkbvVVvp5WzYWnrovBTLdk2PWkOYzCg==} engines: {node: ^22.15.0 || ^23.8.0 || >=24.0.0} @@ -3470,9 +3042,6 @@ packages: fd-package-json@2.0.0: resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -3488,10 +3057,6 @@ packages: find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - find-up-simple@1.0.1: - resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} - engines: {node: '>=18'} - find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -3546,33 +3111,14 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - geckodriver@8.0.0: - resolution: {integrity: sha512-2jGCA6deI5MAjODxmHljPqvaUccGbeeqMUHXlW4k3FNDp4YDpxSI+P5QG+w5KIsnCeuGEfTTNi2euKqHI1eD6Q==} - engines: {node: '>=22.19.0'} - hasBin: true - peerDependencies: - '@wdio/logger': ^9.18.0 || ^10.0.0 - 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.*} - - get-east-asian-width@1.7.0: - resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} - engines: {node: '>=18'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} - get-port@7.2.0: - resolution: {integrity: sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg==} - engines: {node: '>=16'} - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -3584,10 +3130,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@14.0.4: - resolution: {integrity: sha512-mMiwNKoJZ4HAIRVJxotO6LsNRReA30gQwP9E2/p94tfLv+jygCNpKDjhhSL6OLY6LE371EZm5EAf7RpYuUqlsw==} - engines: {node: 18 || 20 || >=22} - globals@17.12.0: resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==} engines: {node: '>=18'} @@ -3596,12 +3138,6 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - grapheme-splitter@1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3670,12 +3206,6 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - htmlfy@0.8.1: - resolution: {integrity: sha512-xWROBw9+MEGwxpotll0h672KCaLrKKiCYzsyN8ZgL9cQbVumFnyvsk2JqiB9ELAV1GLj1GG/jxZUjV9OZZi/yQ==} - - htmlparser2@10.1.0: - resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} - https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -3698,13 +3228,6 @@ packages: typescript: optional: true - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -3713,9 +3236,6 @@ packages: resolution: {integrity: sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==} engines: {node: '>= 4'} - immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - immutable@5.1.9: resolution: {integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==} @@ -3723,16 +3243,10 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-meta-resolve@4.2.0: - resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -3773,40 +3287,9 @@ packages: is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} - is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} - engines: {node: '>=18'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jest-diff@30.5.2: - resolution: {integrity: sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-matcher-utils@30.5.2: - resolution: {integrity: sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-message-util@30.5.1: - resolution: {integrity: sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-mock@30.5.2: - resolution: {integrity: sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-regex-util@30.5.0: - resolution: {integrity: sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - jest-util@30.5.1: - resolution: {integrity: sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -3850,9 +3333,6 @@ packages: jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - jszip@3.10.2: - resolution: {integrity: sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==} - keyv@5.6.0: resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} @@ -3861,17 +3341,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - lazystream@1.0.1: - resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} - engines: {node: '>= 0.6.3'} - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - lightningcss-android-arm64@1.33.0: resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} engines: {node: '>= 12.0.0'} @@ -3954,9 +3427,6 @@ packages: engines: {node: '>=22.22.1'} hasBin: true - locate-app@2.5.0: - resolution: {integrity: sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q==} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -3964,22 +3434,12 @@ packages: lodash-es@4.18.1: resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} - lodash.clonedeep@4.5.0: - resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - loglevel-plugin-prefix@0.8.4: - resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==} - - loglevel@1.9.2: - resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} - engines: {node: '>= 0.6.0'} - longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -3990,19 +3450,12 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lru-cache@11.5.3: - resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} - engines: {node: 20 || >=22} - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magic-string@1.4.1: resolution: {integrity: sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==} @@ -4126,10 +3579,6 @@ packages: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} - minimatch@11.0.5: - resolution: {integrity: sha512-snPlVppFcYWfLLFQGhDxUyBcHNtMlWmn3jtj7pEaO+DP8pqL0IvAzXhMYmn1z9ASNtgmwLBdFxSf4tMUB0OTKA==} - engines: {node: 20 || >=22} - minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -4138,13 +3587,6 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} - mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - - modern-tar@0.8.5: - resolution: {integrity: sha512-snEhs+6G5Tjd4I7tLCDOaoln2RgE0bD19RzEKgvgK2hZ5VKy3MpLhLTZ2fWpXSTg4K2cyPwp+VHATFJhxfnOeA==} - engines: {node: '>=18.0.0'} - monaco-editor@0.57.0: resolution: {integrity: sha512-5BkI9KGoqrNvBGUe15/QlZq3OooZ8WLg1AxTpaqHRCP3HNpzPPZKE2EDz8M7c+VRmCeUw1Brp4cx/PWm3kI/5A==} @@ -4201,25 +3643,10 @@ packages: resolution: {integrity: sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==} engines: {node: '>=18'} - non-error@0.1.0: - resolution: {integrity: sha512-TMB1uHiGsHRGv1uYclfhivcnf0/PdFp2pNqRxXjncaAsjYMoisaQJI+SSZCqRq+VliwRTC8tsMQfmrWjDMhkPQ==} - engines: {node: '>=20'} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - obug@2.2.1: resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} engines: {node: '>=12.20.0'} @@ -4246,9 +3673,6 @@ packages: package-manager-detector@1.8.0: resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} - pako@1.0.11: - resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -4260,16 +3684,6 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - parse-ms@4.0.0: - resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} - engines: {node: '>=18'} - - parse5-htmlparser2-tree-adapter@7.1.0: - resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} - - parse5-parser-stream@7.1.2: - resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} - parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -4287,20 +3701,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@3.0.0: - resolution: {integrity: sha512-1pTbQqH1wIwMi/eleQ5leFTTCkzFsPcgXNjwOA8YGGW/W4/kHT3dssE7Z6NH+zuqNLOzOB0uaD61yl7cAcVhgg==} - engines: {node: 18 || 20 || >=22} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -4321,21 +3725,6 @@ packages: engines: {node: '>=14'} hasBin: true - pretty-format@30.5.1: - resolution: {integrity: sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - pretty-ms@9.3.1: - resolution: {integrity: sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==} - engines: {node: '>=18'} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -4371,9 +3760,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - query-selector-shadow-dom@1.0.1: - resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} - react-dom@19.3.1: resolution: {integrity: sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==} peerDependencies: @@ -4414,9 +3800,6 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-is@19.3.0: resolution: {integrity: sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==} @@ -4446,17 +3829,6 @@ packages: resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==} engines: {node: '>=0.10.0'} - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readable-stream@4.7.0: - resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - readdir-glob@3.0.0: - resolution: {integrity: sha512-AhNB2KgKeVJr16nK9LLZbJNWnYoT23ZrumNKFDebHBdkC8KHSqWo871JAUhoWC/RtjEVdqNMFpM6qrwRbaUqpw==} - engines: {node: '>=18'} - readdirp@5.1.1: resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} engines: {node: '>= 20.19.0'} @@ -4509,35 +3881,11 @@ packages: engines: {node: '>= 0.4'} hasBin: true - ret@0.5.0: - resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} - engines: {node: '>=10'} - - rgb2hex@0.2.5: - resolution: {integrity: sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==} - rolldown@1.2.16: resolution: {integrity: sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - safaridriver@3.0.0: - resolution: {integrity: sha512-NJSsR0tgn5j4fhg7vj4kD5mQ+t2ZRH3GX1jdUYraEjej9j8ZtBXEe4WZh2/89i7S9+GO3cZKWhUisWb7gyjRwA==} - engines: {node: '>=22.19.0'} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-regex2@5.1.1: - resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==} - hasBin: true - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.104.1: resolution: {integrity: sha512-yDA+1aIG3EHgN4V/BvuhCvu61FF6hEd4e+9DxikUm9U0CAGuvdIZ/UYy7qbOxjhbbWQraoyLlMuzdRGOSV5Bmw==} engines: {node: '>=20.19.0'} @@ -4559,16 +3907,9 @@ packages: engines: {node: '>=10'} hasBin: true - serialize-error@13.0.2: - resolution: {integrity: sha512-G+YKwp9I6IvWMe5RlwqxdmTPIcq/G8HK/sU51gdV1XAJznYkRSTB0ph+zB9AfSxG0fx6xrqYA6i9HeIcHFTXow==} - engines: {node: '>=20'} - server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} - setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -4580,10 +3921,6 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - smol-toml@1.9.1: resolution: {integrity: sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==} engines: {node: '>= 18'} @@ -4609,21 +3946,10 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - spacetrim@0.11.59: - resolution: {integrity: sha512-lLYsktklSRKprreOm7NXReW8YiX2VBjbgmXYEziOoGf/qsJqAEACaDvoTtUOycwjpaSh+bT8eu0KrJn7UNxiCg==} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -4633,9 +3959,6 @@ packages: std-env@4.2.0: resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} - streamx@2.28.1: - resolution: {integrity: sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -4643,27 +3966,9 @@ packages: string-ts@2.3.1: resolution: {integrity: sha512-xSJq+BS52SaFFAVxuStmx6n5aYZU571uYUnUrPXkPFCfdHyZMMlbP2v2Wx5sNBnAVzq/2+0+mcBLBa3Xa5ubYw==} - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - string-width@8.3.0: - resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==} - engines: {node: '>=20'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - strip-json-comments@5.0.3: resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} engines: {node: '>=14.16'} @@ -4697,13 +4002,6 @@ packages: systemjs@6.15.1: resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==} - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} - - tar-stream@3.2.1: - resolution: {integrity: sha512-nqsEO8zLZJvrOMdEwkA0QdCLFbetHMn95Zqu4fKwX+hkaTWJPZZOrxx/PwtxoK0MMGQmBQNRW3CPs8IFYQz4cQ==} - tar@7.5.22: resolution: {integrity: sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==} engines: {node: '>=18'} @@ -4712,17 +4010,11 @@ packages: resolution: {gitHosted: true, integrity: sha512-fy5wxkEBj+L3c1ZefPpU6F9Wmhsx9iLJI3xBeYlvpGg7Iqzo6+qR2KcWpGilgHbpJWcPb6c22QMxyLR369vkeQ==, tarball: https://codeload.github.com/clash-verge-rev/tauri-plugin-mihomo/tar.gz/e49bbedcc67d60da2c10e81bc6433b0f8e8fa232} version: 0.7.0 - teex@1.0.1: - resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} - terser@5.51.2: resolution: {integrity: sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==} engines: {node: '>=10'} hasBin: true - text-decoder@1.2.7: - resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} - timers-ext@0.1.8: resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==} engines: {node: '>=0.12'} @@ -4743,10 +4035,6 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinyrainbow@3.1.1: - resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} - engines: {node: '>=14.0.0'} - trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -4778,14 +4066,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@4.26.0: - resolution: {integrity: sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==} - engines: {node: '>=16'} - - type-fest@5.10.0: - resolution: {integrity: sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==} - engines: {node: '>=20'} - type@2.7.3: resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} @@ -4808,9 +4088,6 @@ packages: resolution: {integrity: sha512-t3OptUArewg9OpYGDskTSlxhyLDahfmZ4d36hE44Efb1y3HRLLpzsVqwH+AwBVymP4t7pP71xN+nnduudn2awA==} engines: {node: '>=14'} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@8.9.0: resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} @@ -4818,14 +4095,6 @@ packages: resolution: {integrity: sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==} engines: {node: '>=18.17'} - undici@7.30.1: - resolution: {integrity: sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==} - engines: {node: '>=20.18.1'} - - undici@8.12.3: - resolution: {integrity: sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw==} - engines: {node: '>=22.19.0'} - unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -4875,21 +4144,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - urlpattern-polyfill@10.1.0: - resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} - use-sync-external-store@1.7.0: resolution: {integrity: sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - userhome@1.0.1: - resolution: {integrity: sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==} - engines: {node: '>= 0.8.0'} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - validator@13.15.35: resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} engines: {node: '>= 0.10'} @@ -5008,43 +4267,13 @@ packages: vscode-uri@3.2.0: resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} - wait-port@1.1.0: - resolution: {integrity: sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==} - engines: {node: '>=10'} - hasBin: true - walk-up-path@4.0.0: resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} engines: {node: 20 || >=22} - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - webdriver@10.2.0: - resolution: {integrity: sha512-RV2+j+CAgJgJb59QliIqMik2DmP4l9dW38tDSmzWdilonCAxM5fKZAydpBt6CNlToG0jqpZ9nL3Mo6YnQh/xqw==} - engines: {node: '>=22.23.4 <23 || >=24.22.1 <25 || >=26.12.1'} - - webdriverio@10.2.0: - resolution: {integrity: sha512-efmXasjo/+tWN+C5HCqsaPIJDJLH7smua1aeizL/DoEDg7f4YYVLgD9Zhav9DYrrouag9C04Mmj6bo6a2KA3hA==} - engines: {node: '>=22.23.4 <23 || >=24.22.1 <25 || >=26.12.1'} - peerDependencies: - puppeteer-core: '>=24 <26' - peerDependenciesMeta: - puppeteer-core: - optional: true - - whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} - deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation - - whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -5059,26 +4288,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} - - ws@8.22.2: - resolution: {integrity: sha512-/rEZ2ejown2N5gxVTHUu9bVVC7ttu0/Tgzf5hbTOixR3Fs7mbbXgn2DbdbcKgJOmpQMtrIUWivtYuVMnp/W/yA==} - 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 - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -5095,25 +4304,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yargs-parser@22.0.0: - resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - - yargs@18.2.0: - resolution: {integrity: sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zip-stream@7.0.5: - resolution: {integrity: sha512-dSvYKdvLsAHCDqPOhIwk/q5CvuWtTB3Dgpoe0uVEFjTzIOAmsQpprX25InCvrvJsirEbu1OHyy67n/kAj1Sw/w==} - engines: {node: '>=18'} - zod-validation-error@3.5.4: resolution: {integrity: sha512-+hEiRIiPobgyuFlEojnqjJnhFvg4r/i3cqgcm67eehZf/WBaK3g6cD02YU9mtdVxZjv8CzCA9n/Rhrs3yAAvAw==} engines: {node: '>=18.0.0'} @@ -6249,35 +5443,6 @@ snapshots: dependencies: minipass: 7.1.3 - '@isaacs/nonrecursive-glob-match@1.1.0': {} - - '@jest/diff-sequences@30.5.0': {} - - '@jest/expect-utils@30.5.2': - dependencies: - '@jest/get-type': 30.5.0 - - '@jest/get-type@30.5.0': {} - - '@jest/pattern@30.5.0': - dependencies: - '@types/node': 26.6.5 - jest-regex-util: 30.5.0 - - '@jest/schemas@30.5.0': - dependencies: - '@sinclair/typebox': 0.34.52 - - '@jest/types@30.5.1': - dependencies: - '@jest/pattern': 30.5.0 - '@jest/schemas': 30.5.0 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 26.6.5 - '@types/yargs': 17.0.35 - chalk: 4.1.2 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -6663,17 +5828,6 @@ snapshots: '@preact/signals-core@1.14.4': {} - '@promptbook/utils@0.69.5': - dependencies: - spacetrim: 0.11.59 - - '@puppeteer/browsers@3.2.3(yauzl@2.10.0)': - dependencies: - modern-tar: 0.8.5 - yargs: 18.2.0 - optionalDependencies: - yauzl: 2.10.0 - '@remix-run/route-pattern@0.22.1': {} '@rolldown/binding-android-arm-eabi@1.2.16': @@ -6729,8 +5883,6 @@ snapshots: estree-walker: 2.0.2 picomatch: 4.0.7 - '@sinclair/typebox@0.34.52': {} - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: '@babel/core': 7.29.7(supports-color@7.2.0) @@ -6931,16 +6083,6 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@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/json-schema@7.0.15': {} '@types/lodash-es@4.17.12': @@ -6955,14 +6097,6 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@20.19.43': - dependencies: - undici-types: 6.21.0 - - '@types/node@22.20.5': - dependencies: - undici-types: 6.21.0 - '@types/node@26.6.5': dependencies: undici-types: 8.9.0 @@ -6983,14 +6117,6 @@ snapshots: dependencies: csstype: 3.2.3 - '@types/sinonjs__fake-timers@15.0.1': {} - - '@types/split2@4.2.3': - dependencies: - '@types/node': 26.6.5 - - '@types/stack-utils@2.0.3': {} - '@types/trusted-types@2.0.7': optional: true @@ -7000,16 +6126,6 @@ snapshots: '@types/validator@13.15.10': {} - '@types/ws@8.18.1': - dependencies: - '@types/node': 26.6.5 - - '@types/yargs-parser@21.0.3': {} - - '@types/yargs@17.0.35': - dependencies: - '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.11.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -7206,169 +6322,10 @@ snapshots: optionalDependencies: vite: 8.3.3(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.104.1)(terser@5.51.2)(yaml@2.9.1) - '@vitest/pretty-format@4.1.11': - dependencies: - tinyrainbow: 3.1.1 - - '@vitest/snapshot@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - '@vitest/utils': 4.1.11 - magic-string: 0.30.21 - pathe: 2.0.3 - '@vitest/spy@5.0.1': {} - '@vitest/utils@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 - '@vscode/l10n@0.0.18': {} - '@wdio/config@10.2.0(supports-color@7.2.0)(yauzl@2.10.0)': - dependencies: - '@wdio/logger': 10.2.0 - '@wdio/types': 10.2.0 - '@wdio/utils': 10.2.0(supports-color@7.2.0)(yauzl@2.10.0) - deepmerge-ts: 8.0.2 - glob: 14.0.4 - import-meta-resolve: 4.2.0 - jiti: 2.7.0 - transitivePeerDependencies: - - proxy-agent - - supports-color - - yauzl - - '@wdio/globals@9.31.3(expect-webdriverio@6.1.0)(webdriverio@10.2.0(supports-color@7.2.0)(yauzl@2.10.0))': - dependencies: - expect-webdriverio: 6.1.0(@wdio/globals@9.31.3)(@wdio/logger@9.29.1)(webdriverio@10.2.0(supports-color@7.2.0)(yauzl@2.10.0)) - webdriverio: 10.2.0(supports-color@7.2.0)(yauzl@2.10.0) - - '@wdio/logger@10.2.0': - dependencies: - chalk: 6.0.1 - loglevel: 1.9.2 - loglevel-plugin-prefix: 0.8.4 - safe-regex2: 5.1.1 - strip-ansi: 7.2.0 - - '@wdio/logger@9.29.1': - dependencies: - chalk: 5.6.2 - loglevel: 1.9.2 - loglevel-plugin-prefix: 0.8.4 - safe-regex2: 5.1.1 - strip-ansi: 7.2.0 - - '@wdio/native-core@1.3.1(supports-color@7.2.0)': - dependencies: - '@wdio/logger': 9.29.1 - '@wdio/native-types': 2.6.1 - '@wdio/native-utils': 2.8.1(supports-color@7.2.0) - debug: 4.4.3(supports-color@7.2.0) - get-port: 7.2.0 - tslib: 2.8.1 - transitivePeerDependencies: - - supports-color - - tsx - - '@wdio/native-spy@1.4.1': {} - - '@wdio/native-types@2.6.1': {} - - '@wdio/native-utils@2.8.1(supports-color@7.2.0)': - dependencies: - '@wdio/logger': 9.29.1 - debug: 4.4.3(supports-color@7.2.0) - esbuild: 0.28.2 - find-up-simple: 1.0.1 - json5: 2.2.3 - smol-toml: 1.9.1 - yaml: 2.9.1 - transitivePeerDependencies: - - supports-color - - '@wdio/protocols@10.2.0': {} - - '@wdio/repl@10.2.0': - dependencies: - '@types/node': 22.20.5 - - '@wdio/reporter@9.31.2': - dependencies: - '@types/node': 20.19.43 - '@wdio/logger': 9.29.1 - '@wdio/types': 9.31.2 - diff: 8.0.4 - object-inspect: 1.13.4 - - '@wdio/spec-reporter@9.31.2': - dependencies: - '@wdio/reporter': 9.31.2 - '@wdio/types': 9.31.2 - chalk: 5.6.2 - easy-table: 1.2.0 - pretty-ms: 9.3.1 - - '@wdio/tauri-service@1.5.1(expect-webdriverio@6.1.0)(supports-color@7.2.0)(webdriverio@10.2.0(supports-color@7.2.0)(yauzl@2.10.0))': - dependencies: - '@wdio/globals': 9.31.3(expect-webdriverio@6.1.0)(webdriverio@10.2.0(supports-color@7.2.0)(yauzl@2.10.0)) - '@wdio/logger': 9.29.1 - '@wdio/native-core': 1.3.1(supports-color@7.2.0) - '@wdio/native-spy': 1.4.1 - '@wdio/native-types': 2.6.1 - '@wdio/native-utils': 2.8.1(supports-color@7.2.0) - '@wdio/spec-reporter': 9.31.2 - '@wdio/types': 9.31.2 - debug: 4.4.3(supports-color@7.2.0) - get-port: 7.2.0 - tslib: 2.8.1 - webdriverio: 10.2.0(supports-color@7.2.0)(yauzl@2.10.0) - transitivePeerDependencies: - - expect-webdriverio - - supports-color - - tsx - - '@wdio/types@10.2.0': - dependencies: - '@types/node': 22.20.5 - - '@wdio/types@9.31.2': - dependencies: - '@types/node': 20.19.43 - - '@wdio/utils@10.2.0(supports-color@7.2.0)(yauzl@2.10.0)': - dependencies: - '@puppeteer/browsers': 3.2.3(yauzl@2.10.0) - '@types/split2': 4.2.3 - '@wdio/logger': 10.2.0 - '@wdio/types': 10.2.0 - aria-query: 5.3.2 - decamelize: 7.0.0 - deepmerge-ts: 8.0.2 - edgedriver: 8.0.0(@wdio/logger@10.2.0) - electron-to-chromium: 1.5.444 - geckodriver: 8.0.0(@wdio/logger@10.2.0) - get-port: 7.2.0 - import-meta-resolve: 4.2.0 - locate-app: 2.5.0 - mitt: 3.0.1 - safaridriver: 3.0.0 - split2: 4.2.0 - wait-port: 1.1.0(supports-color@7.2.0) - transitivePeerDependencies: - - proxy-agent - - supports-color - - yauzl - - '@zip.js/zip.js@2.24.0': {} - - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - acorn-jsx@5.3.2(acorn@8.18.0): dependencies: acorn: 8.18.0 @@ -7405,42 +6362,10 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ansi-regex@5.0.1: {} - - ansi-regex@6.3.0: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@5.2.0: {} - - ansi-styles@6.2.3: {} - - archiver@8.0.0: - dependencies: - async: 3.2.6 - buffer-crc32: 1.0.0 - is-stream: 4.0.1 - lazystream: 1.0.1 - normalize-path: 3.0.0 - readable-stream: 4.7.0 - readdir-glob: 3.0.0 - tar-stream: 3.2.1 - zip-stream: 7.0.5 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a - argparse@2.0.1: {} - aria-query@5.3.2: {} - assertion-error@2.0.1: {} - async@3.2.6: {} - asynckit@0.4.0: {} axios@1.20.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0): @@ -7453,8 +6378,6 @@ snapshots: - debug - supports-color - b4a@1.9.0: {} - babel-plugin-macros@3.1.0: dependencies: '@babel/runtime': 7.29.7 @@ -7489,43 +6412,10 @@ snapshots: balanced-match@4.0.4: {} - bare-events@2.9.2: {} - - bare-fs@4.8.1: - dependencies: - bare-events: 2.9.2 - bare-path: 3.1.2 - bare-stream: 2.13.4(bare-events@2.9.2) - bare-url: 2.5.4 - fast-fifo: 1.3.2 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - bare-path@3.1.2: {} - - bare-stream@2.13.4(bare-events@2.9.2): - dependencies: - b4a: 1.9.0 - streamx: 2.28.1 - teex: 1.0.1 - optionalDependencies: - bare-events: 2.9.2 - transitivePeerDependencies: - - react-native-b4a - - bare-url@2.5.4: - dependencies: - bare-path: 3.1.2 - - base64-js@1.5.1: {} - baseline-browser-mapping@2.11.25: {} before-after-hook@4.0.0: {} - boolbase@1.0.0: {} - brace-expansion@5.0.12: dependencies: balanced-match: 4.0.4 @@ -7543,18 +6433,8 @@ snapshots: node-releases: 2.0.56 update-browserslist-db: 1.3.3(browserslist@4.29.0) - buffer-crc32@0.2.13: - optional: true - - buffer-crc32@1.0.0: {} - buffer-from@1.1.2: {} - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - cacheable@2.5.0: dependencies: '@cacheable/memory': 2.2.0 @@ -7578,15 +6458,6 @@ snapshots: chai@6.2.2: {} - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chalk@5.6.2: {} - - chalk@6.0.1: {} - character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -7595,37 +6466,12 @@ snapshots: character-reference-invalid@2.0.1: {} - cheerio-select@2.1.0: - dependencies: - boolbase: 1.0.0 - css-select: 5.2.2 - css-what: 6.2.2 - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.2.2 - - cheerio@1.2.0: - dependencies: - cheerio-select: 2.1.0 - dom-serializer: 2.0.0 - domhandler: 5.0.3 - domutils: 3.2.2 - encoding-sniffer: 0.2.1 - htmlparser2: 10.1.0 - parse5: 7.3.0 - parse5-htmlparser2-tree-adapter: 7.1.0 - parse5-parser-stream: 7.1.2 - undici: 7.30.1 - whatwg-mimetype: 4.0.0 - chokidar@5.0.0: dependencies: readdirp: 5.1.1 chownr@3.0.0: {} - ci-info@4.4.0: {} - cidr-block@2.3.0: {} cli-color@2.0.4: @@ -7638,23 +6484,8 @@ snapshots: client-only@0.0.1: {} - cliui@9.0.1: - dependencies: - string-width: 7.2.0 - strip-ansi: 7.2.0 - wrap-ansi: 9.0.2 - - clone@1.0.4: - optional: true - clsx@2.1.1: {} - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -7665,20 +6496,10 @@ snapshots: commander@2.20.3: {} - commander@9.5.0: {} - comment-parser@1.4.9: {} compare-versions@6.1.1: {} - compress-commons@7.0.1: - dependencies: - crc-32: 1.2.2 - crc32-stream: 7.0.1 - is-stream: 4.0.1 - normalize-path: 3.0.0 - readable-stream: 4.7.0 - content-type@3.1.1: {} convert-source-map@1.9.0: {} @@ -7693,8 +6514,6 @@ snapshots: core-js@3.50.0: {} - core-util-is@1.0.3: {} - cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 @@ -7712,13 +6531,6 @@ snapshots: optionalDependencies: typescript: 6.0.3 - crc-32@1.2.2: {} - - crc32-stream@7.0.1: - dependencies: - crc-32: 1.2.2 - readable-stream: 4.7.0 - cross-env@10.1.0: dependencies: '@epic-web/invariant': 1.0.0 @@ -7730,20 +6542,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-select@5.2.2: - dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 5.0.3 - domutils: 3.2.2 - nth-check: 2.1.1 - - css-shorthand-properties@1.1.2: {} - - css-value@0.0.1: {} - - css-what@6.2.2: {} - csstype@3.2.3: {} d@1.0.2: @@ -7759,25 +6557,12 @@ snapshots: optionalDependencies: supports-color: 7.2.0 - decamelize@6.0.1: {} - - decamelize@7.0.0: {} - decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 - deep-eql@5.0.2: {} - deep-is@0.1.4: {} - deepmerge-ts@8.0.2: {} - - defaults@1.0.4: - dependencies: - clone: 1.0.4 - optional: true - delayed-stream@1.0.0: {} dequal@2.0.3: {} @@ -7788,35 +6573,15 @@ snapshots: dependencies: dequal: 2.0.3 - diff@8.0.4: {} - dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.29.7 csstype: 3.2.3 - dom-serializer@2.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 - - domelementtype@2.3.0: {} - - domhandler@5.0.3: - dependencies: - domelementtype: 2.3.0 - dompurify@3.4.15: optionalDependencies: '@types/trusted-types': 2.0.7 - domutils@3.2.2: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - dot-case@3.0.4: dependencies: no-case: 3.0.4 @@ -7828,34 +6593,12 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - easy-table@1.2.0: - dependencies: - ansi-regex: 5.0.1 - optionalDependencies: - wcwidth: 1.0.1 - - edgedriver@8.0.0(@wdio/logger@10.2.0): - dependencies: - '@wdio/logger': 10.2.0 - '@zip.js/zip.js': 2.24.0 - decamelize: 6.0.1 - undici: 8.12.3 - electron-to-chromium@1.5.444: {} - emoji-regex@10.6.0: {} - - encoding-sniffer@0.2.1: - dependencies: - iconv-lite: 0.6.3 - whatwg-encoding: 3.1.1 - entities@4.5.0: {} entities@6.0.1: {} - entities@7.0.1: {} - error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -7930,11 +6673,10 @@ snapshots: '@esbuild/win32-arm64': 0.28.2 '@esbuild/win32-ia32': 0.28.2 '@esbuild/win32-x64': 0.28.2 + optional: true escalade@3.2.0: {} - escape-string-regexp@2.0.0: {} - escape-string-regexp@4.0.0: {} eslint-import-context@0.1.9(unrs-resolver@1.12.2): @@ -8215,37 +6957,8 @@ snapshots: event-target-bus@1.1.0: {} - event-target-shim@5.0.1: {} - - events-universal@1.0.1: - dependencies: - bare-events: 2.9.2 - transitivePeerDependencies: - - bare-abort-controller - - events@3.3.0: {} - expect-type@1.4.0: {} - expect-webdriverio@6.1.0(@wdio/globals@9.31.3)(@wdio/logger@9.29.1)(webdriverio@10.2.0(supports-color@7.2.0)(yauzl@2.10.0)): - dependencies: - '@vitest/snapshot': 4.1.11 - '@wdio/globals': 9.31.3(expect-webdriverio@6.1.0)(webdriverio@10.2.0(supports-color@7.2.0)(yauzl@2.10.0)) - '@wdio/logger': 9.29.1 - deep-eql: 5.0.2 - expect: 30.5.2 - jest-matcher-utils: 30.5.2 - webdriverio: 10.2.0(supports-color@7.2.0)(yauzl@2.10.0) - - expect@30.5.2: - dependencies: - '@jest/expect-utils': 30.5.2 - '@jest/get-type': 30.5.0 - jest-matcher-utils: 30.5.2 - jest-message-util: 30.5.1 - jest-mock: 30.5.2 - jest-util: 30.5.1 - ext@1.7.0: dependencies: type: 2.7.3 @@ -8254,8 +6967,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-fifo@1.3.2: {} - fast-fnv1a@1.0.0: {} fast-json-stable-stringify@2.1.0: {} @@ -8266,11 +6977,6 @@ snapshots: dependencies: walk-up-path: 4.0.0 - fd-slicer@1.1.0: - dependencies: - pend: 1.2.0 - optional: true - fdir@6.5.0(picomatch@4.0.7): optionalDependencies: picomatch: 4.0.7 @@ -8281,8 +6987,6 @@ snapshots: find-root@1.1.0: {} - find-up-simple@1.0.1: {} - find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -8334,20 +7038,8 @@ snapshots: function-bind@1.1.2: {} - geckodriver@8.0.0(@wdio/logger@10.2.0): - dependencies: - '@wdio/logger': 10.2.0 - '@zip.js/zip.js': 2.24.0 - decamelize: 6.0.1 - modern-tar: 0.8.5 - undici: 8.12.3 - gensync@1.0.0-beta.2: {} - get-caller-file@2.0.5: {} - - get-east-asian-width@1.7.0: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -8361,8 +7053,6 @@ snapshots: hasown: 2.0.4 math-intrinsics: 1.1.0 - get-port@7.2.0: {} - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -8376,21 +7066,12 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@14.0.4: - dependencies: - minimatch: 11.0.5 - minipass: 7.1.3 - path-scurry: 3.0.0 - globals@17.12.0: {} gopd@1.2.0: {} - graceful-fs@4.2.11: {} - - grapheme-splitter@1.0.4: {} - - has-flag@4.0.0: {} + has-flag@4.0.0: + optional: true has-symbols@1.1.0: {} @@ -8505,15 +7186,6 @@ snapshots: html-void-elements@3.0.0: {} - htmlfy@0.8.1: {} - - htmlparser2@10.1.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.2.2 - entities: 7.0.1 - https-proxy-agent@5.0.1(supports-color@7.2.0): dependencies: agent-base: 6.0.2(supports-color@7.2.0) @@ -8536,18 +7208,10 @@ snapshots: optionalDependencies: typescript: 6.0.3 - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - ieee754@1.2.1: {} - ignore@5.3.2: {} ignore@7.0.9: {} - immediate@3.0.6: {} - immutable@5.1.9: {} import-fresh@3.3.1: @@ -8555,12 +7219,8 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-meta-resolve@4.2.0: {} - imurmurhash@0.1.4: {} - inherits@2.0.4: {} - inline-style-parser@0.2.7: {} is-alphabetical@2.0.1: {} @@ -8594,57 +7254,8 @@ snapshots: is-promise@2.2.2: {} - is-stream@4.0.1: {} - - isarray@1.0.0: {} - isexe@2.0.0: {} - jest-diff@30.5.2: - dependencies: - '@jest/diff-sequences': 30.5.0 - '@jest/get-type': 30.5.0 - chalk: 4.1.2 - pretty-format: 30.5.1 - - jest-matcher-utils@30.5.2: - dependencies: - '@jest/get-type': 30.5.0 - chalk: 4.1.2 - jest-diff: 30.5.2 - pretty-format: 30.5.1 - - jest-message-util@30.5.1: - dependencies: - '@babel/code-frame': 7.29.7 - '@jest/types': 30.5.1 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-util: 30.5.1 - picomatch: 4.0.7 - pretty-format: 30.5.1 - slash: 3.0.0 - stack-utils: 2.0.6 - - jest-mock@30.5.2: - dependencies: - '@jest/expect-utils': 30.5.2 - '@jest/types': 30.5.1 - '@types/node': 26.6.5 - jest-util: 30.5.1 - - jest-regex-util@30.5.0: {} - - jest-util@30.5.1: - dependencies: - '@jest/types': 30.5.1 - '@types/node': 26.6.5 - chalk: 4.1.2 - ci-info: 4.4.0 - graceful-fs: 4.2.11 - picomatch: 4.0.7 - jiti@2.7.0: {} js-cookie@3.0.8: {} @@ -8673,13 +7284,6 @@ snapshots: jsonc-parser@3.3.1: {} - jszip@3.10.2: - dependencies: - lie: 3.3.0 - pako: 1.0.11 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - keyv@5.6.0: dependencies: '@keyv/serialize': 1.1.1 @@ -8698,19 +7302,11 @@ snapshots: unbash: 6.0.4 yaml: 2.9.1 - lazystream@1.0.1: - dependencies: - readable-stream: 2.3.8 - levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - lie@3.3.0: - dependencies: - immediate: 3.0.6 - lightningcss-android-arm64@1.33.0: optional: true @@ -8770,28 +7366,16 @@ snapshots: optionalDependencies: yaml: 2.9.1 - locate-app@2.5.0: - dependencies: - '@promptbook/utils': 0.69.5 - type-fest: 4.26.0 - userhome: 1.0.1 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 lodash-es@4.18.1: {} - lodash.clonedeep@4.5.0: {} - lodash.debounce@4.0.8: {} lodash@4.18.1: {} - loglevel-plugin-prefix@0.8.4: {} - - loglevel@1.9.2: {} - longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -8802,8 +7386,6 @@ snapshots: dependencies: tslib: 2.8.1 - lru-cache@11.5.3: {} - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -8812,10 +7394,6 @@ snapshots: dependencies: es5-ext: 0.10.64 - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - magic-string@1.4.1: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -9071,21 +7649,12 @@ snapshots: dependencies: brace-expansion: 5.0.12 - minimatch@11.0.5: - dependencies: - '@isaacs/nonrecursive-glob-match': 1.1.0 - brace-expansion: 5.0.12 - minipass@7.1.3: {} minizlib@3.1.0: dependencies: minipass: 7.1.3 - mitt@3.0.1: {} - - modern-tar@0.8.5: {} - monaco-editor@0.57.0: dependencies: dompurify: 3.4.15 @@ -9146,18 +7715,8 @@ snapshots: node-releases@2.0.56: {} - non-error@0.1.0: {} - - normalize-path@3.0.0: {} - - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - object-assign@4.1.1: {} - object-inspect@1.13.4: {} - obug@2.2.1: {} optionator@0.9.4: @@ -9225,8 +7784,6 @@ snapshots: package-manager-detector@1.8.0: {} - pako@1.0.11: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -9248,17 +7805,6 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parse-ms@4.0.0: {} - - parse5-htmlparser2-tree-adapter@7.1.0: - dependencies: - domhandler: 5.0.3 - parse5: 7.3.0 - - parse5-parser-stream@7.1.2: - dependencies: - parse5: 7.3.0 - parse5@7.3.0: dependencies: entities: 6.0.1 @@ -9271,18 +7817,8 @@ snapshots: path-parse@1.0.7: {} - path-scurry@3.0.0: - dependencies: - lru-cache: 11.5.3 - minipass: 7.1.3 - path-type@4.0.0: {} - pathe@2.0.3: {} - - pend@1.2.0: - optional: true - picocolors@1.1.1: {} picomatch@4.0.7: {} @@ -9297,21 +7833,6 @@ snapshots: prettier@3.9.8: {} - pretty-format@30.5.1: - dependencies: - '@jest/react-is-18': react-is@18.3.1 - '@jest/react-is-19': react-is@19.3.0 - '@jest/schemas': 30.5.0 - ansi-styles: 5.2.0 - - pretty-ms@9.3.1: - dependencies: - parse-ms: 4.0.0 - - process-nextick-args@2.0.1: {} - - process@0.11.10: {} - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -9336,8 +7857,6 @@ snapshots: dependencies: react: 19.3.1 - query-selector-shadow-dom@1.0.1: {} - react-dom@19.3.1(react@19.3.1): dependencies: react: 19.3.1 @@ -9366,8 +7885,6 @@ snapshots: react-is@16.13.1: {} - react-is@18.3.1: {} - react-is@19.3.0: {} react-markdown@10.1.0(@types/react@19.3.0)(react@19.3.1)(supports-color@7.2.0): @@ -9407,28 +7924,6 @@ snapshots: react@19.3.1: {} - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - readable-stream@4.7.0: - dependencies: - abort-controller: 3.0.0 - buffer: 6.0.3 - events: 3.3.0 - process: 0.11.10 - string_decoder: 1.3.0 - - readdir-glob@3.0.0: - dependencies: - minimatch: 10.2.6 - readdirp@5.1.1: {} regenerate-unicode-properties@10.2.2: @@ -9495,10 +7990,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - ret@0.5.0: {} - - rgb2hex@0.2.5: {} - rolldown@1.2.16: dependencies: '@oxc-project/types': 0.156.0 @@ -9520,18 +8011,6 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.16 '@rolldown/binding-win32-x64-msvc': 1.2.16 - safaridriver@3.0.0: {} - - safe-buffer@5.1.2: {} - - safe-buffer@5.2.1: {} - - safe-regex2@5.1.1: - dependencies: - ret: 0.5.0 - - safer-buffer@2.1.2: {} - sass@1.104.1: dependencies: chokidar: 5.0.0 @@ -9548,15 +8027,8 @@ snapshots: semver@7.8.5: {} - serialize-error@13.0.2: - dependencies: - non-error: 0.1.0 - type-fest: 5.10.0 - server-only@0.0.1: {} - setimmediate@1.0.5: {} - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -9565,8 +8037,6 @@ snapshots: siginfo@2.0.0: {} - slash@3.0.0: {} - smol-toml@1.9.1: {} snake-case@3.0.4: @@ -9587,63 +8057,23 @@ snapshots: space-separated-tokens@2.0.2: {} - spacetrim@0.11.59: {} - - split2@4.2.0: {} - stable-hash-x@0.2.0: {} - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 - stackback@0.0.2: {} state-local@1.0.7: {} std-env@4.2.0: {} - streamx@2.28.1: - dependencies: - events-universal: 1.0.1 - fast-fifo: 1.3.2 - text-decoder: 1.2.7 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - string-argv@0.3.2: {} string-ts@2.3.1: {} - string-width@7.2.0: - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.7.0 - strip-ansi: 7.2.0 - - string-width@8.3.0: - dependencies: - get-east-asian-width: 1.7.0 - strip-ansi: 7.2.0 - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.3.0 - strip-json-comments@5.0.3: {} style-to-js@1.1.21: @@ -9659,6 +8089,7 @@ snapshots: supports-color@7.2.0: dependencies: has-flag: 4.0.0 + optional: true supports-preserve-symlinks-flag@1.0.0: {} @@ -9672,19 +8103,6 @@ snapshots: systemjs@6.15.1: {} - tagged-tag@1.0.0: {} - - tar-stream@3.2.1: - dependencies: - b4a: 1.9.0 - bare-fs: 4.8.1 - fast-fifo: 1.3.2 - streamx: 2.28.1 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a - tar@7.5.22: dependencies: '@isaacs/fs-minipass': 4.0.1 @@ -9697,13 +8115,6 @@ snapshots: dependencies: '@tauri-apps/api': 2.12.4 - teex@1.0.1: - dependencies: - streamx: 2.28.1 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - terser@5.51.2: dependencies: '@jridgewell/source-map': 0.3.11 @@ -9711,12 +8122,6 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - text-decoder@1.2.7: - dependencies: - b4a: 1.9.0 - transitivePeerDependencies: - - react-native-b4a - timers-ext@0.1.8: dependencies: es5-ext: 0.10.64 @@ -9733,8 +8138,6 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 - tinyrainbow@3.1.1: {} - trim-lines@3.0.1: {} trough@2.2.0: {} @@ -9763,12 +8166,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@4.26.0: {} - - type-fest@5.10.0: - dependencies: - tagged-tag: 1.0.0 - type@2.7.3: {} types-pac@1.0.3: {} @@ -9788,16 +8185,10 @@ snapshots: unbash@6.0.4: {} - undici-types@6.21.0: {} - undici-types@8.9.0: {} undici@6.29.1: {} - undici@7.30.1: {} - - undici@8.12.3: {} - unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: @@ -9881,16 +8272,10 @@ snapshots: dependencies: punycode: 2.3.1 - urlpattern-polyfill@10.1.0: {} - use-sync-external-store@1.7.0(react@19.3.1): dependencies: react: 19.3.1 - userhome@1.0.1: {} - - util-deprecate@1.0.2: {} - validator@13.15.35: {} vfile-location@5.0.3: @@ -9969,86 +8354,10 @@ snapshots: vscode-uri@3.2.0: {} - wait-port@1.1.0(supports-color@7.2.0): - dependencies: - chalk: 4.1.2 - commander: 9.5.0 - debug: 4.4.3(supports-color@7.2.0) - transitivePeerDependencies: - - supports-color - walk-up-path@4.0.0: {} - wcwidth@1.0.1: - dependencies: - defaults: 1.0.4 - optional: true - web-namespaces@2.0.1: {} - webdriver@10.2.0(supports-color@7.2.0)(yauzl@2.10.0): - dependencies: - '@types/node': 22.20.5 - '@types/ws': 8.18.1 - '@wdio/config': 10.2.0(supports-color@7.2.0)(yauzl@2.10.0) - '@wdio/logger': 10.2.0 - '@wdio/protocols': 10.2.0 - '@wdio/types': 10.2.0 - '@wdio/utils': 10.2.0(supports-color@7.2.0)(yauzl@2.10.0) - deepmerge-ts: 8.0.2 - https-proxy-agent: 9.1.0(supports-color@7.2.0) - undici: 8.12.3 - ws: 8.22.2 - transitivePeerDependencies: - - bufferutil - - kerberos - - proxy-agent - - supports-color - - utf-8-validate - - yauzl - - webdriverio@10.2.0(supports-color@7.2.0)(yauzl@2.10.0): - dependencies: - '@types/node': 22.20.5 - '@types/sinonjs__fake-timers': 15.0.1 - '@wdio/config': 10.2.0(supports-color@7.2.0)(yauzl@2.10.0) - '@wdio/logger': 10.2.0 - '@wdio/protocols': 10.2.0 - '@wdio/repl': 10.2.0 - '@wdio/types': 10.2.0 - '@wdio/utils': 10.2.0(supports-color@7.2.0)(yauzl@2.10.0) - archiver: 8.0.0 - aria-query: 5.3.2 - cheerio: 1.2.0 - css-shorthand-properties: 1.1.2 - css-value: 0.0.1 - grapheme-splitter: 1.0.4 - htmlfy: 0.8.1 - is-plain-obj: 4.1.0 - jszip: 3.10.2 - lodash.clonedeep: 4.5.0 - query-selector-shadow-dom: 1.0.1 - rgb2hex: 0.2.5 - serialize-error: 13.0.2 - urlpattern-polyfill: 10.1.0 - webdriver: 10.2.0(supports-color@7.2.0)(yauzl@2.10.0) - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - bufferutil - - kerberos - - proxy-agent - - react-native-b4a - - supports-color - - utf-8-validate - - yauzl - - whatwg-encoding@3.1.1: - dependencies: - iconv-lite: 0.6.3 - - whatwg-mimetype@4.0.0: {} - which@2.0.2: dependencies: isexe: 2.0.0 @@ -10060,16 +8369,6 @@ snapshots: word-wrap@1.2.5: {} - wrap-ansi@9.0.2: - dependencies: - ansi-styles: 6.2.3 - string-width: 7.2.0 - strip-ansi: 7.2.0 - - ws@8.22.2: {} - - y18n@5.0.8: {} - yallist@3.1.1: {} yallist@5.0.0: {} @@ -10078,31 +8377,8 @@ snapshots: yaml@2.9.1: {} - yargs-parser@22.0.0: {} - - yargs@18.2.0: - dependencies: - cliui: 9.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - string-width: 8.3.0 - y18n: 5.0.8 - yargs-parser: 22.0.0 - - yauzl@2.10.0: - dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 - optional: true - yocto-queue@0.1.0: {} - zip-stream@7.0.5: - dependencies: - compress-commons: 7.0.1 - normalize-path: 3.0.0 - readable-stream: 4.7.0 - zod-validation-error@3.5.4(zod@3.25.76): dependencies: zod: 3.25.76 diff --git a/renovate.json b/renovate.json index 1a3515fa0..a2c6134d1 100644 --- a/renovate.json +++ b/renovate.json @@ -50,12 +50,7 @@ }, { "description": "Update Tauri npm packages and Cargo crates together so frontend and backend stay in sync", - "matchPackageNames": [ - "@tauri-apps/**", - "@wdio/tauri-service", - "tauri", - "tauri-*" - ], + "matchPackageNames": ["@tauri-apps/**", "tauri", "tauri-*"], "groupName": "tauri", "addLabels": ["tauri"] }, diff --git a/scripts/perf/AGENTS.md b/scripts/perf/AGENTS.md new file mode 100644 index 000000000..749d307ae --- /dev/null +++ b/scripts/perf/AGENTS.md @@ -0,0 +1,28 @@ +# Product Measurement Adapter Guidelines + +[GUIDE.md](GUIDE.md) defines the product adapter boundary and explicit tool invocation. +Keep real component imports, initialization and the isolated native entry here, +using product frontend/native build dependencies. Do not copy components into the +tool. Preserve perf build gates; ordinary builds must not require the external tool. + +The adapter must not load or modify daily configuration or control the daily app, +Core, Service, TUN, DNS or system proxy. Preserve loopback isolation, owned-process +PID/start attribution, nonactivation, visibility/occlusion and source continuity. +Do not weaken gates or substitute Chrome/Vite for native WebView evidence. Label +JavaScript adapters separately from native plugin/IPC paths. Expected startup errors +must use the owned app's normal nonzero exit, never panic through Tauri setup. + +Review adapter source changes, including initialization and native/build configuration, +for changed measurement meaning. Matching interfaces alone do not prove equivalence; +an adapter review cannot waive other acceptance conditions. + +For implementation changes, run scoped ESLint, both perf TypeScript projects +(`tsconfig.json` and `tsconfig.node.json`), formatting and relevant production checks, +including Rust checks for native changes. Run scoped Rust Clippy before freezing +the optimized artifact. Check ordinary-build exclusion and application-entry prerequisites. + +Follow the external AGENTS.md's +[measurement-validation requirements](https://github.com/clash-verge-rev/clash-verge-rev-harness/blob/main/AGENTS.md) +and README's +[validation order](https://github.com/clash-verge-rev/clash-verge-rev-harness/blob/main/README.md#validate-before-freezing-a-build). +These govern affected native coverage, comparison, failure cleanup and evidence reuse. diff --git a/scripts/perf/GUIDE.md b/scripts/perf/GUIDE.md index 9623b4863..fdf7d5b7c 100644 --- a/scripts/perf/GUIDE.md +++ b/scripts/perf/GUIDE.md @@ -1,140 +1,35 @@ -# macOS Traffic Performance Harness +# Product measurement adapter -This guide is the operating and maintenance contract for humans and coding agents. -The only scenario is the production home traffic graph in a dedicated, optimized -Tauri WKWebView. It measures foreground updates, native hide, then restoration. -It does not initialize the complete homepage or run Mihomo, Service or networking. +This directory owns the isolated native entry, real Traffic/Logs/Connections +imports, initialization, JavaScript socket adapter, replay receiver and minimal DOM +layer. These evolve with product APIs and use the product's React/Tauri/Wry +versions. Keep the explicit perf build gates; ordinary builds need no external tool. -## Build And Run - -Use this repository's pnpm, Rust and Xcode Command Line Tools on macOS. No global -WebDriver installation is needed. Keep each binary beside its generated manifest. +The external [clash-verge-rev-harness operating guide](https://github.com/clash-verge-rev/clash-verge-rev-harness/blob/main/README.md) +owns running, sampling, reports, comparison and owned cleanup. Follow its +[first setup and installation instructions](https://github.com/clash-verge-rev/clash-verge-rev-harness/blob/main/README.md#build-and-run), +reusing an existing checkout. For subsequent calls, pass both absolute roots: ```sh -pnpm install --frozen-lockfile -pnpm perf:build target/perf/baseline-build -caffeinate -di pnpm perf:run -- --binary target/perf/baseline-build/clash-verge --output target/perf/baseline --repeat 3 --seconds 20 --warmup 10 -pnpm perf:compare -- target/perf/baseline target/perf/baseline -pnpm perf:test +project=/absolute/path/to/clash-verge-rev +harness=/absolute/path/to/clash-verge-rev-harness +node "$harness/build.mjs" --project "$project" --output target/perf/build +caffeinate -di node "$harness/run.mjs" --project "$project" --binary target/perf/build/clash-verge --output target/perf/smoke --repeat 1 --seconds 5 --warmup 2 +node "$harness/compare.mjs" "$project/target/perf/smoke" "$project/target/perf/smoke" ``` -`perf:build [directory]` uses release optimization, Vite `--mode perf` and the -explicit Cargo `perf-harness` feature. It preserves the binary and records HEAD, -dirty state, compiled source, lockfile, frontend, build-settings and binary hashes. -Source changes during compilation reject the manifest. Each run also hashes the -external collector and runner. Ordinary builds exclude the driver and measurement -entry; debug assertions are not the gate. Do not edit compiled inputs during builds. +JavaScript socket replacement does not measure native plugin IPC. DOM mutation/text +hashes show view-input delivery, not painting or cache ownership. Adapter changes +require reviewing measurement meaning under the external validation requirements. -Output directories must be new and Git ignored; use `target/perf/`. JSON/CSV keep -raw process samples, observations, phase markers, attribution and cleanup evidence. -`summary.json` and `report.md` provide distributions by independent run, phase and -process role. Failed runs exit nonzero and retain their evidence. The explicit -`traffic-webview-v3` schema rejects older reports instead of silently migrating them. +For virtual display mode, this adapter places only the hidden measurement window +on the exact owned NSScreen, checks complete visibleFrame containment, mode/Space +flags and nonactivation, and exits on drift. The tool owns helper lifecycle and topology +restoration; see its +[virtual display guide](https://github.com/clash-verge-rev/clash-verge-rev-harness/blob/main/README.md#optional-virtual-display). -## Isolation And Visibility - -The dedicated measurement main bypasses normal initialization and shutdown. It -does not read or write daily configuration, subscriptions, proxy, TUN or Service. -It uses a distinct app identity and incognito WebView. Both automatic services -bind loopback and are owned by the current run; never kill processes by name. - -The measurement window stays above ordinary windows and covers part of the screen, -without activation or mouse capture: clicks pass through to the application behind it. -Native topmost and click-through states are verified, while native focus and application -activation must remain false. WebDriver uses the embedded WebView directly; DOM focus -is recorded but not required. Daily windows are unchanged; the topmost window exits -with its owned measurement instance. You may keep using another application. -The console must be unlocked. Lock state is checked before startup and recorded -at phase boundaries; unknown state fails and observed drift rejects comparison. -Boundary checks do not provide continuous lock monitoring or unlock the computer. - -No focus does not mean no visibility: shown stages require `document.hidden=false`, -fresh Worker data, advancing Canvas draw timestamps and changed Canvas pixels. -Topmost placement avoids ordinary-window occlusion; system overlays or another Space -can still pause WebKit. Such a run fails; do not weaken Canvas assertions to make it -pass. `caffeinate -di` lasts only for the command and cannot prevent deliberate lock. -Hidden stages still require continuous input and Worker data. - -## Fixed Workload - -The native window is 1000 x 700 logical pixels. Verify driver dimensions against -DPR and record the actual CSS viewport; titlebar space is excluded. The graph is -920 x 360 with a ten-minute range, default bezier style and real data retention. -The measurement preload sets blur pause false without changing production defaults. - -Deterministic loopback WebSocket replay enters the public traffic append boundary, -then the production Worker and Canvas. It does not cover Mihomo or network traffic. -There is no history prefill. Absolute 200 ms deadlines sustain 5 Hz without cumulative -timer drift or catch-up bursts; missing a complete period fails the source. -Warmup is at least the requested duration with a five-second readiness allowance. -Fixed input anchors reserve two seconds for each native transition; measured stages -retain their full duration. Readiness uses observable state and deadlines, not sleep. -Before the first show, wait for actual replay input and Worker data so the graph's -visibility listeners are installed; then require fresh Canvas output after showing. -Total replay, including allowances, must remain below ten minutes for raw history. -Two-input endpoint variation is 400 ms; larger differences reject comparison. - -Worker snapshots are asynchronous. Count/latest-value checks verify deterministic -upload/download values at the retained point count, with a joint period of 60 and -received-minus-retained count below 60. This detects net missing/duplicate points, -not every historical element or exactly cancelling losses and duplicates. -Source age must be below 2500 ms, Worker age below 3500 ms and visible draw age below -2500 ms. Future timestamps, lost sequence, process changes and failed restore fail. - -## Metrics And Comparison - -The native command obtains the actual WKWebView process ID using the private -`_webProcessIdentifier` selector, with availability checking. External collection -verifies the canonical system WebContent executable and process start identity. -It handles PID reuse and exits; unsupported ownership fails closed. Main and WebView -remain separate. Mihomo is not started; shared GPU/network attribution is unavailable. - -CPU is cumulative process time converted from Mach ticks; interval percentages use -adjacent valid samples, with one occupied core equal to 100%. The first difference -is unavailable. Memory is RSS bytes, not physical footprint or exclusive memory. -Reports include CPU mean/P50/P95/peak/seconds, RSS median/P95/peak/delta, sample count -and time coverage. Memory slope requires at least 60 seconds of continuous samples. -RSS growth is not proof of a leak; process statistics are not function profiles. -The embedded driver adds a 20 Hz macOS runloop timer and observation overhead. - -For A/B, build the candidate separately and alternate independent instances: - -```sh -pnpm perf:build target/perf/candidate-build -for i in 1 2 3; do - caffeinate -di pnpm perf:run -- --binary target/perf/baseline-build/clash-verge --repeat 1 --output target/perf/ab-baseline/$i - caffeinate -di pnpm perf:run -- --binary target/perf/candidate-build/clash-verge --repeat 1 --output target/perf/ab-candidate/$i -done -pnpm perf:compare -- target/perf/ab-baseline target/perf/ab-candidate -pnpm perf:run -- --binary target/perf/candidate-build/clash-verge --repeat 1 --seconds 5 --warmup 2 --fail-phase hidden --output target/perf/failed -``` - -Comparison accepts summary files, run directories or groups of run directories. -Build mode, toolchain, platform, host, power, WebKit, viewport and workload must match. -Invalid and incomparable reports exit 2. A/A remains inconclusive. A/B compares -independent-run summaries, not adjacent time samples. An optional third numeric -argument is a percentage floor justified by baseline noise; there is no universal -pass threshold. Fewer than three runs per group, overlapping ranges or differences -within either group's range remain inconclusive. Background load limits inference. - -## Automation Delivery Boundary - -When the operator is a coding agent, the final change keeps only the -performance-optimization code. Harness additions introduced during the -work — measurement entries, configs, scripts, reports, manifests — must -stay untracked by Git and out of every commit; keep them on ignored -paths or remove them before delivery. Commits remain minimal and -necessary for the optimization itself. Track or commit harness files -only when a human explicitly requests it at the end. - -## Maintenance And Acceptance - -Keep measurement code behind the explicit build gates. Reuse the existing driver, -Worker, graph, attribution and cleanup; do not add mock IPC or replace real WebView -acceptance with Chrome/Vite. Preserve source/binary/report hashes and every failed run. -After changes, run scoped ESLint, both perf TypeScript projects, `pnpm perf:test`, -format checks and relevant production checks. Verify a real three-phase smoke, -three independent runs, A/A, invalid/incomparable reports and injected-failure cleanup. -Verify ordinary builds exclude measurement code and daily instance state is intact. -Changes to contracts require a schema bump and new reports. Use bounded WebKit CPU -or Allocation/Heap captures for deeper diagnosis; do not infer hotspots from counters. +`isolation.json` owns the product configuration and process watch scope; keep it +aligned with product data directories and executable names. The tool owns +[command isolation snapshots](https://github.com/clash-verge-rev/clash-verge-rev-harness/blob/main/README.md#command-isolation-snapshots). +The adapter must not load daily configuration or control Core, Service, TUN, DNS +or system proxy. diff --git a/scripts/perf/adapter.ts b/scripts/perf/adapter.ts new file mode 100644 index 000000000..2a4a5ead1 --- /dev/null +++ b/scripts/perf/adapter.ts @@ -0,0 +1,57 @@ +import { MihomoWebSocket } from 'tauri-plugin-mihomo-api' + +import { subscribe, scenario } from './replay' + +// Only this perf entry replaces socket transport; the normal application never imports it. +export function installAdapter() { + const connect = async (kind: string) => { + const data = document.documentElement.dataset + data.perfSubscriptions = String(Number(data.perfSubscriptions || 0) + 1) + const callbacks = new Set< + (message: { type: 'Text'; data: string }) => void + >() + const unsubscribe = subscribe((frame) => { + const messages = + kind === 'logs' + ? (frame.payload as ILogItem[]) + : [ + kind === 'traffic' + ? { up: frame.sequence * 1000, down: frame.sequence * 5000 } + : frame.payload, + ] + for (const payload of messages) { + const message = { type: 'Text' as const, data: JSON.stringify(payload) } + callbacks.forEach((callback) => { + callback(message) + data.perfDelivered = String(Number(data.perfDelivered || 0) + 1) + }) + } + }) + return { + addListener: ( + callback: (message: { type: 'Text'; data: string }) => void, + ) => { + callbacks.add(callback) + return () => callbacks.delete(callback) + }, + close: async () => { + unsubscribe() + callbacks.clear() + data.perfSubscriptions = String(Number(data.perfSubscriptions) - 1) + }, + } as unknown as MihomoWebSocket + } + MihomoWebSocket.connect_logs = async () => connect('logs') + MihomoWebSocket.connect_connections = async () => connect('connections') + MihomoWebSocket.connect_traffic = async () => connect('traffic') + if (scenario === 'logs') + localStorage.setItem( + 'clash-log', + JSON.stringify({ + enable: true, + logLevel: 'INFO', + logFilter: 'all', + logOrder: 'desc', + }), + ) +} diff --git a/scripts/perf/app.rs b/scripts/perf/app.rs index cdd2245b5..546743d58 100644 --- a/scripts/perf/app.rs +++ b/scripts/perf/app.rs @@ -1,5 +1,136 @@ use objc2::{class, msg_send, runtime::AnyObject, sel}; -use tauri::WebviewWindow; +use objc2_foundation::{NSPoint, NSRect, ns_string}; +use tauri::{Manager as _, WebviewWindow}; + +#[link(name = "CoreGraphics", kind = "framework")] +unsafe extern "C" { + fn CGDisplayCopyDisplayMode(display: u32) -> *mut std::ffi::c_void; + fn CGDisplayModeGetRefreshRate(mode: *mut std::ffi::c_void) -> f64; + fn CGDisplayModeRelease(mode: *mut std::ffi::c_void); + fn CGMainDisplayID() -> u32; + fn CGDisplayIsInMirrorSet(display: u32) -> u32; + fn CGDisplayModeGetWidth(mode: *mut std::ffi::c_void) -> usize; + fn CGDisplayModeGetHeight(mode: *mut std::ffi::c_void) -> usize; + fn CGDisplayModeGetPixelWidth(mode: *mut std::ffi::c_void) -> usize; + fn CGDisplayModeGetPixelHeight(mode: *mut std::ffi::c_void) -> usize; +} + +fn expected_display() -> Result, String> { + match std::env::var("PERF_DISPLAY_MODE").as_deref() { + Ok("physical") | Err(std::env::VarError::NotPresent) => Ok(None), + Ok("virtual") => { + let id = std::env::var("PERF_DISPLAY_ID") + .map_err(|e| e.to_string())? + .parse::() + .map_err(|e| e.to_string())?; + if id == 0 { + return Err("invalid owned display ID".into()); + } + Ok(Some(id)) + } + _ => Err("unknown measurement display mode".into()), + } +} +const fn bounds(rect: NSRect) -> [f64; 4] { + [rect.origin.x, rect.origin.y, rect.size.width, rect.size.height] +} +fn contained(frame: NSRect, screen: NSRect) -> bool { + frame.origin.x >= screen.origin.x + && frame.origin.y >= screen.origin.y + && frame.origin.x + frame.size.width <= screen.origin.x + screen.size.width + && frame.origin.y + frame.size.height <= screen.origin.y + screen.size.height +} +unsafe fn screen_id(screen: *mut AnyObject) -> u32 { + unsafe { + if screen.is_null() { + return 0; + } + let dictionary: *mut AnyObject = msg_send![screen, deviceDescription]; + let value: *mut AnyObject = msg_send![dictionary, objectForKey: ns_string!("NSScreenNumber")]; + msg_send![value, unsignedIntValue] + } +} +unsafe fn display_state(native: &AnyObject) -> Result, String> { + let Some(wanted) = expected_display()? else { + return Ok(None); + }; + unsafe { + let screen: *mut AnyObject = msg_send![native, screen]; + let id = screen_id(screen); + if id == 0 || id != wanted || id == CGMainDisplayID() || CGDisplayIsInMirrorSet(id) != 0 { + return Err("owned virtual screen unavailable".into()); + } + let frame: NSRect = msg_send![screen, frame]; + let visible: NSRect = msg_send![screen, visibleFrame]; + let window_frame: NSRect = msg_send![native, frame]; + let scale: f64 = msg_send![screen, backingScaleFactor]; + let behavior: usize = msg_send![native, collectionBehavior]; + let mode = CGDisplayCopyDisplayMode(id); + if mode.is_null() { + return Err("owned display mode unavailable".into()); + } + let rate = CGDisplayModeGetRefreshRate(mode); + let logical = [CGDisplayModeGetWidth(mode), CGDisplayModeGetHeight(mode)]; + let pixels = [CGDisplayModeGetPixelWidth(mode), CGDisplayModeGetPixelHeight(mode)]; + CGDisplayModeRelease(mode); + if logical != [1280, 900] + || pixels != [2560, 1800] + || scale != 2.0 + || (rate - 60.0).abs() > 0.01 + || frame.size.width != 1280.0 + || frame.size.height != 900.0 + || !contained(window_frame, visible) + || behavior & 1 == 0 + || behavior & 2 != 0 + { + return Err("owned virtual screen geometry/mode changed".into()); + } + Ok(Some( + serde_json::json!({"id": id, "frame": bounds(frame), "visibleFrame": bounds(visible), "windowFrame": bounds(window_frame), "scale": scale, "refreshHz": rate, "collectionBehavior": behavior, "contained": true, "main": false, "mirrored": false, "logical": logical, "pixels": pixels}), + )) + } +} +unsafe fn target_screen(wanted: u32) -> Result<*mut AnyObject, String> { + unsafe { + let screens: *mut AnyObject = msg_send![class!(NSScreen), screens]; + let count: usize = msg_send![screens, count]; + let mut target = std::ptr::null_mut::(); + for index in 0..count { + let screen: *mut AnyObject = msg_send![screens, objectAtIndex: index]; + if screen_id(screen) == wanted { + target = screen; + break; + } + } + if target.is_null() || wanted == CGMainDisplayID() { + return Err("exact owned virtual display missing".into()); + } + Ok(target) + } +} +unsafe fn place_virtual(window: &WebviewWindow) -> Result<(), String> { + unsafe { + let Some(wanted) = expected_display()? else { + return Ok(()); + }; + let target = target_screen(wanted)?; + let native = &*(window.ns_window().map_err(|e| e.to_string())? as *const AnyObject); + let visible: NSRect = msg_send![target, visibleFrame]; + let frame: NSRect = msg_send![native, frame]; + if frame.size.width > visible.size.width || frame.size.height > visible.size.height { + return Err("owned window does not fit virtual visibleFrame".into()); + } + let behavior: usize = msg_send![native, collectionBehavior]; + let _: () = msg_send![native, setCollectionBehavior: (behavior & !2usize) | 1usize]; + let origin = NSPoint::new( + visible.origin.x + (visible.size.width - frame.size.width) / 2.0, + visible.origin.y + (visible.size.height - frame.size.height) / 2.0, + ); + let _: () = msg_send![native, setFrameOrigin: origin]; + display_state(native)?; + Ok(()) + } +} #[tauri::command] async fn perf_state(window: WebviewWindow, action: String) -> Result { @@ -7,16 +138,28 @@ async fn perf_state(window: WebviewWindow, action: String) -> Result(); + let display = match display_state(native) { + Ok(state) => state, + Err(error) => { + let _: () = msg_send![native, orderOut: sender]; + let _ = tx.send(Err(error)); + fail_app.exit(2); + return; + } + }; if action == "show" { let _: () = msg_send![native, orderBack: sender]; } else if action == "hide" { let _: () = msg_send![native, orderOut: sender]; } let visible: bool = msg_send![native, isVisible]; + let occlusion: usize = msg_send![native, occlusionState]; + let active_space: bool = msg_send![native, isOnActiveSpace]; let focused: bool = msg_send![native, isKeyWindow]; let click_through: bool = msg_send![native, ignoresMouseEvents]; let app: *const AnyObject = msg_send![class!(NSApplication), sharedApplication]; @@ -28,21 +171,41 @@ async fn perf_state(window: WebviewWindow, action: String) -> Result Vec { + Vec::new() +} + +#[tauri::command] +fn get_verge_config() -> serde_json::Value { + serde_json::json!({"language": "en", "theme_mode": "light"}) +} + pub fn run() -> std::process::ExitCode { #[cfg(feature = "clippy")] let context = tauri::test::mock_context(tauri::test::noop_assets()); @@ -50,23 +213,66 @@ pub fn run() -> std::process::ExitCode { let context = tauri::generate_context!(); let result = tauri::Builder::default() .plugin(tauri_plugin_wdio_webdriver::init()) - .invoke_handler(tauri::generate_handler![perf_state]) + .invoke_handler(tauri::generate_handler![perf_state, get_clash_logs, get_verge_config]) .setup(|app| { - app.handle().set_activation_policy(tauri::ActivationPolicy::Accessory)?; - let port: u16 = std::env::var("PERF_REPLAY_PORT")?.parse()?; - tauri::WebviewWindowBuilder::new( - app, - "perf", - tauri::WebviewUrl::App(format!("index.html?port={port}").into()), - ) - .title("Clash Verge - performance replay") - .inner_size(1000., 700.) - .visible(false) - .focused(false) - .always_on_top(true) - .incognito(true) - .build()? - .set_ignore_cursor_events(true)?; + let startup = (|| -> Result<(), Box> { + app.handle() + .set_activation_policy(tauri::ActivationPolicy::Prohibited)?; + let scenario = std::env::var("PERF_SCENARIO").unwrap_or_else(|_| "traffic".into()); + let layer = std::env::var("PERF_LAYER").unwrap_or_else(|_| "component".into()); + if !matches!(scenario.as_str(), "traffic" | "logs" | "connections") + || !matches!(layer.as_str(), "page" | "component" | "dom") + { + return Err("invalid perf scene".into()); + } + if let Some(wanted) = expected_display().map_err(std::io::Error::other)? { + unsafe { + target_screen(wanted).map_err(std::io::Error::other)?; + } + } + let port: u16 = std::env::var("PERF_REPLAY_PORT")?.parse()?; + let window = tauri::WebviewWindowBuilder::new( + app, + "perf", + tauri::WebviewUrl::App(format!("index.html?port={port}&scenario={scenario}&layer={layer}").into()), + ) + .title("Clash Verge - performance replay") + .inner_size(1000., 700.) + .visible(false) + .focused(false) + .focusable(false) + .always_on_top(true) + .incognito(true) + .build()?; + window.set_ignore_cursor_events(true)?; + unsafe { + place_virtual(&window).map_err(std::io::Error::other)?; + } + if expected_display().map_err(std::io::Error::other)?.is_some() { + let handle = app.handle().clone(); + window.on_window_event(move |event| { + if matches!( + event, + tauri::WindowEvent::Moved(_) | tauri::WindowEvent::ScaleFactorChanged { .. } + ) && let Some(window) = handle.get_webview_window("perf") + { + let valid = window + .ns_window() + .ok() + .is_some_and(|native| unsafe { display_state(&*(native as *const AnyObject)).is_ok() }); + if !valid { + let _ = window.hide(); + handle.exit(2); + } + } + }); + } + Ok(()) + })(); + if let Err(error) = startup { + eprintln!("Measurement startup failed: {error}"); + app.handle().exit(1); + } Ok(()) }) .build(context); diff --git a/scripts/perf/build.mjs b/scripts/perf/build.mjs deleted file mode 100644 index a8e39dff2..000000000 --- a/scripts/perf/build.mjs +++ /dev/null @@ -1,107 +0,0 @@ -import { execFileSync } from 'node:child_process' -import { createHash } from 'node:crypto' -import { - copyFileSync, - existsSync, - mkdirSync, - readFileSync, - writeFileSync, -} from 'node:fs' -import { resolve } from 'node:path' -const output = resolve(process.argv[2] ?? 'target/perf/build') -const hash = (bytes) => createHash('sha256').update(bytes).digest('hex') -const command = (cmd, args) => - execFileSync(cmd, args, { encoding: 'utf8' }).trim() -const source = () => - command('git', ['ls-files', '-co', '--exclude-standard']) - .split('\n') - .filter(existsSync) - .filter( - (path) => - !path.startsWith('scripts/perf/') || - /\.(rs|tsx|html|mts|json)$/.test(path), - ) - .sort() - .map((path) => `${path}\0${hash(readFileSync(path))}`) - .join('\n') -mkdirSync(output, { recursive: true }) -const before = hash(source()) -const buildSettingsSha256 = hash( - ['Cargo.toml', '.cargo/config.toml'] - .filter(existsSync) - .map((path) => `${path}\0${hash(readFileSync(path))}`) - .join('\n'), -) -execFileSync( - 'pnpm', - [ - 'exec', - 'vite', - 'build', - '--mode', - 'perf', - '--config', - 'scripts/perf/vite.mts', - ], - { stdio: 'inherit' }, -) -const mode = 'release + perf-harness (build settings hash recorded)' -const buildCommand = - 'TAURI_CONFIG= cargo build -p clash-verge --release --features perf-harness --locked --target-dir target/perf/cargo' -execFileSync( - 'cargo', - [ - 'build', - '-p', - 'clash-verge', - '--release', - '--features', - 'perf-harness', - '--locked', - '--target-dir', - 'target/perf/cargo', - ], - { - stdio: 'inherit', - env: { - ...process.env, - TAURI_CONFIG: readFileSync('scripts/perf/tauri.json', 'utf8'), - }, - }, -) -if (before !== hash(source())) - throw new Error('Source changed during build; refuse manifest') -const binary = `${output}/clash-verge` -copyFileSync('target/perf/cargo/release/clash-verge', binary) -const files = command('find', ['target/perf/dist', '-type', 'f']) - .split('\n') - .sort() -writeFileSync( - `${binary}.json`, - JSON.stringify( - { - head: command('git', ['rev-parse', 'HEAD']), - dirty: command('git', ['status', '--porcelain']), - sourceSha256: before, - binarySha256: hash(readFileSync(binary)), - frontendSha256: hash( - files.map((path) => `${path}\0${hash(readFileSync(path))}`).join('\n'), - ), - mode, - buildSettingsSha256, - buildEnvironment: Object.fromEntries( - Object.entries(process.env).filter(([key]) => - /^(RUSTFLAGS|CARGO_ENCODED_RUSTFLAGS|CARGO_PROFILE_|RUSTC_WRAPPER)/.test( - key, - ), - ), - ), - buildCommand, - binary, - toolchain: `${command('rustc', ['--version'])}; node ${process.version}; pnpm ${command('pnpm', ['--version'])}`, - }, - null, - 2, - ), -) -console.log(`Measurement binary and manifest: ${binary}`) diff --git a/scripts/perf/compare.mjs b/scripts/perf/compare.mjs deleted file mode 100644 index a061f1831..000000000 --- a/scripts/perf/compare.mjs +++ /dev/null @@ -1,41 +0,0 @@ -import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs' -import { join } from 'node:path' - -import { compare } from './report.mjs' -const args = process.argv.slice(2).filter((v) => v !== '--') -if (args.length < 2) - throw new Error( - 'Usage: pnpm perf:compare -- baseline/summary.json candidate/summary.json [noise-threshold-percent]', - ) -const threshold = args[2] === undefined ? null : Number(args[2]) -if (threshold !== null && (!Number.isFinite(threshold) || threshold < 0)) - throw new Error( - 'threshold must be a nonnegative percentage justified by baseline noise', - ) -function load(path) { - if (statSync(path).isFile()) return JSON.parse(readFileSync(path)) - if (existsSync(join(path, 'summary.json'))) - return load(join(path, 'summary.json')) - const reports = readdirSync(path) - .sort() - .map((name) => load(join(path, name))) - const first = reports[0] - if ( - !first || - reports.some( - (r) => - r.build?.binarySha256 !== first.build?.binarySha256 || - ['invalid', 'incomparable'].includes(compare(first, r).verdict), - ) - ) - throw new Error('group contains mixed builds, contracts, or invalid runs') - return { ...first, runs: reports.flatMap((r) => r.runs) } -} -let result -try { - result = compare(...args.slice(0, 2).map(load), threshold) -} catch (error) { - result = { verdict: 'invalid', reason: String(error) } -} -console.log(JSON.stringify(result, null, 2)) -if (['invalid', 'incomparable'].includes(result.verdict)) process.exitCode = 2 diff --git a/scripts/perf/entry.tsx b/scripts/perf/entry.tsx index 4fb70ac09..0f20c2e56 100644 --- a/scripts/perf/entry.tsx +++ b/scripts/perf/entry.tsx @@ -1,52 +1,29 @@ -import { ThemeProvider, createTheme } from '@mui/material' -import { useEffect } from 'react' -import { createRoot } from 'react-dom/client' -import { SWRConfig } from 'swr' +import { layer, scenario, startReplay } from './replay' -import { EnhancedCanvasTrafficGraph } from '../../src/components/home/enhanced-canvas-traffic-graph' -import { useTrafficMonitorEnhanced } from '../../src/hooks/use-traffic-monitor' -import { initializeLanguage } from '../../src/services/i18n' -import { setPreloadConfig } from '../../src/services/preload' - -setPreloadConfig({ - pause_render_traffic_stats_on_blur: false, -} as IVergeConfig) -await initializeLanguage('en') -export function Replay() { - const { - graphData: { appendData }, - } = useTrafficMonitorEnhanced({ subscribe: false }) - useEffect(() => { - let received = 0, - previous = 0, - gaps = 0 - const socket = new WebSocket( - `ws://127.0.0.1:${new URLSearchParams(location.search).get('port')}`, - ) - socket.onmessage = ({ data }) => { - const point = JSON.parse(data) - appendData(point) - if (previous && point.sequence !== previous + 1) gaps++ - previous = point.sequence - document.documentElement.dataset.perfReceived = String(++received) - document.documentElement.dataset.perfGaps = String(gaps) - document.documentElement.dataset.perfSourceTime = String(point.timestamp) +if (scenario === 'traffic') { + await import('./traffic') +} else { + const root = document.getElementById('root')! + document.body.style.cssText = 'margin:0;font:14px -apple-system,sans-serif' + root.style.cssText = 'width:1000px;height:700px;overflow:hidden' + new MutationObserver(() => { + document.documentElement.dataset.perfDraw = String(Date.now()) + }).observe(root, { childList: true, characterData: true, subtree: true }) + window.addEventListener('error', (event) => { + document.documentElement.dataset.perfError = event.message + }) + window.addEventListener('unhandledrejection', (event) => { + document.documentElement.dataset.perfError = String(event.reason) + }) + if (layer === 'dom') { + const { mount } = await import('./minimal') + mount(root) + } else { + if (layer === 'page') { + const { installAdapter } = await import('./adapter') + installAdapter() } - return () => socket.close() - }, [appendData]) - return ( -
- -
- ) + await import('./scenes') + } + startReplay() } -createRoot(document.getElementById('root')!).render( - - - - - , -) diff --git a/scripts/perf/isolation.json b/scripts/perf/isolation.json new file mode 100644 index 000000000..d441ea6e8 --- /dev/null +++ b/scripts/perf/isolation.json @@ -0,0 +1,13 @@ +{ + "configRoots": [ + "Library/Application Support/io.github.clash-verge-rev.clash-verge-rev", + "Library/Application Support/io.github.clash-verge-rev.clash-verge-rev.dev" + ], + "configDirectories": ["profiles"], + "configExtensions": [".yaml", ".yml", ".json", ".js"], + "processNames": { + "app": "clash-verge", + "core": "verge-mihomo", + "service": "clash-verge-service" + } +} diff --git a/scripts/perf/minimal.ts b/scripts/perf/minimal.ts new file mode 100644 index 000000000..ab08cf738 --- /dev/null +++ b/scripts/perf/minimal.ts @@ -0,0 +1,21 @@ +import { scenario, subscribe } from './replay' + +export function mount(root: HTMLElement) { + const rows = Array.from({ length: 20 }, () => { + const row = document.createElement('div') + row.style.cssText = 'height:32px;white-space:nowrap;overflow:hidden' + root.appendChild(row) + return row + }) + subscribe(({ payload }) => { + const texts = + scenario === 'logs' + ? (payload as ILogItem[]).map((log) => log.payload) + : (payload as IConnections).connections.map( + (connection) => connection.metadata.host, + ) + rows.forEach((row, index) => { + row.textContent = texts[index % texts.length] ?? '' + }) + }) +} diff --git a/scripts/perf/replay.ts b/scripts/perf/replay.ts new file mode 100644 index 000000000..4efc08339 --- /dev/null +++ b/scripts/perf/replay.ts @@ -0,0 +1,37 @@ +export interface Frame { + sequence: number + timestamp: number + payload: ILogItem[] | IConnections +} +export const params = new URLSearchParams(location.search) +export const scenario = params.get('scenario') ?? 'traffic' +export const layer = params.get('layer') ?? 'component' +const listeners = new Set<(frame: Frame) => void>() +export const subscribe = (listener: (frame: Frame) => void) => { + listeners.add(listener) + return () => { + listeners.delete(listener) + } +} +export function startReplay() { + const data = document.documentElement.dataset + let previous = 0 + data.perfReceived = '0' + data.perfGaps = '0' + const socket = new WebSocket(`ws://127.0.0.1:${params.get('port')}`) + socket.onmessage = ({ data: raw }) => { + const frame = JSON.parse(raw) as Frame + if (frame.sequence !== previous + 1) + data.perfGaps = String(Number(data.perfGaps) + 1) + previous = frame.sequence + data.perfReceived = String(previous) + data.perfSourceTime = String(frame.timestamp) + for (const listener of listeners) listener(frame) + } + socket.onerror = () => { + data.perfError = 'replay socket failed' + } + socket.onclose = () => { + data.perfError = 'replay socket closed' + } +} diff --git a/scripts/perf/report.mjs b/scripts/perf/report.mjs deleted file mode 100644 index 9ca534d6e..000000000 --- a/scripts/perf/report.mjs +++ /dev/null @@ -1,443 +0,0 @@ -import { isDeepStrictEqual } from 'node:util' - -export const SCHEMA = 'traffic-webview-v3' -export const phases = ['foreground', 'hidden', 'restored'] -export const roles = ['webview', 'main'] -export const quantile = (values, p) => { - const v = values.filter(Number.isFinite).sort((a, b) => a - b) - if (!v.length) return null - const index = (v.length - 1) * p - return ( - v[Math.floor(index)] + - (v[Math.ceil(index)] - v[Math.floor(index)]) * (index % 1) - ) -} -export function delta(previous, next) { - if ( - !previous || - previous.pid !== next.pid || - previous.start !== next.start || - ![previous.time, next.time, previous.cpu, next.cpu].every( - Number.isFinite, - ) || - next.time <= previous.time || - next.cpu < previous.cpu - ) - return null - return ((next.cpu - previous.cpu) / (next.time - previous.time)) * 100000 -} -export function summarize(rows, seconds) { - const output = {} - for (const phase of phases) - for (const role of roles) { - const selected = rows.filter((r) => r.phase === phase && r.role === role) - const samples = selected.map((r, i) => ({ - ...r, - percent: delta(selected[i - 1], r), - cpuDelta: i ? r.cpu - selected[i - 1].cpu : null, - })) - const valid = samples.filter((r) => Number.isFinite(r.percent)) - const memory = samples.map((r) => r.rss) - const span = - samples.length > 1 ? samples.at(-1).time - samples[0].time : 0 - const continuous = - samples.length > 1 && - samples.every( - (r) => - [r.time, r.cpu, r.rss].every(Number.isFinite) && - Number.isInteger(r.pid) && - r.pid > 0 && - /^\d+$/.test(r.start) && - r.time >= 0 && - r.cpu >= 0 && - r.rss > 0, - ) && - samples.every( - (r, i) => - !i || - (r.time > samples[i - 1].time && - r.time - samples[i - 1].time < 2500 && - r.cpu >= samples[i - 1].cpu && - r.pid === samples[i - 1].pid && - r.start === samples[i - 1].start), - ) - output[`${phase}/${role}`] = { - count: samples.length, - cpuCount: valid.length, - missing: Math.max(0, Math.floor(seconds) - samples.length), - coverage: Math.min(1, samples.length / seconds), - continuous, - observedSeconds: span / 1000, - intervalCoverage: Math.min(1, span / ((seconds - 1) * 1000)), - cpuMean: null, - cpuP50: quantile( - valid.map((r) => r.percent), - 0.5, - ), - cpuP95: quantile( - valid.map((r) => r.percent), - 0.95, - ), - cpuPeak: quantile( - valid.map((r) => r.percent), - 1, - ), - cpuSeconds: valid.length - ? valid.reduce((sum, r) => sum + r.cpuDelta, 0) - : null, - rssP50: quantile(memory, 0.5), - rssP95: quantile(memory, 0.95), - rssPeak: quantile(memory, 1), - rssDelta: continuous ? samples.at(-1).rss - samples[0].rss : null, - rssSlopeBytesPerSecond: - span >= 60000 && continuous - ? (samples.at(-1).rss - samples[0].rss) / (span / 1000) - : null, - } - output[`${phase}/${role}`].cpuMean = - valid.length && span > 0 - ? (output[`${phase}/${role}`].cpuSeconds / span) * 100000 - : null - } - return output -} -export function replayValues(sequence) { - return { - up: 100000 + (sequence % 20) * 10000, - down: 500000 + (sequence % 30) * 20000, - } -} -export const validVisibility = (s) => - typeof s.hidden === 'boolean' && - (s.phase === 'hidden' - ? s.visible === false && s.hidden - : phases.includes(s.phase) && s.visible === true && !s.hidden) -export const validObservation = (s) => - validWorkload(s) && - validVisibility(s) && - s.nativeFocused === false && - s.appActive === false && - s.top === true && - s.clickThrough === true && - [s.now, s.source, s.worker, s.draw].every( - (v) => Number.isFinite(v) && v > 0, - ) && - s.now >= s.source && - s.now - s.source < 2500 && - s.now >= s.worker && - s.now - s.worker < 3500 && - s.now >= s.draw && - (s.phase === 'hidden' || s.now - s.draw < 2500) -export function validWorkload(s) { - if (!s) return false - const expected = replayValues(s.points) - return ( - Number.isInteger(s.received) && - Number.isInteger(s.points) && - s.points > 0 && - s.received >= s.points && - s.received - s.points < 60 && - s.lastUp === expected.up && - s.lastDown === expected.down && - s.gaps === 0 - ) -} -export function compare(a, b, threshold = null) { - const stats = new Map() - const text = (value) => typeof value === 'string' && value.length > 0 - const object = (value) => - value && typeof value === 'object' && !Array.isArray(value) - const expectedMarks = [ - 'ready', - ...phases.flatMap((phase) => [phase, `${phase}:end`]), - ] - const valid = (x) => - x?.schema === SCHEMA && - text(x?.harnessSha256) && - [ - 'head', - 'sourceSha256', - 'binarySha256', - 'frontendSha256', - 'buildSettingsSha256', - 'mode', - 'buildCommand', - 'binary', - 'toolchain', - ].every((key) => text(x.build?.[key])) && - typeof x.build.dirty === 'string' && - object(x.build.buildEnvironment) && - [ - 'method', - 'mode', - 'toolchain', - 'buildSettingsSha256', - 'platform', - 'machine', - 'arch', - 'host', - 'hardware', - 'power', - 'powerSource', - 'webkit', - 'replay', - 'automation', - ].every((key) => text(x.contract?.[key])) && - object(x.contract.buildEnvironment) && - x.build.buildSettingsSha256 === x.contract.buildSettingsSha256 && - [ - 'warmup', - 'intervalMs', - 'transitionSeconds', - 'readinessAllowanceSeconds', - ].every((key) => Number.isFinite(x.contract[key]) && x.contract[key] > 0) && - x.contract.scenario === 'traffic' && - typeof x.contract.consoleLocked === 'boolean' && - x.contract.windowPolicy === 'nonactivating-top-click-through-v1' && - x.contract.replayMs === 200 && - x.contract.rangeMinutes === 10 && - ['window', 'viewport'].every( - (key) => - Array.isArray(x.contract[key]) && - x.contract[key].length === (key === 'window' ? 2 : 3) && - x.contract[key].every((v) => Number.isFinite(v) && v > 0), - ) && - Number.isFinite(x.contract?.seconds) && - x.contract.seconds >= 5 && - Array.isArray(x.contract.phaseTargets) && - x.contract.phaseTargets.length === phases.length && - x.contract.phaseTargets.every((n) => Number.isInteger(n) && n > 0) && - Array.isArray(x.runs) && - x.runs.length && - x.runs.every((r) => { - if ( - !r?.ok || - !Array.isArray(r.samples) || - !r.samples.every( - (s) => s && roles.includes(s.role) && phases.includes(s.phase), - ) || - !Array.isArray(r.marks) || - !r.marks.every((m) => m && typeof m.name === 'string') - ) - return false - stats.set(r, summarize(r.samples, x.contract.seconds)) - return ( - r.cleanup?.mainExited && - r.cleanup?.driverClosed && - r.cleanup?.webviewExited && - Array.isArray(r.consoleStates) && - r.consoleStates.length === expectedMarks.length && - r.consoleStates.every( - (s, i) => - s && - s.name === (i ? expectedMarks[i] : 'initial') && - typeof s.locked === 'boolean' && - Number.isFinite(s.time) && - s.time > 0 && - (!i || s.time >= r.consoleStates[i - 1].time), - ) && - Number.isInteger(r.replay?.sent) && - r.replay.sent > 0 && - Number.isFinite(r.replay.maxLateMs) && - r.replay.maxLateMs >= 0 && - r.replay.maxLateMs < x.contract.replayMs && - !r.replay.error && - r.actualWarmupMs >= x.contract.warmup * 1000 && - Array.isArray(r.observations) && - r.observations.length > 0 && - r.observations.every( - (s, i) => - validObservation(s) && (!i || s.now > r.observations[i - 1].now), - ) && - r.marks.length === expectedMarks.length && - r.marks.every( - (m, i) => - m.name === expectedMarks[i] && - Number.isFinite(m.time) && - m.time > 0 && - (!i || - (validObservation({ ...m, phase: m.name.split(':')[0] }) && - /^[a-f0-9]{64}$/.test(m.pixelsSha256))) && - (!phases.includes(m.name) || - (m.target === x.contract.phaseTargets[phases.indexOf(m.name)] && - m.received === m.target)) && - (!i || m.time > r.marks[i - 1].time), - ) && - roles.every((role) => - r.samples - .filter((s) => s.role === role) - .every( - (s, i, rows) => - !i || - (s.pid === rows[i - 1].pid && - s.start === rows[i - 1].start && - s.time > rows[i - 1].time && - s.cpu >= rows[i - 1].cpu && - phases.indexOf(s.phase) >= phases.indexOf(rows[i - 1].phase)), - ), - ) && - validStats(stats.get(r)) && - phases.every((phase) => { - const start = r.marks.find((m) => m.name === phase) - const end = r.marks.find((m) => m.name === `${phase}:end`) - const observed = r.observations.filter((s) => s.phase === phase) - return ( - start && - end && - observed.length > 0 && - [ - start.deadlineIndex, - end.deadlineIndex, - start.senderSent, - end.senderSent, - ].every((n) => Number.isInteger(n) && n >= 0) && - end.deadlineIndex > start.deadlineIndex && - end.senderSent >= start.senderSent && - end.senderSent <= r.replay.sent && - start.senderSent >= start.received && - end.senderSent >= end.received && - Math.abs( - end.received - - start.received - - (end.deadlineIndex - start.deadlineIndex), - ) <= 2 && - observed.every( - (s) => - s.now >= start.now && - s.now <= end.now && - s.received >= start.received && - s.received <= end.received, - ) && - end.worker > start.worker && - (phase === 'hidden' || - (end.draw > start.draw && - end.pixelsSha256 !== start.pixelsSha256)) - ) - }) - ) - }) - if (![a, b].every(valid)) - return { verdict: 'invalid', reason: 'failed, malformed or missing runs' } - if ( - [a, b].some((x) => - x.runs.some((r) => - r.consoleStates.some((s) => s.locked !== x.contract.consoleLocked), - ), - ) - ) - return { - verdict: 'incomparable', - reason: 'console lock state changed at observed boundaries', - } - if ( - !isDeepStrictEqual(a.contract, b.contract) || - ['mode', 'buildCommand', 'toolchain', 'buildEnvironment'].some( - (key) => !isDeepStrictEqual(a.build[key], b.build[key]), - ) || - a.harnessSha256 !== b.harnessSha256 - ) - return { - verdict: 'incomparable', - reason: 'harness, platform, build mode, workload or environment differ', - } - for (const phase of phases) { - for (const name of [phase, `${phase}:end`]) { - const sizes = [a, b].map((x) => - x.runs.map((r) => r.marks.find((m) => m.name === name)?.received), - ) - if (!sizes.flat().every(Number.isFinite)) - return { verdict: 'invalid', reason: 'missing observed workload size' } - if ( - Math.abs(quantile(sizes[0], 0.5) - quantile(sizes[1], 0.5)) > 2 || - sizes.some((s) => Math.max(...s) - Math.min(...s) > 2) - ) - return { - verdict: 'incomparable', - reason: `${name}: input size varies by more than two replay points`, - } - } - } - const same = a.build.binarySha256 === b.build.binarySha256 - const enough = a.runs.length >= 3 && b.runs.length >= 3 - const metrics = {} - for (const phase of phases) - for (const role of roles) - for (const metric of ['cpuMean', 'rssP50']) { - const key = `${phase}/${role}` - const av = a.runs.map((r) => stats.get(r)[key][metric]) - const bv = b.runs.map((r) => stats.get(r)[key][metric]) - const before = quantile(av, 0.5), - after = quantile(bv, 0.5) - const noise = Math.max(...av) - Math.min(...av) - const candidateRange = Math.max(...bv) - Math.min(...bv) - const overlap = - Math.max(...av) >= Math.min(...bv) && - Math.max(...bv) >= Math.min(...av) - metrics[`${key}/${metric}`] = { - before, - after, - delta: after - before, - baselineRange: noise, - candidateRange, - verdict: - same || - !enough || - threshold === null || - !av.every(Number.isFinite) || - !bv.every(Number.isFinite) || - overlap || - Math.abs(after - before) <= - Math.max( - noise, - candidateRange, - (Math.abs(before) * threshold) / 100, - ) - ? 'inconclusive' - : after < before - ? 'lower' - : 'higher', - } - } - return { - verdict: 'inconclusive', - kind: same ? 'A/A' : 'A/B', - reason: same - ? 'same binary; natural variation only' - : 'inspect independent-run metrics; no universal pass threshold', - metrics, - } -} -export function markdown(report) { - const lines = [ - '# Home traffic graph - replay measurement', - '', - `Success: ${report.runs.filter((r) => r.ok).length}/${report.runs.length}`, - '', - 'CPU: one fully occupied core = 100%. Memory: process resident size bytes, not exclusive memory. Null = unavailable; first CPU observation has no delta. Slope requires 60 seconds of continuous observations.', - '', - '| Run / stage / role | CPU mean / P50 / P95 / peak (%) | CPU s | RSS P50 / P95 / peak / delta (bytes) | Samples / coverage |', - '|---|---|---|---|---|', - ] - for (const [i, run] of report.runs.entries()) { - if (run.error) lines.push(`\nRun ${i + 1} failed: ${run.error}\n`) - for (const [key, s] of Object.entries(run.stats)) - lines.push( - `| ${i + 1}/${key} | ${[s.cpuMean, s.cpuP50, s.cpuP95, s.cpuPeak].map((v) => v?.toFixed(2) ?? 'unavailable').join(' / ')} | ${s.cpuSeconds?.toFixed(3) ?? 'unavailable'} | ${[s.rssP50, s.rssP95, s.rssPeak, s.rssDelta].map((v) => v ?? 'unavailable').join(' / ')} | ${s.count} / ${(s.coverage * 100).toFixed(1)}% |`, - ) - } - lines.push( - '', - 'Scope: loopback replay → public traffic append boundary → production Worker → homepage Canvas component. Mihomo and its network/subscription chain are not running. Shared GPU/network processes are unavailable and excluded. Process statistics do not identify function hotspots, component CPU or retained objects; memory growth is not proof of a leak.', - '', - 'Next measurement: use WebKit CPU sampling for the highest-CPU foreground/restored phase; use Allocation/Heap captures for repeatable RSS growth. React Compiler is an untested local A/B candidate only.', - ) - return lines.join('\n') + '\n' -} -export const validStats = (stats) => - Object.values(stats).every( - (s) => - s.continuous && - s.coverage >= 0.8 && - s.intervalCoverage >= 0.8 && - s.cpuCount >= 3, - ) diff --git a/scripts/perf/report.test.mjs b/scripts/perf/report.test.mjs deleted file mode 100644 index c0b5af052..000000000 --- a/scripts/perf/report.test.mjs +++ /dev/null @@ -1,340 +0,0 @@ -import assert from 'node:assert/strict' -import { execFileSync } from 'node:child_process' -import { mkdirSync } from 'node:fs' -import { test } from 'node:test' - -import { - compare, - delta, - phases, - roles, - quantile, - summarize, - validWorkload, - replayValues, - SCHEMA, - validVisibility, -} from './report.mjs' -const latest = (sequence) => { - const { up, down } = replayValues(sequence) - return { lastUp: up, lastDown: down } -} -const observation = (phase, now) => ({ - phase, - now, - source: now - 100, - worker: now - 100, - draw: now - 100, - visible: phase !== 'hidden', - hidden: phase === 'hidden', - focused: false, - nativeFocused: false, - appActive: false, - top: true, - clickThrough: true, -}) -const rows = phases.flatMap((phase, phaseIndex) => - roles.flatMap((role) => - Array.from({ length: 5 }, (_, i) => ({ - phase, - role, - pid: role === 'main' ? 1 : 2, - start: '1', - time: (phaseIndex * 5 + i) * 1000, - cpu: (phaseIndex * 5 + i) / 10, - rss: 100 + i, - })), - ), -) -const run = { - ok: true, - samples: rows, - cleanup: { mainExited: true, driverClosed: true, webviewExited: true }, - replay: { sent: 130, maxLateMs: 10 }, - actualWarmupMs: 3000, - observations: phases.map((phase, i) => ({ - ...observation(phase, 2000 + i * 10000), - received: 35 + i * 35, - points: 34 + i * 35, - gaps: 0, - ...latest(34 + i * 35), - })), - marks: [ - { name: 'ready', time: 1 }, - ...phases.flatMap((name, i) => [ - { - name, - target: 35 + i * 35, - deadlineIndex: 35 + i * 35, - senderSent: 35 + i * 35, - received: 35 + i * 35, - points: 34 + i * 35, - ...latest(34 + i * 35), - gaps: 0, - time: 1000 + i * 10000, - ...observation(name, 1000 + i * 10000), - pixelsSha256: 'a'.repeat(64), - }, - { - name: `${name}:end`, - deadlineIndex: 60 + i * 35, - senderSent: 60 + i * 35, - received: 60 + i * 35, - points: 59 + i * 35, - ...latest(59 + i * 35), - gaps: 0, - time: 6000 + i * 10000, - ...observation(name, 6000 + i * 10000), - pixelsSha256: 'b'.repeat(64), - }, - ]), - ], -} -run.consoleStates = run.marks.map((m, i) => ({ - name: i ? m.name : 'initial', - time: m.time, - locked: false, -})) -const baseline = { - schema: SCHEMA, - harnessSha256: 'fixture-harness', - build: { - head: 'fixture-head', - dirty: '', - sourceSha256: 'fixture-source', - binarySha256: 'same', - frontendSha256: 'fixture-assets', - buildSettingsSha256: 'fixture-settings', - mode: 'release', - buildCommand: 'fixture-build', - binary: '/fixture/app', - toolchain: 'fixture-tools', - buildEnvironment: {}, - }, - contract: { - scenario: 'traffic', - replayMs: 200, - consoleLocked: false, - windowPolicy: 'nonactivating-top-click-through-v1', - seconds: 5, - warmup: 2, - phaseTargets: [35, 70, 105], - transitionSeconds: 2, - readinessAllowanceSeconds: 5, - intervalMs: 1000, - rangeMinutes: 10, - window: [1000, 700], - viewport: [1000, 668, 2], - method: 'fixture-method', - mode: 'release', - toolchain: 'fixture-tools', - buildSettingsSha256: 'fixture-settings', - platform: 'macOS', - machine: 'fixture-model', - arch: 'arm64', - host: 'fixture-host', - hardware: 'fixture-hardware', - power: 'fixture-power', - powerSource: 'AC', - webkit: 'fixture-webkit', - replay: 'fixture-5Hz', - automation: 'fixture-wdio', - buildEnvironment: {}, - }, - runs: [run, run, run], -} -test('quantiles, CPU units, first observation and PID reuse', () => { - assert.equal(quantile([0, 10, 20], 0.95), 19) - assert.equal(delta(undefined, rows[0]), null) - assert.equal(delta(rows[0], rows[1]), 10) - assert.equal(delta(rows[0], { ...rows[1], start: '2' }), null) - assert.equal(delta(rows[0], { ...rows[1], time: NaN }), null) - assert.equal(summarize(rows, 5)['hidden/main'].cpuMean, 10) - assert.equal(summarize([], 5)['hidden/main'].cpuSeconds, null) -}) -test('report schema: A/A, failed, incomparable and corrupt samples', () => { - assert.equal(compare(baseline, baseline).kind, 'A/A') - assert.equal( - compare(baseline, { ...baseline, schema: 'old' }).verdict, - 'invalid', - ) - for (const change of [ - { appActive: true }, - { nativeFocused: true }, - { top: false }, - { top: undefined }, - { clickThrough: false }, - { clickThrough: undefined }, - { hidden: true }, - { worker: 3000 }, - { now: 6000 }, - { draw: 3000 }, - { worker: null }, - ]) { - const invalid = structuredClone(baseline) - Object.assign(invalid.runs[0].observations[0], change) - assert.equal(compare(baseline, invalid).verdict, 'invalid') - } - for (const key of ['draw', 'pixelsSha256']) { - const stalled = structuredClone(baseline) - stalled.runs[0].marks[2][key] = stalled.runs[0].marks[1][key] - assert.equal(compare(baseline, stalled).verdict, 'invalid') - } - const missingPhase = structuredClone(baseline) - missingPhase.runs[0].observations.pop() - assert.equal(compare(baseline, missingPhase).verdict, 'invalid') - const consoleDrift = structuredClone(baseline) - consoleDrift.runs[0].consoleStates[3].locked = true - assert.equal(compare(baseline, consoleDrift).verdict, 'incomparable') - consoleDrift.runs[0].consoleStates[3].locked = null - assert.equal(compare(baseline, consoleDrift).verdict, 'invalid') - assert.equal( - validVisibility({ phase: 'restored', visible: true, hidden: true }), - false, - ) - const missedPeriod = structuredClone(baseline) - missedPeriod.runs[0].replay.maxLateMs = 200 - assert.equal(compare(baseline, missedPeriod).verdict, 'invalid') - const delayed = { - received: 100, - points: 97, - gaps: 0, - lastUp: 270000, - lastDown: 640000, - } - assert.equal(validWorkload(delayed), true) - assert.equal(validWorkload(null), false) - assert.equal(validWorkload({ ...delayed, points: 96 }), false) - assert.equal(validWorkload({ ...delayed, points: 98 }), false) - assert.equal(validWorkload({ ...delayed, received: 157 }), false) - assert.equal(validWorkload({ ...delayed, lastDown: 660000 }), false) - const missedAnchor = structuredClone(baseline) - missedAnchor.runs[0].marks[1].received++ - assert.equal(compare(baseline, missedAnchor).verdict, 'invalid') - const droppedInputs = structuredClone(baseline) - const end = droppedInputs.runs[0].marks[2] - end.received -= 10 - end.points -= 10 - Object.assign(end, latest(end.points)) - assert.equal(compare(droppedInputs, droppedInputs).verdict, 'invalid') - const workerLag = structuredClone(baseline) - workerLag.runs[0].observations[0].points -= 3 - assert.equal(compare(baseline, workerLag).verdict, 'invalid') - const inputDrift = structuredClone(baseline) - inputDrift.runs = baseline.runs.map((r) => structuredClone(r)) - inputDrift.runs[0].marks[2].received += 3 - inputDrift.runs[0].marks[2].points += 3 - inputDrift.runs[0].marks[2].deadlineIndex += 3 - inputDrift.runs[0].marks[2].senderSent += 3 - Object.assign( - inputDrift.runs[0].marks[2], - latest(inputDrift.runs[0].marks[2].points), - ) - assert.equal(compare(baseline, inputDrift).verdict, 'incomparable') - const snapshotPhase = structuredClone(baseline) - snapshotPhase.runs[0].marks[2].points++ - Object.assign( - snapshotPhase.runs[0].marks[2], - latest(snapshotPhase.runs[0].marks[2].points), - ) - assert.equal(compare(baseline, snapshotPhase).kind, 'A/A') - const changedSettings = structuredClone(baseline) - changedSettings.build.buildSettingsSha256 = - changedSettings.contract.buildSettingsSha256 = 'changed-settings' - assert.equal(compare(baseline, changedSettings).verdict, 'incomparable') - assert.equal( - compare(baseline, { ...baseline, harnessSha256: 'changed' }).verdict, - 'incomparable', - ) - assert.equal( - compare(baseline, { - ...baseline, - contract: { ...baseline.contract, seconds: 6 }, - }).verdict, - 'incomparable', - ) - assert.equal( - compare(baseline, { ...baseline, runs: [{ ...run, ok: false }] }).verdict, - 'invalid', - ) - const corrupt = structuredClone(baseline) - corrupt.runs[0].samples[1].time = 0 - assert.equal(compare(baseline, corrupt).verdict, 'invalid') - const shifted = structuredClone(baseline) - shifted.runs[0].samples - .filter((s) => s.phase === 'hidden') - .forEach((s) => { - s.time -= 5000 - }) - assert.equal(compare(baseline, shifted).verdict, 'invalid') - const badMarks = structuredClone(baseline) - badMarks.runs[0].marks.forEach((m) => { - m.time = -1 - }) - assert.equal(compare(baseline, badMarks).verdict, 'invalid') - const clustered = structuredClone(baseline) - clustered.runs[0].samples.forEach((s) => { - s.time /= 1000 - }) - assert.equal(compare(baseline, clustered).verdict, 'invalid') - for (const [section, key] of [ - ['contract', 'platform'], - ['build', 'sourceSha256'], - ]) { - const missing = structuredClone(baseline) - delete missing[section][key] - assert.equal(compare(missing, missing).verdict, 'invalid') - } - assert.equal( - compare(baseline, { - ...baseline, - contract: { ...baseline.contract, platform: 'other-macOS' }, - }).verdict, - 'incomparable', - ) - for (const malformed of [ - null, - {}, - { runs: [null] }, - { ...baseline, runs: [{ ...run, samples: null }] }, - ]) - assert.equal(compare(baseline, malformed).verdict, 'invalid') - const noisy = structuredClone(baseline) - noisy.runs = baseline.runs.map((r) => structuredClone(r)) - noisy.build.binarySha256 = 'different' - noisy.runs.forEach((r, i) => - r.samples.forEach((s) => { - s.cpu *= [0.1, 0.2, 10][i] - }), - ) - assert.equal( - compare(baseline, noisy, 5).metrics['foreground/webview/cpuMean'].verdict, - 'inconclusive', - ) -}) -test('macOS collector agrees with process.cpuUsage on a busy owned process', { - skip: process.platform !== 'darwin', -}, () => { - mkdirSync('target/perf', { recursive: true }) - execFileSync('cc', [ - '-Wall', - '-Wextra', - '-Werror', - 'scripts/perf/sample.c', - '-o', - 'target/perf/test-sample', - ]) - const sample = () => - Number( - execFileSync('target/perf/test-sample', [String(process.pid)], { - encoding: 'utf8', - }).split(' ')[2], - ) - const first = sample(), - cpu = process.cpuUsage(), - start = performance.now() - while (performance.now() - start < 500) Math.sqrt(Math.random()) - const used = process.cpuUsage(cpu), - measured = sample() - first - assert.ok(Math.abs(measured - (used.user + used.system) / 1e6) < 0.05) -}) diff --git a/scripts/perf/run.mjs b/scripts/perf/run.mjs deleted file mode 100644 index 9094faa77..000000000 --- a/scripts/perf/run.mjs +++ /dev/null @@ -1,674 +0,0 @@ -import { spawn, execFileSync } from 'node:child_process' -import { createHash, randomUUID } from 'node:crypto' -import { - mkdirSync, - readFileSync, - writeFileSync, - openSync, - closeSync, - realpathSync, -} from 'node:fs' -import { createServer } from 'node:net' -import { resolve, dirname } from 'node:path' -import { setTimeout as sleep } from 'node:timers/promises' -import { parseArgs } from 'node:util' - -import { createTauriCapabilities } from '@wdio/tauri-service' -import { remote } from 'webdriverio' -import { WebSocketServer } from 'ws' - -import { - delta, - summarize, - markdown, - phases, - roles, - validStats, - validWorkload, - replayValues, - SCHEMA, - validObservation, - validVisibility, -} from './report.mjs' - -const { values } = parseArgs({ - args: process.argv.slice(2).filter((v) => v !== '--'), - options: { - output: { type: 'string', default: `target/perf/runs/${Date.now()}` }, - binary: { type: 'string', default: 'target/perf/build/clash-verge' }, - repeat: { type: 'string', default: '3' }, - seconds: { type: 'string', default: '20' }, - warmup: { type: 'string', default: '10' }, - 'fail-phase': { type: 'string' }, - }, -}) -const seconds = Number(values.seconds), - warmup = Number(values.warmup), - repeat = Number(values.repeat) -const replayMs = 200 -if (process.platform !== 'darwin') throw new Error('macOS only') -function readConsole() { - const consoleState = JSON.parse( - execFileSync('/usr/bin/plutil', ['-convert', 'json', '-o', '-', '-'], { - input: execFileSync('/usr/sbin/ioreg', ['-n', 'Root', '-d1', '-a']), - encoding: 'utf8', - }), - ) - const locked = consoleState.IOConsoleLocked - if (typeof locked !== 'boolean') - throw new Error('Environment unavailable: unknown console lock state') - return locked -} -function checkConsole() { - const locked = readConsole() - if (locked) - throw new Error( - 'Environment unavailable: an unlocked graphical session is required; window focus is not required', - ) - return locked -} -const consoleLocked = checkConsole() -if ( - ![seconds, warmup, repeat].every(Number.isFinite) || - seconds < 5 || - warmup < 2 || - repeat < 1 || - !Number.isInteger(repeat) -) - throw new Error('Require seconds >= 5, warmup >= 2, integer repeat >= 1') -const phaseTargets = phases.map( - (_, i) => - Math.ceil(((warmup + 5) * 1000) / replayMs) + - i * Math.ceil(((seconds + 2) * 1000) / replayMs), -) -if ((phaseTargets[2] * replayMs) / 1000 + seconds + 5 >= 600) - throw new Error( - 'Total replay including transition allowance must be < 10 minutes', - ) -const binary = realpathSync(values.binary), - output = resolve(values.output) -const webcontentBinary = realpathSync( - '/System/Library/Frameworks/WebKit.framework/Versions/A/XPCServices/com.apple.WebKit.WebContent.xpc/Contents/MacOS/com.apple.WebKit.WebContent', -) -const build = JSON.parse(readFileSync(`${binary}.json`)) -const hash = (data) => createHash('sha256').update(data).digest('hex') -if (hash(readFileSync(binary)) !== build.binarySha256) - throw new Error('Binary does not match build manifest') -try { - execFileSync('git', ['check-ignore', '-q', '--no-index', output]) -} catch { - throw new Error('Output directory must be Git ignored') -} -mkdirSync(dirname(output), { recursive: true }) -mkdirSync(output) -execFileSync('cc', ['-O2', 'scripts/perf/sample.c', '-o', `${output}/sample`]) -const command = (name, args) => - execFileSync(name, args, { encoding: 'utf8' }).trim() -const report = { - schema: SCHEMA, - build, - contract: { - scenario: 'traffic', - replayMs, - consoleLocked, - method: 'mach-rusage-v0-seconds/rss-bytes-v1', - mode: build.mode, - buildSettingsSha256: build.buildSettingsSha256, - buildEnvironment: build.buildEnvironment, - toolchain: build.toolchain, - platform: command('sw_vers', []), - machine: command('sysctl', ['-n', 'hw.model']), - arch: process.arch, - host: hash(command('scutil', ['--get', 'LocalHostName'])), - hardware: command('sysctl', ['hw.ncpu', 'hw.memsize']), - power: command('pmset', ['-g', 'custom']), - powerSource: command('pmset', ['-g', 'batt']).split('\n')[0], - webkit: command('defaults', [ - 'read', - '/System/Library/Frameworks/WebKit.framework/Resources/Info', - 'CFBundleVersion', - ]), - seconds, - warmup, - phaseTargets, - transitionSeconds: 2, - readinessAllowanceSeconds: 5, - intervalMs: 1000, - window: [1000, 700], - windowPolicy: 'nonactivating-top-click-through-v1', - rangeMinutes: 10, - replay: - 'deterministic-v1, 5Hz, no prefill, append boundary, default bezier, blur pause false', - automation: - 'wdio plugin 1.3.0; 20Hz macOS runloop pump plus one WebDriver observation/second', - }, - runs: [], -} -report.units = { - time: 'monotonic milliseconds', - cpu: 'cumulative CPU seconds', - percent: 'one CPU core = 100%', - rss: 'resident size bytes', - start: 'Mach process start ticks', -} -report.monotonicOriginEpochMs = performance.timeOrigin -report.harnessSha256 = hash( - ['run.mjs', 'report.mjs', 'sample.c'] - .map((p) => readFileSync(`scripts/perf/${p}`)) - .join('\n'), -) -let interrupted = false -const abort = new AbortController() -const pause = (ms) => sleep(ms, undefined, { signal: abort.signal }) -for (const signal of ['SIGINT', 'SIGTERM']) - process.on(signal, () => { - interrupted = true - abort.abort() - }) -const assert = (value, reason) => { - if (!value) throw new Error(reason) -} -const drawn = (s) => s.now >= s.draw && s.now - s.draw < 2500 -const marker = (s) => ({ - ...s, - pixelsSha256: hash(s.pixels), - pixels: undefined, - ua: undefined, - token: undefined, -}) -async function freePort() { - const server = createServer() - await new Promise((done) => server.listen(0, '127.0.0.1', done)) - const port = server.address().port - await new Promise((done) => server.close(done)) - return port -} -for (let iteration = 0; iteration < repeat && !interrupted; iteration++) { - const initialLocked = checkConsole() - const token = randomUUID(), - port = await freePort() - const run = { - token, - ok: false, - samples: [], - observations: [], - consoleStates: [ - { name: 'initial', locked: initialLocked, time: Date.now() }, - ], - marks: [], - stats: {}, - cleanup: {}, - replay: { sent: 0, maxLateMs: 0 }, - } - report.runs.push(run) - const replay = new WebSocketServer({ host: '127.0.0.1', port: 0 }) - await new Promise((done) => replay.on('listening', done)) - let sequence = 0, - replayStarted, - timer - replay.on('connection', () => { - if (replayStarted !== undefined) { - run.replay.error = 'unexpected replay reconnection' - clearTimeout(timer) - return - } - replayStarted = performance.now() - timer = setTimeout(send, replayMs) - }) - const deadlineIndex = () => - Math.floor((performance.now() - replayStarted) / replayMs) - function send() { - const deadline = replayStarted + (sequence + 1) * replayMs - const late = performance.now() - deadline - if (late < 0) { - timer = setTimeout(send, Math.ceil(-late)) - return - } - run.replay.maxLateMs = Math.max(run.replay.maxLateMs, late) - if (late >= replayMs) { - run.replay.error = `replay source missed a complete ${replayMs} ms period` - return - } - sequence++ - run.replay.sent = sequence - const timestamp = Date.now() - const point = JSON.stringify({ - sequence, - timestamp, - ...replayValues(sequence), - }) - for (const socket of replay.clients) - if (socket.readyState === 1) socket.send(point) - timer = setTimeout( - send, - Math.max( - 0, - replayStarted + (sequence + 1) * replayMs - performance.now(), - ), - ) - } - const log = openSync(`${output}/${iteration + 1}.log`, 'w') - const child = spawn(binary, [], { - env: { - ...process.env, - PERF_TOKEN: token, - PERF_REPLAY_PORT: String(replay.address().port), - TAURI_WEBDRIVER_PORT: String(port), - }, - stdio: ['ignore', log, log], - }) - closeSync(log) - let browser, childError, previous, mainStart - const identities = {} - child.on('error', (error) => { - childError = error - }) - const exited = new Promise((done) => child.once('exit', done)) - const alive = () => - !childError && child.exitCode === null && child.signalCode === null - const collect = (pids) => - command(`${output}/sample`, pids.map(String)) - .split('\n') - .map((line) => { - const [pid, start, cpu, rss, ...path] = line.split(' ') - return { - pid: Number(pid), - start, - cpu: Number(cpu), - rss: Number(rss), - path: path.join(' '), - time: performance.now(), - } - }) - const identify = (role, row) => { - assert( - row.path === (role === 'main' ? binary : webcontentBinary), - `unexpected ${role} process executable: ${row.path}`, - ) - assert( - BigInt(row.start) >= BigInt(mainStart), - 'process predates owned application', - ) - const identity = `${row.pid}/${row.start}` - assert( - !identities[role] || identities[role] === identity, - 'process identity changed during scenario', - ) - identities[role] = identity - } - const state = async (action = 'state', pixels = false) => - browser.execute( - async (action, pixels) => { - const native = await window.__TAURI_INTERNALS__.invoke('perf_state', { - action, - }) - const canvas = document.querySelector('canvas') - return { - ...native, - now: Date.now(), - hidden: document.hidden, - focused: document.hasFocus(), - received: Number(document.documentElement.dataset.perfReceived), - source: Number(document.documentElement.dataset.perfSourceTime), - gaps: Number(document.documentElement.dataset.perfGaps), - worker: Number(document.documentElement.dataset.perfWorker), - points: Number(document.documentElement.dataset.perfPoints), - lastUp: Number(document.documentElement.dataset.perfLastUp), - lastDown: Number(document.documentElement.dataset.perfLastDown), - draw: Number(canvas?.dataset.perfDraw), - pixels: pixels ? canvas?.toDataURL() : null, - ua: navigator.userAgent, - } - }, - action, - pixels, - ) - const checked = async (action, pixels) => { - assert(!interrupted, 'interrupted') - assert(!run.replay.error, run.replay.error) - assert( - alive(), - `owned app exited: ${childError ?? child.exitCode ?? child.signalCode}`, - ) - const value = await state(action, pixels) - assert(!run.replay.error, run.replay.error) - run.lastObservation = { ...value, pixels: undefined, ua: undefined } - assert( - value.token === token && value.main === child.pid, - 'instance identity mismatch', - ) - if (!identities.webview) { - run.initialAttribution = collect([value.webview])[0] - identify('webview', run.initialAttribution) - } - assert( - value.nativeFocused === false && value.appActive === false, - 'measurement window unexpectedly activated', - ) - assert( - value.top === true && value.clickThrough === true, - 'measurement window must be natively topmost and click-through', - ) - return value - } - try { - mainStart = collect([child.pid])[0].start - const deadline = Date.now() + 30000 - while (true) { - assert( - Date.now() < deadline && !interrupted && alive(), - 'embedded WebDriver readiness deadline or app exit', - ) - try { - if ( - ( - await fetch(`http://127.0.0.1:${port}/status`, { - signal: AbortSignal.timeout(500), - }) - ).ok - ) - break - } catch {} - await pause(100) - } - const listener = command('lsof', [ - '-nP', - '-a', - '-p', - String(child.pid), - `-iTCP:${port}`, - '-sTCP:LISTEN', - ]) - assert( - listener.includes(`127.0.0.1:${port}`) && !listener.includes(`*:${port}`), - 'driver must belong to this app and bind loopback', - ) - const capabilities = createTauriCapabilities(binary) - delete capabilities['wdio:tauriServiceOptions'] - browser = await remote({ - hostname: '127.0.0.1', - port, - capabilities, - logLevel: 'error', - connectionRetryCount: 0, - connectionRetryTimeout: 5000, - }) - const handles = await browser.getWindowHandles() - assert(handles.length === 1, 'expected exactly one measurement WebView') - await browser.switchToWindow(handles[0]) - // Replay's parent effect starts the socket after the graph installs its listeners. - await browser.waitUntil( - async () => { - const s = await checked() - return ( - validWorkload(s) && - s.received >= 3 && - s.now >= s.worker && - s.now - s.worker < 2500 - ) - }, - { - timeout: 15000, - interval: 100, - timeoutMsg: 'traffic effects/Worker not ready before show', - }, - ) - run.initialData = run.lastObservation - await checked('show') - await browser.waitUntil( - async () => { - const s = await checked() - return validObservation({ phase: 'foreground', ...s }) - }, - { - timeout: 15000, - interval: 250, - timeoutMsg: 'traffic data path/page not ready', - }, - ) - run.userAgent = (await checked()).ua - run.backgroundLoad = command('sysctl', ['-n', 'vm.loadavg']) - const viewport = await browser.execute(() => [ - innerWidth, - innerHeight, - devicePixelRatio, - ]) - run.viewport = { dom: viewport, driver: await browser.getWindowRect() } - const driverScale = run.viewport.driver.width / 1000 - assert( - viewport[0] === 1000 && - viewport[1] > 0 && - viewport[1] <= 700 && - (driverScale === 1 || driverScale === viewport[2]) && - run.viewport.driver.height / driverScale === 700, - `unexpected window dimensions: ${JSON.stringify(run.viewport)}`, - ) - assert( - !report.contract.viewport || - JSON.stringify(report.contract.viewport) === JSON.stringify(viewport), - 'viewport changed between runs', - ) - report.contract.viewport = viewport - run.marks.push({ name: 'ready', time: Date.now() }) - const readyAt = performance.now() - assert( - phaseTargets[0] - (await checked()).received >= - (warmup * 1000) / replayMs, - 'readiness exceeded fixed replay allowance', - ) - for (const phase of phases) { - run.consoleStates.push({ - name: phase, - locked: readConsole(), - time: Date.now(), - }) - const operation = performance.now() - let s = await checked(phase === 'hidden' ? 'hide' : 'show', true) - assert( - s.visible === (phase !== 'hidden'), - `${phase}: native visibility mismatch`, - ) - const beforeDraw = s.draw - await browser.waitUntil( - async () => { - s = await checked('state', true) - return ( - validVisibility({ phase, ...s }) && - (phase === 'hidden' || (s.draw > beforeDraw && drawn(s))) - ) - }, - { - timeout: 5000, - interval: 100, - timeoutMsg: `${phase}: window/Canvas readiness deadline`, - }, - ) - const operationMs = performance.now() - operation - const target = phaseTargets[phases.indexOf(phase)] - await browser.waitUntil( - async () => { - s = await checked('state', true) - assert( - s.received <= target, - `${phase}: missed replay anchor ${target}`, - ) - return s.received === target - }, - { - timeout: Math.max(1000, (target - s.received) * replayMs + 1000), - interval: 50, - timeoutMsg: `${phase}: replay anchor deadline`, - }, - ) - assert( - validObservation({ phase, ...s }), - `${phase}: invalid traffic observation`, - ) - const first = s, - start = performance.now(), - firstIndex = deadlineIndex(), - phaseEpoch = Date.now() - if (phase === 'foreground') { - run.actualWarmupMs = start - readyAt - assert( - run.actualWarmupMs >= warmup * 1000, - 'insufficient actual warmup', - ) - } - run.marks.push({ - name: phase, - time: phaseEpoch, - operationMs, - transitionMs: start - operation, - target, - ...marker(s), - deadlineIndex: firstIndex, - senderSent: run.replay.sent, - }) - previous = {} - if (values['fail-phase'] === phase) - throw new Error(`injected failure at ${phase}`) - while (performance.now() - start < seconds * 1000) { - const tick = performance.now() - s = await checked() - run.observations.push({ phase, ...run.lastObservation }) - assert( - validObservation({ phase, ...s }), - `${phase}: invalid visibility, workload or freshness`, - ) - assert( - performance.now() - replayStarted < 600000, - 'replay exceeded raw history window', - ) - const samples = collect(roles.map((role) => s[role])) - run.attribution = { - main: s.main, - webview: s.webview, - observed: samples, - } - for (let i = 0; i < roles.length; i++) { - const row = { ...samples[i], phase, role: roles[i] } - identify(row.role, row) - row.percent = delta(previous[row.role], row) - row.cpuDelta = - row.percent === null ? null : row.cpu - previous[row.role].cpu - run.samples.push(row) - previous[row.role] = row - } - await pause(Math.max(0, 1000 - (performance.now() - tick))) - } - s = await checked('state', true) - const endIndex = deadlineIndex() - const expected = endIndex - firstIndex - assert( - validObservation({ phase, ...s }) && - s.worker > first.worker && - Math.abs(s.received - first.received - expected) <= 2, - `${phase}: unstable or lost workload`, - ) - if (phase !== 'hidden') - assert( - s.draw > first.draw && s.pixels !== first.pixels, - `${phase}: Canvas did not render fresh data`, - ) - run.marks.push({ - name: `${phase}:end`, - time: s.now, - ...marker(s), - deadlineIndex: endIndex, - senderSent: run.replay.sent, - }) - run.consoleStates.push({ - name: `${phase}:end`, - locked: readConsole(), - time: Date.now(), - }) - } - run.stats = summarize(run.samples, seconds) - assert( - validStats(run.stats), - 'insufficient coverage or process discontinuity', - ) - run.ok = true - } catch (error) { - run.error = String(error) - process.exitCode = 1 - } finally { - await browser?.deleteSession().catch(() => {}) - try { - if (alive() && collect([child.pid])[0].start === mainStart) { - child.kill('SIGTERM') - await Promise.race([exited, sleep(5000)]) - if (alive() && collect([child.pid])[0].start === mainStart) { - child.kill('SIGKILL') - await Promise.race([exited, sleep(2000)]) - } - } - } catch (error) { - run.cleanup.error = String(error) - } - clearTimeout(timer) - for (const socket of replay.clients) socket.terminate() - await new Promise((done) => replay.close(done)) - run.cleanup.mainExited = - child.exitCode !== null || child.signalCode !== null - if (identities.webview) { - const [pid, start] = identities.webview.split('/') - const remains = () => { - try { - return collect([pid])[0].start === start - } catch { - return false - } - } - const deadline = Date.now() + 5000 - while (remains() && Date.now() < deadline) await sleep(100) - run.cleanup.webviewExited = !remains() - } - try { - await fetch(`http://127.0.0.1:${port}/status`, { - signal: AbortSignal.timeout(500), - }) - run.cleanup.driverClosed = false - } catch { - run.cleanup.driverClosed = true - } - if ( - !run.cleanup.mainExited || - !run.cleanup.driverClosed || - run.cleanup.webviewExited === false - ) { - run.ok = false - run.error = `${run.error ?? ''} cleanup failed` - process.exitCode = 1 - } - run.stats = summarize(run.samples, seconds) - writeFileSync( - `${output}/${iteration + 1}.json`, - JSON.stringify(run, null, 2), - ) - const columns = [ - 'phase', - 'role', - 'time', - 'pid', - 'start', - 'cpu', - 'percent', - 'rss', - ] - writeFileSync( - `${output}/${iteration + 1}.csv`, - [ - columns.join(','), - ...run.samples.map((r) => - columns.map((c) => r[c] ?? 'unavailable').join(','), - ), - ].join('\n'), - ) - writeFileSync(`${output}/summary.json`, JSON.stringify(report, null, 2)) - writeFileSync(`${output}/report.md`, markdown(report)) - } - console.log(`Run ${iteration + 1}: ${run.ok ? 'passed' : run.error}`) -} -console.log(`Reports: ${output}`) diff --git a/scripts/perf/sample.c b/scripts/perf/sample.c deleted file mode 100644 index f0128c954..000000000 --- a/scripts/perf/sample.c +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include -#include -#include -#include - -int main(int argc, char **argv) { - mach_timebase_info_data_t timebase; - if (mach_timebase_info(&timebase) != KERN_SUCCESS) return 1; - for (int i = 1; i < argc; i++) { - int pid = atoi(argv[i]); - struct rusage_info_v0 r; - char path[PROC_PIDPATHINFO_MAXSIZE]; - if (pid <= 0 || proc_pid_rusage(pid, RUSAGE_INFO_V0, (rusage_info_t *)&r)) return 1; - if (proc_pidpath(pid, path, sizeof(path)) <= 0) return 1; - printf("%d %llu %.9f %llu %s\n", pid, r.ri_proc_start_abstime, - ((double)r.ri_user_time + r.ri_system_time) * timebase.numer / timebase.denom / 1e9, r.ri_resident_size, path); - } - return 0; -} diff --git a/scripts/perf/scenes.tsx b/scripts/perf/scenes.tsx new file mode 100644 index 000000000..02992fec5 --- /dev/null +++ b/scripts/perf/scenes.tsx @@ -0,0 +1,81 @@ +import { ThemeProvider, createTheme } from '@mui/material' +import dayjs from 'dayjs' +import relativeTime from 'dayjs/plugin/relativeTime' +import { useEffect, useState } from 'react' +import { createRoot } from 'react-dom/client' +import { SWRConfig } from 'swr' + +import { VirtualList } from '../../src/components/base/virtual-list' +import { ConnectionRowItem } from '../../src/components/connection/connection-row-item' +import { useConnectionRowViews } from '../../src/components/connection/connection-row-view' +import LogItem from '../../src/components/log/log-item' +import { initializeLanguage } from '../../src/services/i18n' +import { setPreloadConfig } from '../../src/services/preload' + +import { layer, scenario, subscribe } from './replay' +import '../../src/assets/styles/page.scss' + +const searchState = { + text: '', + matchCase: false, + matchWholeWord: false, + useRegularExpression: false, +} +export function Components() { + const [logs, setLogs] = useState([]) + const [connections, setConnections] = useState([]) + useEffect( + () => + subscribe(({ payload }) => { + if (scenario === 'logs') + setLogs((previous) => + previous.concat(payload as ILogItem[]).slice(-1000), + ) + else setConnections((payload as IConnections).connections) + }), + [], + ) + const rows = useConnectionRowViews(connections) + return ( + + scenario === 'logs' ? ( + + ) : ( + {}} + /> + ) + } + /> + ) +} +dayjs.extend(relativeTime) +const root = document.getElementById('root')! +setPreloadConfig({ + language: 'en', + theme_mode: 'light', + pause_render_traffic_stats_on_blur: false, +} as IVergeConfig) +await initializeLanguage('en') +const Scene = + layer === 'component' + ? Components + : scenario === 'logs' + ? (await import('../../src/pages/logs')).default + : (await import('../../src/pages/connections')).default +createRoot(root).render( + + + + + , +) diff --git a/scripts/perf/traffic.tsx b/scripts/perf/traffic.tsx new file mode 100644 index 000000000..4fb70ac09 --- /dev/null +++ b/scripts/perf/traffic.tsx @@ -0,0 +1,52 @@ +import { ThemeProvider, createTheme } from '@mui/material' +import { useEffect } from 'react' +import { createRoot } from 'react-dom/client' +import { SWRConfig } from 'swr' + +import { EnhancedCanvasTrafficGraph } from '../../src/components/home/enhanced-canvas-traffic-graph' +import { useTrafficMonitorEnhanced } from '../../src/hooks/use-traffic-monitor' +import { initializeLanguage } from '../../src/services/i18n' +import { setPreloadConfig } from '../../src/services/preload' + +setPreloadConfig({ + pause_render_traffic_stats_on_blur: false, +} as IVergeConfig) +await initializeLanguage('en') +export function Replay() { + const { + graphData: { appendData }, + } = useTrafficMonitorEnhanced({ subscribe: false }) + useEffect(() => { + let received = 0, + previous = 0, + gaps = 0 + const socket = new WebSocket( + `ws://127.0.0.1:${new URLSearchParams(location.search).get('port')}`, + ) + socket.onmessage = ({ data }) => { + const point = JSON.parse(data) + appendData(point) + if (previous && point.sequence !== previous + 1) gaps++ + previous = point.sequence + document.documentElement.dataset.perfReceived = String(++received) + document.documentElement.dataset.perfGaps = String(gaps) + document.documentElement.dataset.perfSourceTime = String(point.timestamp) + } + return () => socket.close() + }, [appendData]) + return ( +
+ +
+ ) +} +createRoot(document.getElementById('root')!).render( + + + + + , +) diff --git a/scripts/perf/tsconfig.json b/scripts/perf/tsconfig.json index b26f93df8..0a50a8fa6 100644 --- a/scripts/perf/tsconfig.json +++ b/scripts/perf/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["../../src", "./*.tsx"] + "include": ["../../src", "./*.tsx", "./*.ts"] }