diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4da6c3..4508157 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,16 +14,54 @@ concurrency: jobs: check: + name: Code health (TypeScript + Swift metrics) runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: 10 + - uses: astral-sh/setup-uv@v8.1.0 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 20.19 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm check + + ios: + name: iOS build and Swift quality + runs-on: macos-26 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 10 + - uses: actions/setup-node@v4 + with: + node-version: 20.19 + cache: pnpm + - run: pnpm install --frozen-lockfile + - name: Install native quality tools + run: brew install xcodegen peripheryapp/periphery/periphery + - name: Generate CocoaPods workspace + working-directory: ios + run: | + xcodegen generate + pod install --deployment + - name: Build iOS app + run: >- + xcodebuild + -workspace ios/Motion.xcworkspace + -scheme Motion + -sdk iphonesimulator + -destination 'generic/platform=iOS Simulator' + -derivedDataPath "$RUNNER_TEMP/motion-derived" + CODE_SIGNING_ALLOWED=NO + build + - name: Check Swift quality ratchets + env: + MOTION_INDEX_STORE: ${{ runner.temp }}/motion-derived/Index.noindex/DataStore + run: pnpm quality:swift diff --git a/.gitignore b/.gitignore index 5153a72..e944f3c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules/ .wrangler/ dist/ +coverage/ .parcel-cache/ *.tsbuildinfo diff --git a/AGENTS.md b/AGENTS.md index 5c1e690..03466f3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,9 +58,12 @@ pnpm install pnpm run dev # relay :1999 + web :5173 # Reach & Dodge: http://localhost:5173/?debug=1 → mouse=hands, arrows=lean, space=squat # Motion Maker: http://localhost:5173/?game=motion-maker&debug=1 → mouse=a hand, hold left mouse/space=grab -pnpm run typecheck +pnpm check # full TypeScript quality gate and debt ratchets ``` -iOS builds only on a Mac with Xcode 16+ (`cd ios && xcodegen generate`). It -**compiles for the iOS Simulator**; camera + ReplayKit paths still need a -**physical device** to verify (see `PROJECT_STATUS.md`). +iOS builds only on a Mac with Xcode 16+. Generate the CocoaPods workspace with +`cd ios && xcodegen generate && pod install --deployment`, then build the +`Motion.xcworkspace` scheme. `pnpm quality:swift` checks the Swift formatting +and unused-code ratchets after a build when `MOTION_INDEX_STORE` points to its +index store. Camera + ReplayKit paths still need a **physical device** to verify +(see `PROJECT_STATUS.md`). diff --git a/PROJECT_STATUS.md b/PROJECT_STATUS.md index 751ec61..beee157 100644 --- a/PROJECT_STATUS.md +++ b/PROJECT_STATUS.md @@ -27,6 +27,11 @@ multiplayer are **v2**, already scaffolded and parked in-repo. ## Timeline +- **2026-08-12** — Adopted the Fleet code-health standard across TypeScript and + Swift: added focused protocol/controller/readiness tests, honest full-source + coverage, unused-code and cycle checks, complexity/duplication/dependency/ + suppression/hygiene ratchets, and an unsigned iOS build plus Swift quality CI. + Existing measured debt remains explicit in GitHub issue #26. - **2026-08-12** — Aligned the universal iPhone/iPad build, permission prompts, setup guidance, recording states, bundled web game, and App Store copy around device-neutral language; refreshed the personal-team distribution export. @@ -116,7 +121,12 @@ multiplayer are **v2**, already scaffolded and parked in-repo. - **`server/`** — PartyKit relay. The v2 browser/multiplayer transport (kept typecheck-green); also the endpoint the live `streamToWebsite` preview connects to today. -- Shared Ultracite lint baseline with a clean 39-file TypeScript check. +- Shared Ultracite lint baseline with a clean TypeScript check. +- **Code-health gate** — `pnpm check` enforces the cross-platform TypeScript + quality baseline; hosted macOS CI also builds the CocoaPods workspace and + prevents Swift format or unused-code debt from increasing. Full TypeScript + coverage is measured rather than scoped to tested modules, and all temporary + baseline debt is owned by GitHub issue #26. - **Self-contained app** — the web build is bundled into the app (`ios/Resources/webgame`, refreshed by `scripts/sync-webgame.sh`); `GameConfig` auto-prefers it, so the app runs on an iOS device with **no dev server / Mac network**. diff --git a/README.md b/README.md index fb0b3fa..dbaff67 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,14 @@ refresh the bundle after changes with `./scripts/sync-webgame.sh`. ## Checks and public landing release -Pushes and pull requests run the repository-owned web/server typecheck, web -build, and static landing contract. Physical-device and signing evidence stays -manual because hosted CI cannot exercise the camera path. +Pushes and pull requests run the repository-owned TypeScript code-health gate +and an unsigned iOS Simulator build. The gate covers formatting and lint, +types, tests and full-source coverage, unused code, complexity, duplication, +cycles, dependency risk, suppressions, and repository hygiene. Existing debt is +ratcheted so it cannot grow and is tracked in +[#26](https://github.com/Significant-Hobbies/motion/issues/26). Physical-device +and signing evidence stays manual because hosted CI cannot exercise the camera +path. ```bash pnpm check diff --git a/biome.json b/biome.json index f926037..456386f 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.5.5/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.8/schema.json", "extends": ["ultracite/biome/core"], "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, "files": { diff --git a/ios/Sources/Motion/Recording/ScreenRecorder.swift b/ios/Sources/Motion/Recording/ScreenRecorder.swift index 4f6fbf8..fb1c0b2 100644 --- a/ios/Sources/Motion/Recording/ScreenRecorder.swift +++ b/ios/Sources/Motion/Recording/ScreenRecorder.swift @@ -26,7 +26,8 @@ // game canvas won't be in the video, OR // • Switch GameConfig.source to `.bundled` (a file:// load): a same-process/local origin // is more reliably captured than a remote dev-server origin in some configurations. -// See the TODO at the bottom of this file. Do NOT silently assume capture works. +// Physical-device verification remains tracked in GitHub issue #20. Do NOT +// silently assume capture works. // ═══════════════════════════════════════════════════════════════════════════════════ // // Bracketing: the recorder is ARMED by the on-screen Record toggle, then started by the @@ -222,11 +223,9 @@ final class ScreenRecorder { return current } - // TODO(on-device verify): confirm ReplayKit capture includes the WKWebView game canvas - // AND the AVCaptureVideoPreviewLayer camera inset in the SAVED video. If the game canvas - // is blank, switch GameConfig.source to .bundled (file:// load) and/or fall back to a - // native camera-inset + HUD overlay (drop the webview from the captured frame). See the - // header block above for the full fallback plan. + // GitHub issue #20 tracks physical-device verification that ReplayKit includes both + // the WKWebView game canvas and AVCaptureVideoPreviewLayer camera inset in the saved + // video. The header block above records the fallback if either surface is blank. } // MARK: - Off-actor asset writer diff --git a/knip.json b/knip.json new file mode 100644 index 0000000..c4af99d --- /dev/null +++ b/knip.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://unpkg.com/knip@latest/schema.json", + "ignoreExportsUsedInFile": true, + "workspaces": { + ".": { + "entry": ["scripts/**/*.mjs", "vitest.config.ts", "protocol/**/*.test.ts"], + "project": ["scripts/**/*.mjs", "vitest.config.ts", "protocol/**/*.ts"], + "ignoreDependencies": ["@biomejs/biome", "@vitest/coverage-v8", "jscpd"], + "ignoreIssues": { + "protocol/protocol.ts": ["types"] + } + }, + "server": { + "entry": ["src/server.ts"], + "project": ["src/**/*.ts"] + }, + "web": { + "entry": ["src/app/main.ts", "src/sdk/index.ts", "src/**/*.test.ts"], + "project": ["src/**/*.ts"], + "ignoreIssues": { + "src/sdk/controller.ts": ["types"], + "src/sdk/game.ts": ["types"], + "src/sdk/types.ts": ["types"] + } + } + } +} diff --git a/package.json b/package.json index 9fbc1e9..9d5dd5e 100644 --- a/package.json +++ b/package.json @@ -7,20 +7,46 @@ "dev": "concurrently -n server,web -c cyan,magenta \"pnpm run dev:server\" \"pnpm run dev:web\"", "dev:server": "pnpm --filter motion-server run dev", "dev:web": "pnpm --filter motion-web run dev", + "format:check": "node scripts/check-code-health.mjs format", "lint": "ultracite check .", "typecheck": "pnpm --filter motion-server run typecheck && pnpm --filter motion-web run typecheck", "build": "pnpm --filter motion-web run build", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "knip": "knip --no-exit-code --reporter symbols", + "knip:strict": "knip --reporter symbols --no-config-hints", + "quality:coverage": "node scripts/check-code-health.mjs coverage", + "quality:unused": "pnpm run knip:strict", + "quality:complexity": "node scripts/check-code-health.mjs complexity", + "quality:duplication": "node scripts/check-code-health.mjs duplication", + "quality:cycles": "knip --cycles --no-config-hints", + "quality:dependencies": "node scripts/check-code-health.mjs dependencies", + "quality:suppressions": "node scripts/check-code-health.mjs suppressions", + "quality:hygiene": "node scripts/check-code-health.mjs hygiene", + "quality:swift-format": "node scripts/check-code-health.mjs swift-format", + "quality:swift-unused": "node scripts/check-code-health.mjs swift-unused", + "quality:swift": "pnpm run quality:swift-format && pnpm run quality:swift-unused", "check:landing": "node scripts/check-landing.mjs", - "check": "pnpm lint && pnpm typecheck && pnpm build && pnpm check:landing", + "quality": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm check:landing && pnpm quality:coverage && pnpm quality:unused && pnpm quality:complexity && pnpm quality:duplication && pnpm quality:cycles && pnpm quality:dependencies && pnpm quality:suppressions && pnpm quality:hygiene", + "check": "pnpm quality", "deploy": "pnpm exec wrangler pages deploy landing --project-name motion --branch main" }, "devDependencies": { - "@biomejs/biome": "2.5.5", + "@biomejs/biome": "2.5.8", + "@vitest/coverage-v8": "4.1.10", "concurrently": "^9.1.0", + "jscpd": "5.0.14", + "knip": "6.32.2", "ultracite": "7.10.2", - "wrangler": "4.120.0" + "vitest": "4.1.10", + "wrangler": "4.121.0" }, "engines": { - "node": ">=20" + "node": ">=20.19" + }, + "pnpm": { + "overrides": { + "nanoid@>=3.0.0 <4.0.0": "3.3.17" + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c2b568..7519341 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,28 +4,43 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + nanoid@>=3.0.0 <4.0.0: 3.3.17 + importers: .: devDependencies: '@biomejs/biome': - specifier: 2.5.5 - version: 2.5.5 + specifier: 2.5.8 + version: 2.5.8 + '@vitest/coverage-v8': + specifier: 4.1.10 + version: 4.1.10(vitest@4.1.10) concurrently: specifier: ^9.1.0 version: 9.2.4 + jscpd: + specifier: 5.0.14 + version: 5.0.14 + knip: + specifier: 6.32.2 + version: 6.32.2 ultracite: specifier: 7.10.2 version: 7.10.2 + vitest: + specifier: 4.1.10 + version: 4.1.10(@vitest/coverage-v8@4.1.10)(vite@7.3.6(jiti@2.7.0)(yaml@2.9.0)) wrangler: - specifier: 4.120.0 - version: 4.120.0 + specifier: 4.121.0 + version: 4.121.0 server: devDependencies: partykit: - specifier: ^0.0.111 - version: 0.0.111 + specifier: ^0.0.115 + version: 0.0.115 typescript: specifier: ^5.6.0 version: 5.9.3 @@ -40,64 +55,85 @@ importers: specifier: ^5.6.0 version: 5.9.3 vite: - specifier: ^5.4.0 - version: 5.4.21 + specifier: ^7.3.6 + version: 7.3.6(jiti@2.7.0)(yaml@2.9.0) packages: - '@biomejs/biome@2.5.5': - resolution: {integrity: sha512-r1S8nFsAG1MY+vJFZALzIvwXAJv6ejDQ0mxP21Tgr9YK3ZFtjrvbBwDdNhx1rUqvccEIeNg20cYCNzl6Cr69pQ==} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@biomejs/biome@2.5.8': + resolution: {integrity: sha512-aeAeeJB9fSDc7Gq+2GqpQxA0qBj6gj1k2R6L1cYqGePKP/baIq1WX8y6B+D+nRsO5ViQL22K/8IwbqERW0q1nw==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.5.5': - resolution: {integrity: sha512-kUrAhXVWUrwmAUnV2iXSK7umxKFysTwvqK+Ty6ptUcLY/7T3SnCAjUowE4uvwaEej6nXZ7hu/dTtbokKdsPeag==} + '@biomejs/cli-darwin-arm64@2.5.8': + resolution: {integrity: sha512-mk1QON9PHllvvLN5gU3f4rMxeh4syK5p9OvKyWH6/W8ueh04uaC8TUXXByhGufWf/y5mQc03ZLM45zU+cmqMjA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.5.5': - resolution: {integrity: sha512-DamiYc5bUYZ2uxlfc+RLEPtz1Abb6PO5eTbOkufLpSGwd/7AMQAdxhFYiXmwwkJL8IsT8S7GvdgwDHqaMFAvKw==} + '@biomejs/cli-darwin-x64@2.5.8': + resolution: {integrity: sha512-bsGwFMBNyHPyiLSsQcZJxdoRrg1V4JL+d7wEsvUBczlP9U9lwM+7mzQHxI4o1mhBsTmdOBbAb6fHU3Z3snN45w==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.5.5': - resolution: {integrity: sha512-U4WMl/sy/E/Q73vf15VspakLRRs2LDFcCeBxJnQfXzssb88zpV6PJPaQ3ezhQ7H6Ht2/8bvuZeHgJWzmoxllZg==} + '@biomejs/cli-linux-arm64-musl@2.5.8': + resolution: {integrity: sha512-VcJNbstduTHx83NGAdhp78/JOcP45BZHXL7yNsfI1uGzdUgegAz2s+mSoT7wK6PBNzLoqG0zDOXaz/RQYVtSiw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [musl] - '@biomejs/cli-linux-arm64@2.5.5': - resolution: {integrity: sha512-lRKF/pH/1RiYiBKExi3TCZVAtvzEm77aifrvcNiDFrR9WxeAnDUjDnseb6y2XV85mjitLs6SILGm2XG77cHtSQ==} + '@biomejs/cli-linux-arm64@2.5.8': + resolution: {integrity: sha512-XmFiA0WPYFC+uiUDC8WRFzAIH9bo7vwQLav38Uoq4ETC+T/+uBi0TsYGJECkugY3r8USl3jc+Ae2/irAF6F2lQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [glibc] - '@biomejs/cli-linux-x64-musl@2.5.5': - resolution: {integrity: sha512-m7wC7tjX5Lrmo69dc4md8FeKpPU1NTCY1v7xUoQQ2vadWwNnBS0KZOG8471otFPHrTHihQJAjQPgMObpLvDe6A==} + '@biomejs/cli-linux-x64-musl@2.5.8': + resolution: {integrity: sha512-kKmiyokeISRGq2FLwvr+TzsgBusfxaZ0FZNLcOYOpCK/78tRrEjeEBLvq3xLZMpqbANgJdRPI7vZX8ZL37u9/w==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [musl] - '@biomejs/cli-linux-x64@2.5.5': - resolution: {integrity: sha512-H/O39nJEw/2Zm/fm7hrmxxoF8kK/aU1uCoPp70ruXVbomaAdLpJJnCmL11Q2JotT8QVHH06So04Oq53lCSwSwQ==} + '@biomejs/cli-linux-x64@2.5.8': + resolution: {integrity: sha512-S5wcm9OBDvLHodD4PUaN488hCpco9QD/9ZxuYJiw4euWtr/oQvLR72z2ixItH8Wd5BCm6FZaeb+YNvOoM1xHtQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [glibc] - '@biomejs/cli-win32-arm64@2.5.5': - resolution: {integrity: sha512-7BryINPuYypLUAH3o/o5ZdgomJ4zn3EDR0ChZJst7n32S6ZhKbgHXuYydLu+YAnx59ehGFR0z/MG6qnzQi3Yyw==} + '@biomejs/cli-win32-arm64@2.5.8': + resolution: {integrity: sha512-nILH0mzm3Hi3iEdd7o7GpB8kBR/mSQwfQG/tyBqyNrY2GFtcgwfV9nV8xLmbtUpMNY/Oi0Ml1XgfR4flOdq+AA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.5.5': - resolution: {integrity: sha512-bIBFo+n6MIxdNcVFy5CrurbKiZQiUciK3bt8+O9I4wjFZNTfXLpi+giq47522eXqW5NBc9ulx7dR1SlZKi2J5g==} + '@biomejs/cli-win32-x64@2.5.8': + resolution: {integrity: sha512-I2czzXTY61f3nFJxXoMDq80t7MivxDEnCjE+8sDKoFfcKMaoQdkqhIFQ3KyY0XLzeSpUBYeNAXgD+iOV/BU0VA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -129,8 +165,8 @@ packages: cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-64@1.20260801.1': - resolution: {integrity: sha512-wuJWbXpKvncJi1P0GKS+iYpN5tHdb7JPJJ/+6ZQe8zzovHHVMkLJPNBsgWpqeUhpM3g9qTwEKd2rglNKejuh5A==} + '@cloudflare/workerd-darwin-64@1.20260804.1': + resolution: {integrity: sha512-191/PPEFicRK2wK69eXzSjnLgHL79k7zR2VUpyIr8rhFgGns1b5bTHgnBuUrgU2LPbEtwbE5eL2hJ0uhLAFEow==} engines: {node: '>=16'} cpu: [x64] os: [darwin] @@ -141,8 +177,8 @@ packages: cpu: [arm64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260801.1': - resolution: {integrity: sha512-kwoZiTpnhNrF3+APx84Q/oAqvJ3sU9yefGagwm/ASaH/2W19x0vghkW/r4qCoHCK0WW7EPugZ+aXjgPMRtlq1Q==} + '@cloudflare/workerd-darwin-arm64@1.20260804.1': + resolution: {integrity: sha512-aI2cAFLsrNkSz3kLSQrgrO9ICTfY7jb2h0jgaWDE9mQLQQDfpXeYrKWt07tTgMmeNP79o9w3NZe3aqtt8mTGHQ==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] @@ -153,8 +189,8 @@ packages: cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-64@1.20260801.1': - resolution: {integrity: sha512-r0vAxCZH+Jih9Unm1yoyiByPNWNgawcKciOHDm5Q37ZVGOkKLsT9AtLe3yLSaul76WrKqtf+JP2n0WW32VBLJg==} + '@cloudflare/workerd-linux-64@1.20260804.1': + resolution: {integrity: sha512-KBCjxBIlN2jucfQGaTK4EgmPsWzQgYR/zYhPfi3mkWwdoTyG1dgrt2aizKps/SYse85ci/SOxojKk0/K7vstPw==} engines: {node: '>=16'} cpu: [x64] os: [linux] @@ -165,8 +201,8 @@ packages: cpu: [arm64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260801.1': - resolution: {integrity: sha512-zWgpdZtSozvIgzQNmQiDSF8yEOQJUkRAWNsDXXzAAoy+fCn8YUoSibj3mpFSbZRvbUldeBEaW6SCdC2VEMkhNQ==} + '@cloudflare/workerd-linux-arm64@1.20260804.1': + resolution: {integrity: sha512-7lswfarBZ7xkHpTFrNb74ExwOltIalVaASc+GOYfCNtnwFxK/JZSVByfm/hnZEBtrHU7fMY2z7dYT64rwS0pHg==} engines: {node: '>=16'} cpu: [arm64] os: [linux] @@ -177,8 +213,8 @@ packages: cpu: [x64] os: [win32] - '@cloudflare/workerd-windows-64@1.20260801.1': - resolution: {integrity: sha512-2oQz+Ksu4ji6e/+ZoYX+tWQEcxAii2p7l+iR8kx48W1llMalaufAsmVxTlk+3/vrM7D3/2c0iK448e0UQTcIMg==} + '@cloudflare/workerd-windows-64@1.20260804.1': + resolution: {integrity: sha512-GOgRWYtxISN5rAWfx3K7zubt3xEn0/ZPFrbcLL+GwT4ouCKyNoHqfT1cPNB6Nx7t8BHEjnuTG7gkHO4Wd5ORdg==} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -190,9 +226,18 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + '@emnapi/runtime@1.11.3': resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -660,9 +705,247 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@napi-rs/wasm-runtime@1.2.2': + resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 + + '@oxc-parser/binding-android-arm-eabi@0.143.0': + resolution: {integrity: sha512-n9uozULWflPqBtdmI8lAabLqGKNgLVNN0ZH8HfgCwpKGNtzRzauB76jTiW/3YLkcA7N1zskpi9GdVnZuu1SAvg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.143.0': + resolution: {integrity: sha512-9BbdjHETk6O3zH/DDid9IgBtF0GlpLabNKN231uraXpRDSfY+iiZxTP5bk1Z63GBownVdhdINFIeddmMz4MzpQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.143.0': + resolution: {integrity: sha512-gh+6ecoHUy4/sUcolBl/1qPXKBbYNxFY0Pk0ujgQvINTMSftJY7o4yb8gOkDJPeZeB8+a+u7xTe6umoP8N5HFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.143.0': + resolution: {integrity: sha512-qd1hl2d+lXgHv/VQ/M9qm8TrMC5T4RqDBwtOnl+1D0QMjwcz+8AaB4JSg8STgeag0GP6a6L74XEGAsrTSJWNzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.143.0': + resolution: {integrity: sha512-M5XXcNa7aOqLPKTR41msfghKu2yQ4xWvCm11/gwU0JzOzHNk5sgW//rVEjJ+LO48+VDAMzXTSzurUVxIDKwozw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.143.0': + resolution: {integrity: sha512-T/GXusuOkPNQhCQCSBbcU/N8j0rAypuDBl1IyFK+lyYT594XsVz80clPC/OtbSSpBGyJxj8uYEfctxVuxVYoww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.143.0': + resolution: {integrity: sha512-oKu4RcBlXSqo3OC62dp6YTnQaZIurNDpCX3BnAM3+bJxt7s8J2TJKMnC0UYer1qhlRaDCg6wkTaTw+2IlsZ12w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.143.0': + resolution: {integrity: sha512-WJBbD186AZmMGaSIhlktC+rPl8L3peCTXAh88Ih9uEvK0en2mPojGyCGYiL6mHtV1RPV3JyfJW5t6n5hh0lXhA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.143.0': + resolution: {integrity: sha512-t1AcYOwEzgceadT4v5e+vaCCb0AncCA3v5AyzfBAz/tMq11qzVccXKzNHtkWdjBsgvTKwRkaUF3QvT4kot8vcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.143.0': + resolution: {integrity: sha512-RsnO/NoD8376LMJq8JS8TwI0ieNaFRTuNe2GVJntQg6gwZNMENZsEbknHdVwjpOmxdGLGodcwaGSbAeRr5Bgjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.143.0': + resolution: {integrity: sha512-48fSVfR9TZi5CASZFyv0VC6z6BCoeihFsX031mAD/oSH7d9PYsPgIqza7d9mjP7Z2KTEpTFyH6SIu0Ui6R1vdg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.143.0': + resolution: {integrity: sha512-T8CpdD+SfE01DnIOD4HpVxu0ZJOfMJ/VhCvikKfaXAxkZ+9veyLM/D2hpi7Y2hFUyPmVQO3FNZHmYzV/WlVR4g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.143.0': + resolution: {integrity: sha512-QLdeMsCcacenPEFsfxnBUDF1y6opyz5+fmOz9bfD5Y7fiGCMupUCuB3KTPQhNwshIG1P9fPqar9MHxuBDd4bwQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.143.0': + resolution: {integrity: sha512-659ujfqLy6k7cuH3sbzhd8b+ztSq+i6E2E9pG78Q0BmHjAExfGIdgc8cGgMdwAozDXeZFHkJ+LXYJdWsaGdgyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.143.0': + resolution: {integrity: sha512-/Mw/9j4TfZcnKphPrzOE6t4MMknXadcAAuVUlDRTF/ETWB5xOgQvOJV2Mh9We/bWxZdoxaGAdc+hy4GuYwQ2yQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.143.0': + resolution: {integrity: sha512-8rIKWR2BFuifbIK/1XB9wTaSdtuJ25dlE7ZQYDnEwj/2xH2vHsxnvIjHT3ZjSVuLLwGGlSslIG/fbOJ8TV8rTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-win32-arm64-msvc@0.143.0': + resolution: {integrity: sha512-5U9kQYMfRRI6Zq7KDxgbIP0RMnKrfn3gLepRMgJuRkPSUALTiRCk9d/uyhb4lGDjUdzwK7mBkKqhLgzBPCmLpQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.143.0': + resolution: {integrity: sha512-25P7AaHk4R88Yv2XH4gToDVmh0cOu+bEURQU10CRrmvgabfRArSGAP5osmwUKeSUHj0VS50upbpbRWWW/m7mHA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.143.0': + resolution: {integrity: sha512-ORMh3JE1s6V7ySicdRK7vgaDQnn5o+UHg9ct989PlWHbel8O9ARrmWXM6kZjrBMtNucxNayQ8g69G0VfWzhANw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.143.0': + resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} + + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.24.2': + resolution: {integrity: sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.24.2': + resolution: {integrity: sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.24.2': + resolution: {integrity: sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.24.2': + resolution: {integrity: sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + resolution: {integrity: sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + resolution: {integrity: sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + resolution: {integrity: sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + resolution: {integrity: sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + resolution: {integrity: sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + resolution: {integrity: sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + resolution: {integrity: sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + resolution: {integrity: sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + resolution: {integrity: sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + resolution: {integrity: sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + resolution: {integrity: sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.24.2': + resolution: {integrity: sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + resolution: {integrity: sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + resolution: {integrity: sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==} + cpu: [x64] + os: [win32] + '@poppinss/colors@4.1.6': resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} @@ -817,9 +1100,59 @@ packages: '@speed-highlight/core@1.2.24': resolution: {integrity: sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@vitest/coverage-v8@4.1.10': + resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==} + peerDependencies: + '@vitest/browser': 4.1.10 + vitest: 4.1.10 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + acorn-walk@8.3.5: resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} engines: {node: '>=0.4.0'} @@ -840,6 +1173,13 @@ packages: as-table@1.0.55: resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.5: + resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -854,6 +1194,10 @@ packages: capnp-ts@0.7.0: resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -885,6 +1229,12 @@ packages: engines: {node: '>=18'} hasBin: true + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -913,6 +1263,9 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -923,6 +1276,9 @@ packages: error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -937,6 +1293,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + event-target-polyfill@0.0.4: resolution: {integrity: sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==} @@ -948,6 +1307,10 @@ packages: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + fast-string-truncated-width@3.0.3: resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} @@ -957,6 +1320,23 @@ packages: fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + formatly@0.3.0: + resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} + engines: {node: '>=18.3.0'} + hasBin: true + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -973,6 +1353,9 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + get-tsconfig@4.14.1: + resolution: {integrity: sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A==} + glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} @@ -984,6 +1367,9 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -1017,6 +1403,63 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + jscpd-darwin-arm64@5.0.14: + resolution: {integrity: sha512-Ojjl79SBuj9tEW6WbjZ1a/1ZOR89dneH9yLQYQu8WyWaQownttnx7RYFEHU6aGhS4jIvwUEbr+1wxzFTb37cwg==} + cpu: [arm64] + os: [darwin] + + jscpd-darwin-x64@5.0.14: + resolution: {integrity: sha512-DxFg5XvjMZ81iVeqillnM5apqcGCfNTbroNF+mPLr7RkHLGH6mudLgtO+ILL/hfpZXy1bF9oIY5BSudPmN/k9A==} + cpu: [x64] + os: [darwin] + + jscpd-linux-arm64-gnu@5.0.14: + resolution: {integrity: sha512-1uw+XBHEt9pONXNICSp5HpaVWPjG6mQ6deDXaq9Yb0xCNJkX4/8gmn0vhzekIyZD2DspRYKPUolbDsqm/HEdYg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + jscpd-linux-x64-gnu@5.0.14: + resolution: {integrity: sha512-dFTbyyrm+Z9pcXIVzJQCw8QAgiNqIiO69sm4AfA7/wFdPoizoVzjhaXsYXcSV4bs0aoPiWbNazg0J0HgslT/5A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + jscpd-linux-x64-musl@5.0.14: + resolution: {integrity: sha512-SayS7qQJvixyy9eR0+UjepkTsUUwqvlsiuSxfIdHgG2qzqoh/thnkgiu4By8fsiiDpQONsQrRrZDwHRQ3GDrBQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + jscpd-windows-x64-msvc@5.0.14: + resolution: {integrity: sha512-DqjxlVkUanlahGgY2lY7Zkrau4BUTI+AwWky+bPGK4kSK2AIOaUziY9Q19u8b58idXmJA9FKK98Fuu4ajNXVjQ==} + cpu: [x64] + os: [win32] + + jscpd@5.0.14: + resolution: {integrity: sha512-zge+FPZZAymt2Do5Z0+QHyIn4/XcUhrO/W7of9HcHZfx2AK8++dYhLA1uWtwXj47ml3Of8PbcUW4wUWvYMCc3w==} + engines: {node: '>=18'} + hasBin: true + jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} @@ -1024,10 +1467,25 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + knip@6.32.2: + resolution: {integrity: sha512-WXTXbmocrw7gqm1A1TQvFN0OgJ7hUSU6E1g6SPRIzzHFogUBhXByc7cYeOFVtJ2uODg7DP4VbESYBYnfbtBYsg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.4: + resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -1040,8 +1498,8 @@ packages: engines: {node: '>=16.13'} hasBin: true - miniflare@5.20260801.1-alpha: - resolution: {integrity: sha512-BHPVzIDA6mbx7LefxpvkXW7DHx9FKB9GorZatbnrrFTt3CVMU8zuUpbgyCuebwDKcTTOZos43ta8GQ0eMVEpxA==} + miniflare@5.20260804.1-alpha: + resolution: {integrity: sha512-J0QBHEj+d75TyFE9VhH+2dXgvdYt/pfyDlm+9IiYUFocQvqYy9iuW1DIqNawh1N7Kr6iMrDzVkgDSdt6pA75uA==} engines: {node: '>=22.0.0'} minimatch@10.2.6: @@ -1052,6 +1510,9 @@ packages: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1059,8 +1520,8 @@ packages: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.17: + resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -1073,12 +1534,26 @@ packages: engines: {node: '>=18'} hasBin: true + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + ohash@1.1.6: + resolution: {integrity: sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==} + onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} - partykit@0.0.111: - resolution: {integrity: sha512-pbUzJmulPTqZWgtH++eClnLlddTWS0RPKOAQyexYiSqGEg2/Ff84Ep0qWFqy+53wiz098DPFekRI375Qy2GsZw==} + oxc-parser@0.143.0: + resolution: {integrity: sha512-ov0NzaDCOInknS7mP1cwKdJERt3utPW8ldjtdUXQ8Ty0GEFD08wk422vCUN0d7pST6kqtV7dxoI9w1Zi0l/9TA==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.24.2: + resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==} + + partykit@0.0.115: + resolution: {integrity: sha512-WHmJIZsAzRWrm1lrtU7wcl0tpD4rg2vgmn4+hXGPjU4mnXgFxyjVq2ZzO547xRXa1IbETOP6J9INl/ergR99bA==} hasBin: true partysocket@1.3.0: @@ -1104,12 +1579,22 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + postcss@8.5.19: resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} engines: {node: ^10 || ^12 || >=14} @@ -1121,6 +1606,9 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + rollup@4.62.2: resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -1150,6 +1638,9 @@ packages: resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -1157,6 +1648,10 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + smol-toml@1.8.0: + resolution: {integrity: sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==} + engines: {node: '>= 18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1165,9 +1660,15 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stacktracey@2.2.0: resolution: {integrity: sha512-ETyQEz+CzXiLjEbyJqpbp+/T79RQD/6wqFucRBIlVNZfYq2Ay7wbretD4cxpbymZlaPWx58aIhPEY1Cr8DlVvg==} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + stoppable@1.1.0: resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} engines: {node: '>=4', npm: '>=6'} @@ -1184,6 +1685,10 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -1200,14 +1705,29 @@ packages: resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} engines: {node: '>=18'} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@1.3.0: resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} + tinyglobby@0.2.17: + 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'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1216,6 +1736,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + ultracite@7.10.2: resolution: {integrity: sha512-WP1Hu/BKy/BDgntaiU33uq2Y8hKL2gEO5li2wfg7XFvmlyIkGDEy8qbWg7GHsxTEjEpWOAKpyFSF8aqc3JNEGQ==} hasBin: true @@ -1228,6 +1751,10 @@ packages: oxlint: optional: true + unbash@4.0.10: + resolution: {integrity: sha512-b7zoBQvpWp0vuN5q2vK2RRBR2SvuruQAs50DApdDveBSn3eSYd84IaHodFqQIMlvY9K2VnyBUEXgwOBuGU9GBg==} + engines: {node: '>=14'} + undici@5.29.0: resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} engines: {node: '>=14.0'} @@ -1236,25 +1763,33 @@ packages: resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} engines: {node: '>=20.18.1'} + unenv@2.0.0-rc.0: + resolution: {integrity: sha512-H0kl2w8jFL/FAk0xvjVing4bS3jd//mbg1QChDnn58l9Sc5RtduaKmLAL8n+eBw5jJo8ZjYV7CrEGage5LAOZQ==} + unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} - vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} + vite@7.3.6: + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true + jiti: + optional: true less: optional: true lightningcss: @@ -1269,28 +1804,82 @@ packages: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + workerd@1.20240718.0: resolution: {integrity: sha512-w7lOLRy0XecQTg/ujTLWBiJJuoQvzB3CdQ6/8Wgex3QxFhV9Pbnh3UbwIuUfMw3OCCPQc4o7y+1P+mISAgp6yg==} engines: {node: '>=16'} hasBin: true - workerd@1.20260801.1: - resolution: {integrity: sha512-/g9JGTyqnHtoIscpBHqKD8swE2V4StBs2i69PmLiOhH45OP95jCFICl4F1hKlgN57rqfni5LiCitttoX5OOkVA==} + workerd@1.20260804.1: + resolution: {integrity: sha512-b0P38g5/ssemwWxd/mafNYggEZ0ere7PCiUH6RCHkgqjRhhXTP45nDiL2L3iIvi8uF2IjNrGpVgMXEunCaeY/w==} engines: {node: '>=16'} hasBin: true - wrangler@4.120.0: - resolution: {integrity: sha512-cBmu/MeaB/fPacC0JpATs4duTOCagBxrZo+vBzuTX06tLzwSyAHE1drlHUZ8rP0VqVz1fy3ReGYTiHdKkoHltg==} + wrangler@4.121.0: + resolution: {integrity: sha512-dcARWk6CyaD0vJBSLjJn4K2yo+mYko73hzryq+t/9DXnyAq877RWIMl7uOMcDKs5dDXdoickNhZTKxBYT9XKsA==} engines: {node: '>=22.0.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^5.20260801.1 + '@cloudflare/workers-types': ^5.20260804.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -1360,39 +1949,54 @@ packages: snapshots: - '@biomejs/biome@2.5.5': + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@biomejs/biome@2.5.8': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.5.5 - '@biomejs/cli-darwin-x64': 2.5.5 - '@biomejs/cli-linux-arm64': 2.5.5 - '@biomejs/cli-linux-arm64-musl': 2.5.5 - '@biomejs/cli-linux-x64': 2.5.5 - '@biomejs/cli-linux-x64-musl': 2.5.5 - '@biomejs/cli-win32-arm64': 2.5.5 - '@biomejs/cli-win32-x64': 2.5.5 + '@biomejs/cli-darwin-arm64': 2.5.8 + '@biomejs/cli-darwin-x64': 2.5.8 + '@biomejs/cli-linux-arm64': 2.5.8 + '@biomejs/cli-linux-arm64-musl': 2.5.8 + '@biomejs/cli-linux-x64': 2.5.8 + '@biomejs/cli-linux-x64-musl': 2.5.8 + '@biomejs/cli-win32-arm64': 2.5.8 + '@biomejs/cli-win32-x64': 2.5.8 - '@biomejs/cli-darwin-arm64@2.5.5': + '@biomejs/cli-darwin-arm64@2.5.8': optional: true - '@biomejs/cli-darwin-x64@2.5.5': + '@biomejs/cli-darwin-x64@2.5.8': optional: true - '@biomejs/cli-linux-arm64-musl@2.5.5': + '@biomejs/cli-linux-arm64-musl@2.5.8': optional: true - '@biomejs/cli-linux-arm64@2.5.5': + '@biomejs/cli-linux-arm64@2.5.8': optional: true - '@biomejs/cli-linux-x64-musl@2.5.5': + '@biomejs/cli-linux-x64-musl@2.5.8': optional: true - '@biomejs/cli-linux-x64@2.5.5': + '@biomejs/cli-linux-x64@2.5.8': optional: true - '@biomejs/cli-win32-arm64@2.5.5': + '@biomejs/cli-win32-arm64@2.5.8': optional: true - '@biomejs/cli-win32-x64@2.5.5': + '@biomejs/cli-win32-x64@2.5.8': optional: true '@clack/core@1.4.3': @@ -1409,40 +2013,40 @@ snapshots: '@cloudflare/kv-asset-handler@0.5.0': {} - '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260801.1)': + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260804.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260801.1 + workerd: 1.20260804.1 '@cloudflare/workerd-darwin-64@1.20240718.0': optional: true - '@cloudflare/workerd-darwin-64@1.20260801.1': + '@cloudflare/workerd-darwin-64@1.20260804.1': optional: true '@cloudflare/workerd-darwin-arm64@1.20240718.0': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260801.1': + '@cloudflare/workerd-darwin-arm64@1.20260804.1': optional: true '@cloudflare/workerd-linux-64@1.20240718.0': optional: true - '@cloudflare/workerd-linux-64@1.20260801.1': + '@cloudflare/workerd-linux-64@1.20260804.1': optional: true '@cloudflare/workerd-linux-arm64@1.20240718.0': optional: true - '@cloudflare/workerd-linux-arm64@1.20260801.1': + '@cloudflare/workerd-linux-arm64@1.20260804.1': optional: true '@cloudflare/workerd-windows-64@1.20240718.0': optional: true - '@cloudflare/workerd-windows-64@1.20260801.1': + '@cloudflare/workerd-windows-64@1.20260804.1': optional: true '@cloudflare/workers-types@4.20240718.0': {} @@ -1451,11 +2055,27 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -1715,11 +2335,143 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@oxc-parser/binding-android-arm-eabi@0.143.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.143.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.143.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.143.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.143.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.143.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.143.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.143.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.143.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.143.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.143.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.143.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.143.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.143.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.143.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.143.0': + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.143.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.143.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.143.0': + optional: true + + '@oxc-project/types@0.143.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + optional: true + + '@oxc-resolver/binding-android-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.24.2': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.24.2': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + optional: true + '@poppinss/colors@4.1.6': dependencies: kleur: 4.1.5 @@ -1811,8 +2563,77 @@ snapshots: '@speed-highlight/core@1.2.24': {} + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.9': {} + '@vitest/coverage-v8@4.1.10(vitest@4.1.10)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.10 + ast-v8-to-istanbul: 1.0.5 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.4 + obug: 2.1.4 + std-env: 4.2.0 + tinyrainbow: 3.1.1 + vitest: 4.1.10(@vitest/coverage-v8@4.1.10)(vite@7.3.6(jiti@2.7.0)(yaml@2.9.0)) + + '@vitest/expect@4.1.10': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.1 + + '@vitest/mocker@4.1.10(vite@7.3.6(jiti@2.7.0)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.6(jiti@2.7.0)(yaml@2.9.0) + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 + acorn-walk@8.3.5: dependencies: acorn: 8.17.0 @@ -1829,6 +2650,14 @@ snapshots: dependencies: printable-characters: 1.0.42 + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.5: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + balanced-match@4.0.4: {} blake3-wasm@2.1.5: {} @@ -1844,6 +2673,8 @@ snapshots: transitivePeerDependencies: - supports-color + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -1880,6 +2711,10 @@ snapshots: tree-kill: 1.2.2 yargs: 17.7.2 + confbox@0.1.8: {} + + convert-source-map@2.0.0: {} + cookie@0.7.2: {} cookie@1.1.1: {} @@ -1898,12 +2733,16 @@ snapshots: deepmerge@4.3.1: {} + defu@6.1.7: {} + detect-libc@2.1.2: {} emoji-regex@8.0.0: {} error-stack-parser-es@1.0.5: {} + es-module-lexer@2.3.1: {} + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -1961,6 +2800,10 @@ snapshots: escalade@3.2.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + event-target-polyfill@0.0.4: {} execa@8.0.1: @@ -1977,6 +2820,8 @@ snapshots: exit-hook@2.2.1: {} + expect-type@1.4.0: {} + fast-string-truncated-width@3.0.3: {} fast-string-width@3.0.2: @@ -1987,6 +2832,18 @@ snapshots: dependencies: fast-string-width: 3.0.2 + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + formatly@0.3.0: + dependencies: + fd-package-json: 2.0.0 + fsevents@2.3.3: optional: true @@ -1999,6 +2856,10 @@ snapshots: get-stream@8.0.1: {} + get-tsconfig@4.14.1: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-to-regexp@0.4.1: {} glob@13.0.6: @@ -2009,6 +2870,8 @@ snapshots: has-flag@4.0.0: {} + html-escaper@2.0.2: {} + human-signals@5.0.0: {} is-docker@3.0.0: {} @@ -2031,12 +2894,86 @@ snapshots: isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jiti@2.7.0: {} + + js-tokens@10.0.0: {} + + jscpd-darwin-arm64@5.0.14: + optional: true + + jscpd-darwin-x64@5.0.14: + optional: true + + jscpd-linux-arm64-gnu@5.0.14: + optional: true + + jscpd-linux-x64-gnu@5.0.14: + optional: true + + jscpd-linux-x64-musl@5.0.14: + optional: true + + jscpd-windows-x64-msvc@5.0.14: + optional: true + + jscpd@5.0.14: + optionalDependencies: + jscpd-darwin-arm64: 5.0.14 + jscpd-darwin-x64: 5.0.14 + jscpd-linux-arm64-gnu: 5.0.14 + jscpd-linux-x64-gnu: 5.0.14 + jscpd-linux-x64-musl: 5.0.14 + jscpd-windows-x64-msvc: 5.0.14 + jsonc-parser@3.3.1: {} kleur@4.1.5: {} + knip@6.32.2: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + formatly: 0.3.0 + get-tsconfig: 4.14.1 + jiti: 2.7.0 + oxc-parser: 0.143.0 + oxc-resolver: 11.24.2 + picomatch: 4.0.5 + smol-toml: 1.8.0 + strip-json-comments: 5.0.3 + tinyglobby: 0.2.17 + unbash: 4.0.10 + yaml: 2.9.0 + zod: 4.4.3 + lru-cache@11.5.2: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.4: + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + merge-stream@2.0.0: {} mimic-fn@4.0.0: {} @@ -2060,12 +2997,12 @@ snapshots: - supports-color - utf-8-validate - miniflare@5.20260801.1-alpha: + miniflare@5.20260804.1-alpha: dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.35.2 undici: 7.29.0 - workerd: 1.20260801.1 + workerd: 1.20260804.1 ws: 8.21.0 youch: 4.1.0-beta.10 transitivePeerDependencies: @@ -2078,11 +3015,18 @@ snapshots: minipass@7.1.3: {} + mlly@1.8.2: + dependencies: + acorn: 8.17.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.4 + ms@2.1.3: {} mustache@4.2.0: {} - nanoid@3.3.16: {} + nanoid@3.3.17: {} npm-run-path@5.3.0: dependencies: @@ -2094,16 +3038,68 @@ snapshots: pathe: 2.0.3 tinyexec: 1.3.0 + obug@2.1.4: {} + + ohash@1.1.6: {} + onetime@6.0.0: dependencies: mimic-fn: 4.0.0 - partykit@0.0.111: + oxc-parser@0.143.0: + dependencies: + '@oxc-project/types': 0.143.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.143.0 + '@oxc-parser/binding-android-arm64': 0.143.0 + '@oxc-parser/binding-darwin-arm64': 0.143.0 + '@oxc-parser/binding-darwin-x64': 0.143.0 + '@oxc-parser/binding-freebsd-x64': 0.143.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.143.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.143.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.143.0 + '@oxc-parser/binding-linux-arm64-musl': 0.143.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.143.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.143.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.143.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.143.0 + '@oxc-parser/binding-linux-x64-gnu': 0.143.0 + '@oxc-parser/binding-linux-x64-musl': 0.143.0 + '@oxc-parser/binding-openharmony-arm64': 0.143.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.143.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.143.0 + '@oxc-parser/binding-win32-x64-msvc': 0.143.0 + + oxc-resolver@11.24.2: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.24.2 + '@oxc-resolver/binding-android-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-x64': 11.24.2 + '@oxc-resolver/binding-freebsd-x64': 11.24.2 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-arm64-musl': 11.24.2 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-musl': 11.24.2 + '@oxc-resolver/binding-linux-s390x-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-musl': 11.24.2 + '@oxc-resolver/binding-openharmony-arm64': 11.24.2 + '@oxc-resolver/binding-wasm32-wasi': 11.24.2 + '@oxc-resolver/binding-win32-arm64-msvc': 11.24.2 + '@oxc-resolver/binding-win32-x64-msvc': 11.24.2 + + partykit@0.0.115: dependencies: '@cloudflare/workers-types': 4.20240718.0 clipboardy: 4.0.0 esbuild: 0.21.5 miniflare: 3.20240718.0 + ts-dedent: 2.3.0 + unenv: 2.0.0-rc.0 yoga-wasm-web: 0.3.3 optionalDependencies: fsevents: 2.3.3 @@ -2127,13 +3123,23 @@ snapshots: path-to-regexp@6.3.0: {} + pathe@1.1.2: {} + pathe@2.0.3: {} picocolors@1.1.1: {} + picomatch@4.0.5: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + postcss@8.5.19: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.17 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -2141,6 +3147,8 @@ snapshots: require-directory@2.1.1: {} + resolve-pkg-maps@1.0.0: {} + rollup@4.62.2: dependencies: '@types/estree': 1.0.9 @@ -2218,19 +3226,27 @@ snapshots: shell-quote@1.9.0: {} + siginfo@2.0.0: {} + signal-exit@4.1.0: {} sisteransi@1.0.5: {} + smol-toml@1.8.0: {} + source-map-js@1.2.1: {} source-map@0.6.1: {} + stackback@0.0.2: {} + stacktracey@2.2.0: dependencies: as-table: 1.0.55 get-source: 2.0.12 + std-env@4.2.0: {} + stoppable@1.1.0: {} string-width@4.2.3: @@ -2245,6 +3261,8 @@ snapshots: strip-final-newline@3.0.0: {} + strip-json-comments@5.0.3: {} + supports-color@10.2.2: {} supports-color@7.2.0: @@ -2257,14 +3275,27 @@ snapshots: system-architecture@0.1.0: {} + tinybench@2.9.0: {} + tinyexec@1.3.0: {} + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinyrainbow@3.1.1: {} + tree-kill@1.2.2: {} + ts-dedent@2.3.0: {} + tslib@2.8.1: {} typescript@5.9.3: {} + ufo@1.6.4: {} + ultracite@7.10.2: dependencies: '@clack/prompts': 1.7.0 @@ -2277,28 +3308,77 @@ snapshots: yaml: 2.9.0 zod: 4.4.3 + unbash@4.0.10: {} + undici@5.29.0: dependencies: '@fastify/busboy': 2.1.1 undici@7.29.0: {} + unenv@2.0.0-rc.0: + dependencies: + defu: 6.1.7 + mlly: 1.8.2 + ohash: 1.1.6 + pathe: 1.1.2 + ufo: 1.6.4 + unenv@2.0.0-rc.24: dependencies: pathe: 2.0.3 - vite@5.4.21: + vite@7.3.6(jiti@2.7.0)(yaml@2.9.0): dependencies: - esbuild: 0.21.5 + esbuild: 0.28.1 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 postcss: 8.5.19 rollup: 4.62.2 + tinyglobby: 0.2.17 optionalDependencies: fsevents: 2.3.3 + jiti: 2.7.0 + yaml: 2.9.0 + + vitest@4.1.10(@vitest/coverage-v8@4.1.10)(vite@7.3.6(jiti@2.7.0)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@7.3.6(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 7.3.6(jiti@2.7.0)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@vitest/coverage-v8': 4.1.10(vitest@4.1.10) + transitivePeerDependencies: + - msw + + walk-up-path@4.0.0: {} which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + workerd@1.20240718.0: optionalDependencies: '@cloudflare/workerd-darwin-64': 1.20240718.0 @@ -2307,24 +3387,24 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20240718.0 '@cloudflare/workerd-windows-64': 1.20240718.0 - workerd@1.20260801.1: + workerd@1.20260804.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260801.1 - '@cloudflare/workerd-darwin-arm64': 1.20260801.1 - '@cloudflare/workerd-linux-64': 1.20260801.1 - '@cloudflare/workerd-linux-arm64': 1.20260801.1 - '@cloudflare/workerd-windows-64': 1.20260801.1 + '@cloudflare/workerd-darwin-64': 1.20260804.1 + '@cloudflare/workerd-darwin-arm64': 1.20260804.1 + '@cloudflare/workerd-linux-64': 1.20260804.1 + '@cloudflare/workerd-linux-arm64': 1.20260804.1 + '@cloudflare/workerd-windows-64': 1.20260804.1 - wrangler@4.120.0: + wrangler@4.121.0: dependencies: '@cloudflare/kv-asset-handler': 0.5.0 - '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260801.1) + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260804.1) blake3-wasm: 2.1.5 esbuild: 0.28.1 - miniflare: 5.20260801.1-alpha + miniflare: 5.20260804.1-alpha path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260801.1 + workerd: 1.20260804.1 optionalDependencies: fsevents: 2.3.3 transitivePeerDependencies: diff --git a/protocol/protocol.test.ts b/protocol/protocol.test.ts new file mode 100644 index 0000000..0e3e109 --- /dev/null +++ b/protocol/protocol.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, it } from "vitest"; + +import { + CODE_LENGTH, + isPosePacket, + isValidRoomCode, + makeRoomCode, + parseMessage, + type Joints, +} from "./protocol"; + +const joints: Joints = { + head: [0.5, 0.2], + leftHand: [0.3, 0.5], + rightHand: [0.7, 0.5], + torso: [0.5, 0.5], + leftKnee: [0.4, 0.7], + rightKnee: [0.6, 0.7], + leftFoot: [0.4, 0.9], + rightFoot: [0.6, 0.9], +}; + +describe("room codes", () => { + it("generates an unambiguous fixed-length code", () => { + const code = makeRoomCode(() => 0); + + expect(code).toBe("2".repeat(CODE_LENGTH)); + expect(isValidRoomCode(code)).toBe(true); + }); + + it("accepts lowercase but rejects ambiguous and malformed codes", () => { + expect(isValidRoomCode("bcdfgh")).toBe(true); + expect(isValidRoomCode("BCDFG0")).toBe(false); + expect(isValidRoomCode("BCDFG")).toBe(false); + }); +}); + +describe("wire validation", () => { + it("accepts a complete finite pose packet", () => { + expect( + isPosePacket({ + v: 1, + type: "pose", + seq: 1, + sentAt: 25, + quality: 0.9, + joints, + }) + ).toBe(true); + }); + + it("rejects missing joints and non-finite coordinates", () => { + const { rightFoot: _rightFoot, ...missingJoint } = joints; + const invalidCoordinate = { ...joints, head: [Number.NaN, 0.2] }; + + expect( + isPosePacket({ + v: 1, + type: "pose", + seq: 1, + sentAt: 25, + quality: 0.9, + joints: missingJoint, + }) + ).toBe(false); + expect( + isPosePacket({ + v: 1, + type: "pose", + seq: 1, + sentAt: 25, + quality: 0.9, + joints: invalidCoordinate, + }) + ).toBe(false); + }); + + it("parses object messages and rejects malformed input", () => { + expect(parseMessage('{"v":1,"type":"ping","t":42}')).toEqual({ + v: 1, + type: "ping", + t: 42, + }); + expect(parseMessage("not json")).toBeNull(); + expect(parseMessage("[]")).toBeNull(); + expect(parseMessage('{"v":1}')).toBeNull(); + }); +}); diff --git a/scripts/check-code-health.mjs b/scripts/check-code-health.mjs new file mode 100644 index 0000000..6516d2f --- /dev/null +++ b/scripts/check-code-health.mjs @@ -0,0 +1,376 @@ +#!/usr/bin/env node + +import { spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import process from "node:process"; +import { fileURLToPath } from "node:url"; + +const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const productionPaths = ["protocol", "server/src", "web/src", "ios/Sources"]; +const typescriptPaths = [ + "protocol", + "server/src", + "web/src", + "scripts", + "vitest.config.ts", + "package.json", + "web/package.json", + "server/package.json", + "biome.json", +]; + +function log(message) { + process.stdout.write(`${message}\n`); +} + +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: options.cwd ?? projectRoot, + encoding: "utf8", + maxBuffer: 32 * 1024 * 1024, + }); + if (result.error) throw result.error; + if (result.status !== 0 && !options.allowFailure) { + process.stdout.write(result.stdout ?? ""); + process.stderr.write(result.stderr ?? ""); + throw new Error(`${command} exited with status ${result.status}`); + } + return result; +} + +function failRegressions(label, observed, baseline) { + const regressions = Object.entries(baseline).filter( + ([key, maximum]) => observed[key] > maximum + ); + if (regressions.length > 0) { + throw new Error( + regressions + .map( + ([key, maximum]) => + `${label} ${key} regressed: ${observed[key]} > ${maximum}` + ) + .join("\n") + ); + } + if ( + Object.entries(baseline).some(([key, maximum]) => observed[key] < maximum) + ) { + log(`${label} improved; lower the checked-in baseline intentionally.`); + } +} + +function checkMinimums(label, observed, minimums) { + const regressions = Object.entries(minimums).filter( + ([key, minimum]) => observed[key] + Number.EPSILON < minimum + ); + if (regressions.length > 0) { + throw new Error( + regressions + .map( + ([key, minimum]) => + `${label} ${key} regressed: ${observed[key]} < ${minimum}` + ) + .join("\n") + ); + } + if ( + Object.entries(minimums).some(([key, minimum]) => observed[key] > minimum) + ) { + log(`${label} improved; raise the checked-in baseline intentionally.`); + } +} + +function checkFormat() { + const result = run( + "pnpm", + [ + "exec", + "biome", + "check", + "--formatter-enabled=true", + "--linter-enabled=false", + "--assist-enabled=false", + "--reporter=json", + ...typescriptPaths, + ], + { allowFailure: true } + ); + const report = JSON.parse(result.stdout); + const observed = { files: report.summary.errors }; + log(`TypeScript format debt: ${observed.files} files.`); + // Ratcheted legacy debt: https://github.com/Significant-Hobbies/motion/issues/26 + failRegressions("TypeScript format", observed, { files: 19 }); +} + +function checkCoverage() { + const reportDirectory = mkdtempSync(join(tmpdir(), "motion-coverage-")); + run("pnpm", [ + "exec", + "vitest", + "run", + "--coverage", + "--coverage.reporter=json-summary", + `--coverage.reportsDirectory=${reportDirectory}`, + ]); + const total = JSON.parse( + readFileSync(join(reportDirectory, "coverage-summary.json"), "utf8") + ).total; + const observed = { + lines: total.lines.pct, + branches: total.branches.pct, + functions: total.functions.pct, + statements: total.statements.pct, + }; + log( + `TypeScript coverage: ${observed.lines}% lines, ${observed.branches}% branches, ` + + `${observed.functions}% functions, ${observed.statements}% statements.` + ); + // Ratcheted legacy debt: https://github.com/Significant-Hobbies/motion/issues/26 + checkMinimums("TypeScript coverage", observed, { + lines: 6.4, + branches: 10.06, + functions: 7.01, + statements: 6.49, + }); +} + +function checkComplexity() { + const result = run("uvx", [ + "--from", + "lizard==1.23.0", + "lizard", + ...productionPaths, + "-x", + "**/*.test.*", + "--csv", + ]); + const rows = result.stdout + .trim() + .split("\n") + .map((line) => line.match(/^(\d+),(\d+),(\d+),(\d+),(\d+),/u)) + .filter(Boolean) + .map((match) => match.slice(1).map(Number)); + const observed = { + functions: rows.length, + nloc: rows.reduce((sum, row) => sum + row[0], 0), + violations: rows.filter((row) => row[1] > 15 || row[4] > 100 || row[3] > 7) + .length, + maxCcn: Math.max(...rows.map((row) => row[1])), + maxLength: Math.max(...rows.map((row) => row[4])), + maxParams: Math.max(...rows.map((row) => row[3])), + }; + log( + `Complexity: ${observed.functions} functions, ${observed.nloc} NLOC, ` + + `${observed.violations} violations; max CCN ${observed.maxCcn}, ` + + `max length ${observed.maxLength}, max params ${observed.maxParams}.` + ); + // Ratcheted legacy debt: https://github.com/Significant-Hobbies/motion/issues/26 + failRegressions("Complexity", observed, { + violations: 6, + maxCcn: 22, + maxLength: 189, + maxParams: 7, + }); +} + +function checkDuplication() { + const outputDirectory = mkdtempSync(join(tmpdir(), "motion-jscpd-")); + run("pnpm", [ + "exec", + "jscpd", + ...productionPaths, + "--format", + "javascript,typescript,swift", + "--min-lines", + "8", + "--min-tokens", + "60", + "--mode", + "strict", + "--ignore", + "**/*.test.*,**/node_modules/**,**/coverage/**,**/dist/**,**/Resources/webgame/**", + "--reporters", + "json", + "--output", + outputDirectory, + "--silent", + "--no-tips", + ]); + const observed = JSON.parse( + readFileSync(join(outputDirectory, "jscpd-report.json"), "utf8") + ).statistics.total; + log( + `Duplication: ${observed.duplicatedLines}/${observed.lines} lines ` + + `(${observed.percentage.toFixed(4)}%), ${observed.clones} groups across ` + + `${observed.sources} files.` + ); + // Ratcheted legacy debt: https://github.com/Significant-Hobbies/motion/issues/26 + failRegressions("Duplication", observed, { + clones: 7, + duplicatedLines: 102, + percentage: 0.8814379536812997, + }); +} + +function checkDependencies() { + const result = run("pnpm", ["audit", "--json"], { allowFailure: true }); + const report = JSON.parse(result.stdout); + const severe = Object.entries(report.advisories ?? {}).filter( + ([, advisory]) => ["critical", "high"].includes(advisory.severity) + ); + const allowedHigh = new Set(["1114638", "1114640", "1121245"]); + const unexpected = severe.filter(([id]) => !allowedHigh.has(id)); + const missing = [...allowedHigh].filter( + (id) => !severe.some(([observedId]) => observedId === id) + ); + const reviewDate = new Date("2026-09-12T00:00:00Z"); + log( + `Dependencies: ${severe.length} critical/high advisories; ` + + `${severe.length - unexpected.length} accepted PartyKit/Miniflare findings.` + ); + if (Date.now() >= reviewDate.getTime()) { + throw new Error( + "PartyKit dependency-risk exception expired on 2026-09-12 (#26)." + ); + } + if (unexpected.length > 0) { + throw new Error( + `Unexpected critical/high advisories: ${unexpected + .map(([id, advisory]) => `${id}/${advisory.github_advisory_id}`) + .join(", ")}` + ); + } + if (missing.length > 0) { + log( + `Dependency risk improved; remove resolved exceptions: ${missing.join(", ")}.` + ); + } +} + +function countMatches(pattern) { + const result = run( + "git", + [ + "grep", + "-n", + "-E", + pattern, + "--", + ...productionPaths, + ":(exclude)**/*.test.*", + ], + { allowFailure: true } + ); + return result.stdout.trim() ? result.stdout.trim().split("\n") : []; +} + +function checkSuppressions() { + const matches = countMatches( + "(^|[[:space:]])(//|/\\*)[[:space:]]*(eslint-disable|@ts-ignore|@ts-expect-error|istanbul ignore|c8 ignore|swiftlint:disable|biome-ignore)" + ); + log(`Suppressions: ${matches.length} inline directives.`); + if (matches.length > 0) { + throw new Error(`Unjustified suppressions:\n${matches.join("\n")}`); + } +} + +function checkHygiene() { + const conflictMarkers = run( + "git", + ["grep", "-n", "-E", "^(<<<<<<<|=======|>>>>>>>)", "--", "."], + { allowFailure: true } + ).stdout.trim(); + if (conflictMarkers) throw new Error(`Conflict markers:\n${conflictMarkers}`); + const todos = countMatches("TODO|FIXME"); + if (todos.length > 0) { + throw new Error(`Durable TODO/FIXME markers:\n${todos.join("\n")}`); + } + run("git", ["diff", "--check", "HEAD", "--", "."]); + log("Repository hygiene: clean."); +} + +function checkSwiftFormat() { + const result = run( + "xcrun", + ["swift-format", "lint", "--strict", "--recursive", "ios/Sources"], + { allowFailure: true } + ); + const diagnostics = `${result.stdout}\n${result.stderr}` + .split("\n") + .filter((line) => line.includes("error:")).length; + log(`Swift format debt: ${diagnostics} diagnostics.`); + // Ratcheted legacy debt: https://github.com/Significant-Hobbies/motion/issues/26 + failRegressions("Swift format", { diagnostics }, { diagnostics: 4553 }); +} + +function checkSwiftUnused() { + const indexStore = process.env.MOTION_INDEX_STORE; + if (!indexStore) { + throw new Error( + "Set MOTION_INDEX_STORE to a completed Motion build index store." + ); + } + const versionResult = run("periphery", ["version"]); + const version = `${versionResult.stdout}\n${versionResult.stderr}`.trim(); + const major = Number(version.match(/\d+/u)?.[0]); + if (!Number.isInteger(major)) { + throw new Error(`Could not parse Periphery version: ${version}`); + } + const args = [ + "scan", + major < 3 ? "--workspace" : "--project", + "ios/Motion.xcworkspace", + "--schemes", + "Motion", + "--skip-build", + "--index-store-path", + indexStore, + "--format", + "json", + "--relative-results", + "--disable-update-check", + "--quiet", + "--retain-objc-accessible", + "--retain-codable-properties", + "--retain-swift-ui-previews", + ]; + if (major < 3) args.push("--targets", "Motion"); + const result = run("periphery", args); + const observed = { findings: JSON.parse(result.stdout).length }; + log( + `Swift unused-code debt: ${observed.findings} Periphery ${version} findings.` + ); + // Ratcheted legacy debt: https://github.com/Significant-Hobbies/motion/issues/26 + failRegressions("Swift unused code", observed, { findings: 68 }); +} + +const checks = { + complexity: checkComplexity, + coverage: checkCoverage, + dependencies: checkDependencies, + duplication: checkDuplication, + format: checkFormat, + hygiene: checkHygiene, + suppressions: checkSuppressions, + "swift-format": checkSwiftFormat, + "swift-unused": checkSwiftUnused, +}; +const selected = process.argv[2]; + +if (!Object.hasOwn(checks, selected)) { + process.stderr.write( + `Usage: check-code-health.mjs <${Object.keys(checks).join("|")}>\n` + ); + process.exit(2); +} + +try { + checks[selected](); +} catch (error) { + process.stderr.write( + `${error instanceof Error ? error.message : String(error)}\n` + ); + process.exit(1); +} diff --git a/server/package.json b/server/package.json index 94bed17..11c3d69 100644 --- a/server/package.json +++ b/server/package.json @@ -8,7 +8,7 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { - "partykit": "^0.0.111", + "partykit": "^0.0.115", "typescript": "^5.6.0" } } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..657f41c --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + coverage: { + all: true, + exclude: ["**/*.test.ts", "web/src/vite-env.d.ts"], + include: ["protocol/**/*.ts", "server/src/**/*.ts", "web/src/**/*.ts"], + provider: "v8", + reporter: ["text", "json-summary"], + reportsDirectory: "coverage", + }, + include: [ + "protocol/**/*.test.ts", + "server/src/**/*.test.ts", + "web/src/**/*.test.ts", + ], + }, +}); diff --git a/web/package.json b/web/package.json index 77c2c8d..8916e12 100644 --- a/web/package.json +++ b/web/package.json @@ -13,6 +13,6 @@ }, "devDependencies": { "typescript": "^5.6.0", - "vite": "^5.4.0" + "vite": "^7.3.6" } } diff --git a/web/src/sdk/calibration.test.ts b/web/src/sdk/calibration.test.ts new file mode 100644 index 0000000..c870c1c --- /dev/null +++ b/web/src/sdk/calibration.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from "vitest"; + +import type { BodyController } from "./controller"; +import { Readiness } from "./calibration"; +import type { Room } from "./room"; + +function body(overrides: Partial = {}): BodyController { + return { + joints: { + head: [0.5, 0.2], + leftHand: [0.3, 0.5], + rightHand: [0.7, 0.5], + torso: [0.5, 0.5], + leftKnee: [0.4, 0.7], + rightKnee: [0.6, 0.7], + leftFoot: [0.4, 0.9], + rightFoot: [0.6, 0.9], + }, + squatAmount: 0, + leanAmount: 0, + leftHandOpen: 1, + rightHandOpen: 1, + trackingQuality: 0.9, + health: "ok", + hasRequiredJoints: true, + ageMs: 0, + tick: () => undefined, + dispose: () => undefined, + ...overrides, + }; +} + +describe("Readiness", () => { + it("requires an uninterrupted quality hold in debug mode", () => { + const readiness = new Readiness({} as Room, true); + + expect(readiness.evaluate(body(), 0).ready).toBe(false); + expect(readiness.evaluate(body(), 750).qualityHold).toBe(0.5); + expect(readiness.evaluate(body(), 1500)).toMatchObject({ + peer: true, + joints: true, + quality: true, + calibrated: true, + ready: true, + }); + + expect(readiness.evaluate(body({ health: "stale" }), 1600).ready).toBe( + false + ); + expect(readiness.evaluate(body(), 1700).qualityHold).toBe(0); + }); + + it("waits for both the peer and calibration outside debug mode", () => { + let calibrationListener: ((message: { stage: string }) => void) | undefined; + const room = { + peerConnected: false, + on: (event: string, listener: (message: { stage: string }) => void) => { + if (event === "calib") calibrationListener = listener; + }, + } as unknown as Room; + const readiness = new Readiness(room, false); + + expect(readiness.evaluate(body(), 0).peer).toBe(false); + room.peerConnected = true; + calibrationListener?.({ stage: "done" }); + readiness.evaluate(body(), 100); + + expect(readiness.evaluate(body(), 1600).ready).toBe(true); + }); +}); diff --git a/web/src/sdk/controller.test.ts b/web/src/sdk/controller.test.ts new file mode 100644 index 0000000..24178d9 --- /dev/null +++ b/web/src/sdk/controller.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from "vitest"; + +import type { Joints, PosePacket } from "../../../protocol/protocol"; +import { clamp01, clampSigned, PoseController } from "./controller"; + +const joints: Joints = { + head: [0.5, 0.2], + leftHand: [0.3, 0.5], + rightHand: [0.7, 0.5], + torso: [0.6, 0.5], + leftKnee: [0.4, 0.7], + rightKnee: [0.6, 0.7], + leftFoot: [0.4, 0.9], + rightFoot: [0.6, 0.9], +}; + +function packet(overrides: Partial = {}): PosePacket { + return { + v: 1, + type: "pose", + seq: 1, + sentAt: 0, + quality: 0.8, + joints, + ...overrides, + }; +} + +describe("PoseController", () => { + it("moves from no signal to healthy and then stale", () => { + const controller = new PoseController(); + + controller.tick(0); + expect(controller.health).toBe("no_signal"); + + controller.ingest(packet()); + controller.tick(10); + expect(controller.health).toBe("ok"); + expect(controller.hasRequiredJoints).toBe(true); + expect(controller.trackingQuality).toBe(0.8); + expect(controller.leanAmount).toBeCloseTo(0.1786, 3); + + controller.tick(500); + expect(controller.health).toBe("stale"); + expect(controller.ageMs).toBe(500); + }); + + it("rejects duplicate packets and clamps hand confidence", () => { + const controller = new PoseController(); + controller.tick(0); + controller.ingest(packet({ hands: { left: -2, right: 3 } })); + const torsoAfterFirstPacket = controller.joints.torso; + + controller.ingest( + packet({ + hands: { left: 1, right: 0 }, + joints: { ...joints, torso: [1, 1] }, + }) + ); + + expect(controller.joints.torso).toEqual(torsoAfterFirstPacket); + expect(controller.leftHandOpen).toBe(0.5); + expect(controller.rightHandOpen).toBe(1); + }); + + it("classifies low-confidence input", () => { + const controller = new PoseController(); + controller.tick(0); + controller.ingest(packet({ quality: 0.2 })); + controller.tick(1); + + expect(controller.health).toBe("low_quality"); + expect(controller.trackingQuality).toBe(0.2); + }); +}); + +describe("numeric bounds", () => { + it("clamps unsigned and signed values", () => { + expect([clamp01(-1), clamp01(0.4), clamp01(2)]).toEqual([0, 0.4, 1]); + expect([clampSigned(-2), clampSigned(0.4), clampSigned(2)]).toEqual([ + -1, 0.4, 1, + ]); + }); +}); diff --git a/web/src/sdk/controller.ts b/web/src/sdk/controller.ts index ff43d02..948bfae 100644 --- a/web/src/sdk/controller.ts +++ b/web/src/sdk/controller.ts @@ -65,7 +65,7 @@ export interface BodyController { } /** Required joints for the game to be playable. */ -export const REQUIRED_JOINTS: (keyof Joints)[] = [ +const REQUIRED_JOINTS: (keyof Joints)[] = [ "head", "leftHand", "rightHand", diff --git a/web/src/sdk/index.ts b/web/src/sdk/index.ts index 63f8da6..733a286 100644 --- a/web/src/sdk/index.ts +++ b/web/src/sdk/index.ts @@ -486,7 +486,6 @@ export class GameHost { } catch (err) { this.lastError = err instanceof Error ? err.message : String(err); this.lastErrorStack = err instanceof Error ? (err.stack ?? "") : ""; - // eslint-disable-next-line no-console console.error("[motion] frame error:", err); } if (this.debugMode || this.lastError) { diff --git a/web/src/sdk/webcam.ts b/web/src/sdk/webcam.ts index c1b65be..2b4f866 100644 --- a/web/src/sdk/webcam.ts +++ b/web/src/sdk/webcam.ts @@ -115,7 +115,6 @@ function neutralJoints(): Joints { } // Loose MediaPipe types — the module is imported at runtime from a URL. -/* eslint-disable @typescript-eslint/no-explicit-any */ type MpLandmark = { x: number; y: number; z: number }; type MpResult = { landmarks?: MpLandmark[][] }; type MpHandLandmarker = { detectForVideo(video: HTMLVideoElement, ts: number): MpResult }; @@ -224,7 +223,6 @@ export class WebcamController implements BodyController { const msg = err instanceof Error ? err.message : String(err); this.status.textContent = `Camera unavailable: ${msg}. Allow camera access (or pick a camera) and reload.`; - // eslint-disable-next-line no-console console.error("[motion] webcam init failed:", err); } }